// Settings catalog: the static core groups (settingsNav.js) merged with the // plugin-contributed cards from the ADR-010 get_settings_cards hook, served by // GET /api/pluginui/settings-cards. Both SettingsLayout (rail) and SettingsIndex // (landing) read the merged catalog so plugins add settings cards with no core // edit. Fetched once into a module-level ref and shared across callers. import { ref } from 'vue' import { Ruler, Wrench, Cog, Package, Settings, Puzzle, SlidersHorizontal, Palette, Bell, Network, Printer, Droplets } from 'lucide-vue-next' import api from '../api' import { settingsGroups } from '../views/settings/settingsNav' // Icon string keys (as returned by the hook) mapped to Lucide components, // same idea as the sidebar iconMap. Unknown keys fall back to a puzzle piece. const iconMap = { ruler: Ruler, wrench: Wrench, cog: Cog, package: Package, settings: Settings, puzzle: Puzzle, sliders: SlidersHorizontal, palette: Palette, bell: Bell, network: Network, printer: Printer, droplets: Droplets, } function resolveIcon(key) { return iconMap[key] || Puzzle } // Merge plugin cards into a fresh copy of the core groups. A card joins the // group whose title matches its `group`; a new group is appended at the end. function mergeCards(baseGroups, cards) { const merged = baseGroups.map(group => ({ title: group.title, cards: [...group.cards], })) const byTitle = new Map(merged.map(group => [group.title, group])) for (const card of cards) { const entry = { to: card.to, icon: resolveIcon(card.icon), title: card.title, description: card.description || '', position: card.position ?? 99, } let group = byTitle.get(card.group) if (!group) { group = { title: card.group, cards: [] } byTitle.set(card.group, group) merged.push(group) } group.cards.push(entry) } // Order plugin cards within a group by position; core cards keep their order. for (const group of merged) { group.cards.sort((a, b) => (a.position ?? 0) - (b.position ?? 0)) } return merged } // Shared across component instances: seeded with the core groups, replaced with // the merged catalog once the hook endpoint answers. const groups = ref(settingsGroups) let loaded = false async function loadCatalog() { try { const response = await api.get('/pluginui/settings-cards') groups.value = mergeCards(settingsGroups, response.data.data || []) } catch (error) { // Degrade to the core-only catalog; the rail still works without plugins. groups.value = settingsGroups } } export function useSettingsCatalog() { if (!loaded) { loaded = true loadCatalog() } return { groups } }