Skip to content

feat(webv2): duplicate any prompt enhancer LLM system prompt, and let admins edit shared ones - #239

Merged
lstein merged 6 commits into
mainfrom
feat/system-prompt-duplicate-and-admin-edit
Sep 13, 2026
Merged

feat(webv2): duplicate any prompt enhancer LLM system prompt, and let admins edit shared ones#239
lstein merged 6 commits into
mainfrom
feat/system-prompt-duplicate-and-admin-edit

Conversation

@lstein

@lstein lstein commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

On a multiuser install every seeded system prompt is owned by system and 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 though routers/system_prompts.py has always let an admin write any prompt. Each row offered a Shared label 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 canManageSharedSystemPrompts capability, derived from isAdmin exactly as canManagePromptTemplates is, travels the same capabilities → adapter → context path. Editability becomes:

own prompt  ||  (mayManageShared && record.isPublic)

applied both to the affordance and to requireEditableSystemPrompt in 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: classifySystemPrompts keeps those records out of the list entirely, and canEditSystemPrompt refuses 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

  • Harnesses that mock useGenerationUi for the prompt actions now need a capabilities entry — the hook reads it on every render, popover open or closed. Two existing browser tests needed it; the failure is a TypeError, not a soft default, because a missing provider should not be masked.
  • The editor route's ownedRawBytes budget 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 lint clean (format, oxlint, tsc, architecture)
  • pnpm test — 7969 passed
  • pnpm test:browser src/features/generation/ui/promptFields/ — 66 passed
  • pnpm test:performance:architecture — exit 0 (build gate and browser measurement), re-run against the committed tree

New 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

…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 lstein changed the title feat(webv2): duplicate any system prompt, and let admins edit shared ones feat(webv2): duplicate any prompt enhancer LLM system prompt, and let admins edit shared ones Sep 12, 2026
lstein and others added 5 commits September 13, 2026 11:26
…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
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.
@lstein
lstein merged commit be541d7 into main Sep 13, 2026
20 of 21 checks passed
@lstein
lstein deleted the feat/system-prompt-duplicate-and-admin-edit branch September 13, 2026 20:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant