feat(prompts): portal library reorg - collections, facets, versions, two-bucket model (#1010)#1024
Merged
Merged
Conversation
…two-bucket model (#1010) The prompt library presents two buckets: My Prompts (personal plus shared-with-me, attributed) and Library (approved shared prompts grouped by collection); scope taxonomy appears only in promote/admin flows. Collections are org-visible named groups (prompt_collections + prompts.collection_id, at most one per prompt, ON DELETE SET NULL) with creator-or-admin management and owner/admin assignment via new versionhttp REST routes on both surfaces. Facets (collection, tag, status, owner, usage), usage columns and sorting from the audit rollup (now including person-shared prompts), and an inactive badge make dead prompts identifiable. The prompt page adds version history with per-version approval provenance and line diffs (non-privileged viewers get served history only; pending drafts are content-redacted stubs), point-of-use invocation help, and a collection picker. Malformed UUID lookups map to 404, collection bodies are size-bounded, and the portal client accepts both RFC 9457 and {error} shapes.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1024 +/- ##
========================================
Coverage 89.30% 89.31%
========================================
Files 465 468 +3
Lines 51375 51652 +277
========================================
+ Hits 45883 46132 +249
- Misses 3650 3668 +18
- Partials 1842 1852 +10 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1010
Summary
The portal prompt library is reorganized around the user's actual mental model: two buckets instead of a flat three-tab table, collections instead of free-text category as the primary organizing structure, audit-derived usage on every row so dead prompts are identifiable at a glance, and a version history surface that shows exactly what was approved, by whom, and how any version differs from what is served today. The scope taxonomy (global, persona, personal) no longer appears anywhere in the user-facing library; it surfaces only inside the promote and admin flows where it belongs.
Two-bucket model
My Prompts merges the caller's personal prompts with prompts shared to them person-to-person, each shared row attributed with "Shared by ". Library shows the approved shared prompts visible to the caller, grouped by collection with uncollected prompts under a trailing General group. Semantic search spans both buckets: server-ranked results across personal and Library, plus name and description matches from the shared-with-me list.
Collections
Migration 000086 adds
prompt_collections(case-insensitively unique names, bounded name and description) andprompts.collection_idwithON DELETE SET NULL, so a prompt belongs to at most one collection and deleting a collection releases its members to the General group rather than deleting them. Placement is organizational metadata: assignment never produces a version snapshot, never triggers the review gate, and a review-gated content edit cannot smuggle a collection change (identityFieldsChangedincludesCollectionID).REST lives in
pkg/prompt/versionhttp(the established prompt REST surface;pkg/portalandpkg/adminsit at the package-size cap) and is mounted on both surfaces:GET/POST /prompt-collections,PUT/DELETE /prompt-collections/{id}, andPUT /prompts/{id}/collection. Any authenticated user can create a collection; renaming and deleting are creator-or-admin; assignment follows the prompt's own mutation rule (owner for personal prompts, admin for shared, system rows read-only). Bodies are capped at 1 MiB and malformed UUIDs resolve to 404 rather than 500 (SQLSTATE 22P02 maps to not-found in the store, including the pre-existingGetByIDpath).The collection capability reaches the handlers through a
CollectionProvideraccessor on the promptlayer notifying wrapper, so the store decorator exposes it from every capability-combination shape without doubling the wrapper matrix, and collection writes correctly skip theprompts/list_changedhook (they never change the MCP prompt list).Version history, diffs, and provenance
The prompt page renders the version list with per-version author, timestamp, status, and the approval stamp bound to that specific version, plus a line diff between any version and the current content (a small LCS implementation in
ui/src/lib/textDiff.ts; no new dependency). A pending draft on an approved shared prompt is flagged with a banner: readers keep being served the approved version until an admin approves the draft.GET /api/v1/portal/prompts/{id}/versionsaccess now mirrors prompt visibility instead of owner-or-admin, since history is the library's verification surface. Non-privileged viewers of a shared prompt receive the served history only: applied snapshots in full, the pending draft as a content-redacted author/date stub (its existence is the at-a-glance re-review signal), and rejected or superseded drafts omitted entirely. Owners keep full access to their personal history and admins to everything.Usage and dead-prompt identification
Every row shows run count and last-run age from the #1009
prompt_serveaudit rollup, joined client-side fromGET /portal/prompts/usage. Sorting covers name, runs, and last run (usage sorts default to most-active-first), a usage facet isolates recently-used or never/long-unused prompts, and prompts never run or unrun for over 60 days carry an inactive badge. The portal usage endpoint now also includes prompts shared person-to-person with the caller (wired from the portal share store), so shared rows show real usage instead of a false never-run state. While the rollup is loading or unavailable the columns show a dash rather than claiming inactivity.Facets and invocation help
Browse mode narrows by collection, tag, status (My Prompts), owner (Library), and usage, with facet state reset on tab switch so a filter set on one bucket never silently narrows the other. The prompt page adds a copyable natural-language invocation ("Run the prompt with date ") built from the stable name and required arguments, resolved agent-side by
manage_prompt use.UI structure
MyPromptsPageis decomposed into a list model (promptList.ts: rows, facets, decorate-sort),PromptListTable,PromptFacetsBar, andPromptCreateForm; the viewer gainsVersionHistory,InvocationHelp, andCollectionPicker. The viewer's badge vocabulary is now Personal / Library / System. The portal API client accepts the{"error": ...}body shape alongside RFC 9457detail, matching the surfaces it actually calls.Testing
pkg/prompt,pkg/prompt/postgres(sqlmock),pkg/prompt/versionhttp(full permission matrices, redaction, shared-usage inclusion, validation bounds), and the promptlayer capability accessor; every new function at or above the 80% gate.TestCollections_RealDB_RoundTrip) covering the migration schema: case-insensitive uniqueness, FK sentinel,ON DELETE SET NULLrelease, NULL-clearing assignment;make test-realdbgreen.ui/e2e/interactive/prompts.spec.ts) covering both buckets, attribution, collection grouping, every facet including the filtered empty state, run-count sort order, search across buckets, the collections manager create/duplicate/rename/delete flow, version history, diff open/close, viewer assignment, and back navigation, plus an assertion that the words Scope and Persona never render in the library.docs/server/portal-user.md;docs/llms.txtanddocs/llms-full.txtupdated to the implemented contracts.make verifygreen end to end.Depends on #1008 (stable invocation identity) and #1009 (versions, provenance, usage stats), both previously merged. The prompt browser MCP App (#1011) consumes the same collections, facets, and search this establishes.