diff --git a/frontend/src/api/designSystems.ts b/frontend/src/api/designSystems.ts index 89cb9ba..e3d7f21 100644 --- a/frontend/src/api/designSystems.ts +++ b/frontend/src/api/designSystems.ts @@ -74,11 +74,28 @@ export const fetchDesignSystems = () => export const fetchDesignSystem = (id: number) => apiGet(`/api/design-systems/${id}`); +export interface StarterRoleGroup { + group: string; + description: string; + token_count: number; + names: string[]; +} + +/** The starter token ROLES offered at creation — names and purposes, never + * values. A default palette would be one install's taste shipped as product + * (rule #115), so the values are always the operator's to fill. */ +export const listStarterRoleGroups = () => + apiGet<{ groups: StarterRoleGroup[]; default_prefix: string }>( + "/api/design-systems/starter-roles", + ); + export const createDesignSystem = (body: { title: string; description?: string; guidance?: string; parent_id?: number | null; + starter_role_groups?: string[]; + token_prefix?: string; }) => apiPost("/api/design-systems", body); /** Omit `parent_id` to leave it alone; send `null` to make the system a family. */ diff --git a/frontend/src/components/StarterRolePicker.vue b/frontend/src/components/StarterRolePicker.vue new file mode 100644 index 0000000..0011fdb --- /dev/null +++ b/frontend/src/components/StarterRolePicker.vue @@ -0,0 +1,212 @@ + + + + + diff --git a/frontend/src/views/DesignSystemsView.vue b/frontend/src/views/DesignSystemsView.vue index 805f859..ff50efd 100644 --- a/frontend/src/views/DesignSystemsView.vue +++ b/frontend/src/views/DesignSystemsView.vue @@ -42,6 +42,7 @@ import { import DesignTabs from "@/components/DesignTabs.vue"; import { ApiError } from "@/api/client"; import { useToastStore } from "@/stores/toast"; +import StarterRolePicker from "@/components/StarterRolePicker.vue"; const toast = useToastStore(); @@ -148,6 +149,10 @@ const newTitle = ref(""); const newDescription = ref(""); const newParentId = ref(null); const creating = ref(false); +// Starter roles (#2349). The picker fills these on mount; empty means the +// operator unchecked everything, which is a real answer. +const starterGroups = ref([]); +const tokenPrefix = ref(""); async function submitCreate() { const title = newTitle.value.trim(); @@ -158,11 +163,16 @@ async function submitCreate() { title, description: newDescription.value.trim() || undefined, parent_id: newParentId.value, + starter_role_groups: starterGroups.value.length ? starterGroups.value : undefined, + token_prefix: tokenPrefix.value.trim() || undefined, }); newTitle.value = ""; newDescription.value = ""; newParentId.value = null; showCreate.value = false; + // NOT reset: the picker owns these and re-seeds on mount. Clearing them + // here would race the next mount and silently create the following system + // with no roles at all. await loadSystems(); selectedId.value = created.id; toast.show(`Created ${created.title}`); @@ -540,6 +550,10 @@ function isColourish(value: string): boolean { placeholder="What it covers" /> +
+