Rule #27 — the backend half shipped without a surface an operator can touch,
so this is the other half of #2349.
StarterRolePicker is a component rather than inline markup because
DesignSystemsView has TWO creation forms: the empty state is a sibling branch
of the body, not a parent, so a form written into one is unreachable from the
other. Inlining the checklist would have made it the next thing in this
codebase defined twice and free to drift — which is what the button migration
spent nine commits undoing.
What it offers is names and purposes, never values. "Named now, valued later":
a role you haven't filled shows as to-be-decided, while a role that doesn't
exist is what gets written as a literal instead. Every group unchecks
individually, and the prefix is editable because `--fs-` is one family's
convention, not the product's.
Three deliberate details:
- All groups checked by DEFAULT, and that default lives in the UI, not the
service. create_design_system treats None and [] alike (seed nothing) so it
can never write 40 rows into a system whose caller never asked; a UI default
is visible and reversible before the click. Different layers, different
safe answers.
- A failed catalogue fetch is NOT fatal and does not read as an error. Starter
roles are an accelerator, not a prerequisite — the form still creates, and
the operator adds tokens by hand.
- The refs are not cleared after a successful create. The picker owns them and
re-seeds on mount; resetting here would race that and silently create the
next system with no roles.
props + defineEmits rather than defineModel, matching TagInput and the rest of
components/. defineModel is available (Vue 3.5) and would be shorter, but being
the only file in the codebase using a different binding idiom costs more than
the lines it saves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs