feat(webv2): duplicate any prompt enhancer LLM system prompt, and let admins edit shared ones - #239
Merged
Merged
Conversation
…ones On a multiuser install every seeded prompt is owned by `system` and public, so no one could edit them through the UI -- not even to fix a typo in a built-in, and not even as an admin, although the router has always allowed an admin to write any prompt. The list offered `Shared` and nothing else. **Duplicate** is now on every visible row, including rows the viewer cannot edit -- which is the point of it: copying is how someone adapts a prompt that is not theirs. It needs no rights over the source, copies the content and the output-token cap, names the copy `... (copy)` / `... (copy 2)` past anything already in the list, and opens it for editing. **Admins may edit shared prompts.** A `canManageSharedSystemPrompts` capability, derived from `isAdmin` exactly as `canManagePromptTemplates` is, travels the same capabilities -> adapter -> context path. Editability becomes `own || (mayManage && public)`, applied both to the affordance and to the hook's guard, which previously threw on anything unowned and would otherwise have blocked the case being enabled. **Another user's private prompt stays uneditable**, deliberately, even though the API would permit it: that is a moderation capability, not an everyday button. Two independent guards hold it -- `classifySystemPrompts` keeps those records out of the list, and `canEditSystemPrompt` refuses them regardless of rights, so a change to the first cannot silently expose them. A test pins the second. Editing a public prompt changes it for everyone, which the editor now says where the change is being made rather than leaving it to be discovered. The router is unchanged; its "admin may write anything" rule and the two tests documenting it stand. The restriction here is policy-by-affordance, so an admin with an HTTP client can still reach a private prompt. Two mechanical consequences: harnesses that mock `useGenerationUi` for the prompt actions now need `capabilities`, since the hook reads it on every render; and the editor route's `ownedRawBytes` budget, which is pinned with zero headroom, moves by the 53 bytes this adds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NQyWtiuACyQeGZeddtFdLk
lstein
requested review from
JPPhoto,
Pfannkuchensack and
blessedcoolant
as code owners
September 11, 2026 02:34
…plicate-and-admin-edit # Conflicts: # invokeai/frontend/webv2/performance/architecture-baseline.json
Merging main moved both perf baselines under this branch, so the pre-merge numbers no longer applied. Re-measured on the merged tree rather than carried over: - `architecture-baseline.json`: editor `ownedRawBytes` 131729 -> 131793. This budget is pinned with zero headroom, so any editor-code growth fails it by construction. The merge conflicted here (main 131729, this branch 131330); resolved to main's value and measured again, which gave 64 bytes rather than the 53 measured before the merge -- adding the old delta to main's number would have been wrong. - `browser-baseline.json`: editor-minimal and editor-gallery `scriptRawBytes` baseline 3511549 -> 3546763 with its ceiling derived as before. Worth naming: main already measures 3546699 on these routes, 34 bytes under the old ceiling, so most of this is main's drift since the 2026-09-07 capture, not this change's 64 bytes. Recording the measured value necessarily absorbs it. The surgical raise in #236 kept those ceilings honest but left no room, which is why a second budget surfaced behind the first here. launchpad, editor-canvas and editor-workflow are untouched; they never failed. Verified with `pnpm test:performance:architecture` on the merged tree: build gate and browser measurement both clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NQyWtiuACyQeGZeddtFdLk
…plicate-and-admin-edit
main moved twice while this branch was open, so the recorded budgets no longer matched the merged tree. Re-measured rather than extrapolated: - editor ownedRawBytes 132915 -> 132979 (zero-tolerance budget; this branch adds 64 bytes) - editor-minimal/gallery totalRawBytes 3844356 -> 3883548, ceiling derived at 1% as before Most of the totalRawBytes movement is main's drift since the last capture, not this branch's contribution.
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.
Summary
On a multiuser install every seeded system prompt is owned by
systemand public, so nobody could edit one through the UI — not to fix a typo in a built-in, and not even as an admin, even thoughrouters/system_prompts.pyhas always let an admin write any prompt. Each row offered aSharedlabel and no controls.Two changes, plus one deliberate non-change.
Duplicate, for everyone
A copy control now sits on every visible row, including ones the viewer cannot edit — which is the point of it: copying is how you adapt a prompt that is not yours. It needs no rights over the source, copies the content and the output-token cap, names the copy
… (copy)then… (copy 2)past anything already in the list, and opens it for editing.Admin edit of shared prompts
A
canManageSharedSystemPromptscapability, derived fromisAdminexactly ascanManagePromptTemplatesis, travels the same capabilities → adapter → context path. Editability becomes:applied both to the affordance and to
requireEditableSystemPromptin the hook, which previously threw on anything unowned and would otherwise have blocked the very case being enabled.Editing a public prompt changes it for every user, so the editor now says so where the change is being made rather than leaving it to be discovered.
Another user's private prompt stays uneditable
Deliberately, even though the API would permit it — that is a moderation capability, not an everyday button. Two independent guards hold it:
classifySystemPromptskeeps those records out of the list entirely, andcanEditSystemPromptrefuses them regardless of rights, so a change to the first cannot silently expose them. A test pins the second.The router is unchanged. Its "an admin may write anything" rule and the two tests documenting it (
test_admin_can_patch_any_users_prompt,test_admin_can_delete_any_users_prompt) stand. The restriction here is therefore policy-by-affordance: an admin with an HTTP client can still PATCH another user's private prompt. Tightening the API is a separate, breaking change and was consciously not taken.Consequences worth knowing
useGenerationUifor the prompt actions now need acapabilitiesentry — the hook reads it on every render, popover open or closed. Two existing browser tests needed it; the failure is aTypeError, not a soft default, because a missing provider should not be masked.ownedRawBytesbudget is pinned with zero headroom, so the 53 bytes this adds fails it by construction. Raised surgically: that one metric, that one route, in both the captured value and its limit. The launchpad entry is untouched.Test plan
pnpm lintclean (format, oxlint, tsc, architecture)pnpm test— 7969 passedpnpm test:browser src/features/generation/ui/promptFields/— 66 passedpnpm test:performance:architecture— exit 0 (build gate and browser measurement), re-run against the committed treeNew core tests cover the manager / member / single-user matrix, the refusal of another user's private prompt even for a manager, and duplicate naming including gap-skipping and copying a copy.
No browser test was added for the duplicate flow: the decisions it makes (who may edit, what the copy is named) live in
core/and are unit-tested there, and what remains in the component is wiring a button to a catalog call — coverage of that would mostly assert internal choreography through mocks.🤖 Generated with Claude Code
https://claude.ai/code/session_01NQyWtiuACyQeGZeddtFdLk