@stonecrop/desktop 0.33.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"desktop.js","names":[],"sources":["../src/components/ActionSet.vue","../src/components/ActionSet.vue","../src/components/CommandPalette.vue","../src/components/CommandPalette.vue","../src/sheet-nav-toolbar.ts","../src/components/SheetNav.vue","../src/components/SheetNav.vue","../src/components/Desktop.vue","../src/components/Desktop.vue","../src/plugins/index.ts"],"sourcesContent":["<template>\n\t<div :class=\"{ collapsed: !isOpen }\" class=\"action-set\">\n\t\t<div class=\"action-menu-icon\">\n\t\t\t<div id=\"cross\" :class=\"{ rotated: isOpen }\" @click=\"onClick\">×</div>\n\t\t</div>\n\t\t<div style=\"margin-right: 30px\"></div>\n\t\t<div v-for=\"(el, index) in elements\" :key=\"el.label\" class=\"action-element\">\n\t\t\t<div class=\"action-element-header\">\n\t\t\t\t<button\n\t\t\t\t\tv-if=\"el.type == 'button'\"\n\t\t\t\t\t:disabled=\"el.disabled\"\n\t\t\t\t\tclass=\"button-default\"\n\t\t\t\t\t@click=\"handleClick(el.action, el.label)\">\n\t\t\t\t\t{{ el.label }}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<div v-if=\"el.type == 'dropdown'\">\n\t\t\t\t<div class=\"dropdown-header\">\n\t\t\t\t\t<div class=\"cross\" :class=\"{ rotated: dropdownOpen[index] }\" @click=\"toggleDropdown(index)\">×</div>\n\t\t\t\t\t<button class=\"button-default dropdown-title\" @click=\"toggleDropdown(index)\">\n\t\t\t\t\t\t{{ el.label }}\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t\t<div v-show=\"dropdownStates[index]\" class=\"dropdown-container\">\n\t\t\t\t\t<div class=\"dropdown\">\n\t\t\t\t\t\t<div v-for=\"item in el.actions\" :key=\"item.label\">\n\t\t\t\t\t\t\t<button v-if=\"item.action != null\" class=\"dropdown-item\" @click=\"handleClick(item.action, item.label)\">\n\t\t\t\t\t\t\t\t{{ item.label }}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<a v-else-if=\"item.link != null\" :href=\"item.link\"\n\t\t\t\t\t\t\t\t><button class=\"dropdown-item\">{{ item.label }}</button></a\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\n\nimport type { ActionElements } from '../types'\n\nconst { elements = [] } = defineProps<{ elements?: ActionElements[] }>()\nconst emit = defineEmits<{\n\tactionClick: [label: string, action: (() => void | Promise<void>) | undefined]\n}>()\n\n// Track dropdown open state separately (index -> boolean)\nconst dropdownStates = ref<Record<number, boolean>>({})\n\nconst isOpen = ref(true)\nconst dropdownOpen = ref<boolean[]>([])\n\nonMounted(() => {\n\tcloseDropdowns()\n})\n\nconst closeDropdowns = () => {\n\tdropdownStates.value = {}\n\tdropdownOpen.value = []\n}\n\nconst onClick = () => {\n\tisOpen.value = !isOpen.value\n}\n\nconst toggleDropdown = (index: number) => {\n\tconst showDropdown = !dropdownStates.value[index]\n\tcloseDropdowns()\n\tif (showDropdown) {\n\t\tdropdownStates.value[index] = true\n\t\tdropdownOpen.value[index] = true\n\t}\n}\n\nconst handleClick = (action: (() => void | Promise<void>) | undefined, label: string) => {\n\t// Emit event to parent - parent will handle execution\n\temit('actionClick', label, action)\n}\n</script>\n\n<style scoped>\n#cross {\n\tposition: relative;\n\ttransform: rotate(45deg);\n\tcursor: pointer;\n\ttransition: all 0.2s ease-in-out;\n\tuser-select: none;\n\tline-height: 1rem;\n}\n#cross.rotated,\n.cross.rotated {\n\ttransform: rotate(0deg);\n}\n#cross svg {\n\twidth: 1.5em;\n\theight: 1.5em;\n}\n\n.action-set {\n\tposition: fixed;\n\ttop: 300px;\n\tright: 10px;\n\tpadding: 10px;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-end;\n\tbackground: var(--sc-form-background);\n\tborder: 1px solid var(--sc-gray-20);\n\tborder-left: 4px solid var(--sc-gray-20);\n\tborder-radius: 0;\n\toverflow: hidden;\n\tz-index: 1001; /* Above SheetNav (100) and operation log button (999) */\n\t-webkit-transition: all 0.5s ease-in-out;\n\t-moz-transition: all 0.5s ease-in-out;\n\t-o-transition: all 0.5s ease-in-out;\n\ttransition: all 0.5s ease-in-out;\n}\n.action-menu-icon {\n\tposition: relative;\n\tfont-size: 2rem;\n\tdisplay: inline-block;\n\tcolor: var(--sc-gray-60);\n\ttransition: all 0.2s ease-in-out;\n}\n.action-set.collapsed {\n\tmax-width: 46px;\n\tmax-height: 40px;\n\toverflow: hidden;\n}\n.action-set.collapsed .action-element {\n\topacity: 0;\n\t-webkit-transition: opacity 0.25s ease-in-out;\n\t-moz-transition: opacity 0.25s ease-in-out;\n\t-o-transition: opacity 0.25s ease-in-out;\n\ttransition: opacity 0.25s ease-in-out;\n}\n\n.action-element {\n\twidth: 100%;\n\ttext-align: right;\n\tborder: 1px solid var(--sc-gray-20);\n\tbackground: none;\n\tfont-size: 1.5rem;\n\tfont-family: var(--sc-font-family);\n\tfont-weight: 600;\n\tmargin-top: 10px;\n\tposition: relative; /* Make this the positioning context for absolute children */\n}\n.action-element-header {\n\tdisplay: flex;\n\tjustify-content: end;\n}\nbutton.button-default {\n\tbackground-color: transparent;\n\tpadding: 5px 12px;\n\tborder-radius: 0px;\n\tbox-shadow: none;\n\tborder: none;\n\tcursor: pointer;\n\twhite-space: nowrap;\n\tfont-weight: bold;\n\tfont-size: 1rem;\n\ttext-align: right;\n\tcolor: var(--sc-gray-80);\n\tpadding-left: 50px;\n\tfont-family: var(--sc-font-family);\n}\n.dropdown-header:hover button.button-default,\n.dropdown-header:hover,\n.action-element-header:hover {\n\tbackground-color: #f2f2f2;\n}\n\n.dropdown-title {\n\tposition: relative;\n}\n.dropdown-header {\n\tdisplay: flex;\n\talign-items: center;\n}\n.cross {\n\tpointer-events: all;\n\tmargin-left: 5px;\n\tfont-family: var(--sc-font-family);\n\tcolor: var(--sc-gray-60);\n\tcursor: pointer;\n\ttransform: rotate(45deg);\n\tuser-select: none;\n\ttransition: all 0.2s ease-in-out;\n\tline-height: 1rem;\n}\n\nbutton.dropdown-item {\n\twidth: 100%;\n\tpadding: 5px 12px;\n\ttext-align: right;\n\tborder: none;\n\tbackground-color: #ffffff;\n\tcursor: pointer;\n\tborder-radius: 5px;\n\tfont-size: 1rem;\n}\n\nbutton.dropdown-item:hover {\n\tbackground-color: #f2f2f2;\n}\n</style>\n","<template>\n\t<div :class=\"{ collapsed: !isOpen }\" class=\"action-set\">\n\t\t<div class=\"action-menu-icon\">\n\t\t\t<div id=\"cross\" :class=\"{ rotated: isOpen }\" @click=\"onClick\">×</div>\n\t\t</div>\n\t\t<div style=\"margin-right: 30px\"></div>\n\t\t<div v-for=\"(el, index) in elements\" :key=\"el.label\" class=\"action-element\">\n\t\t\t<div class=\"action-element-header\">\n\t\t\t\t<button\n\t\t\t\t\tv-if=\"el.type == 'button'\"\n\t\t\t\t\t:disabled=\"el.disabled\"\n\t\t\t\t\tclass=\"button-default\"\n\t\t\t\t\t@click=\"handleClick(el.action, el.label)\">\n\t\t\t\t\t{{ el.label }}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<div v-if=\"el.type == 'dropdown'\">\n\t\t\t\t<div class=\"dropdown-header\">\n\t\t\t\t\t<div class=\"cross\" :class=\"{ rotated: dropdownOpen[index] }\" @click=\"toggleDropdown(index)\">×</div>\n\t\t\t\t\t<button class=\"button-default dropdown-title\" @click=\"toggleDropdown(index)\">\n\t\t\t\t\t\t{{ el.label }}\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t\t<div v-show=\"dropdownStates[index]\" class=\"dropdown-container\">\n\t\t\t\t\t<div class=\"dropdown\">\n\t\t\t\t\t\t<div v-for=\"item in el.actions\" :key=\"item.label\">\n\t\t\t\t\t\t\t<button v-if=\"item.action != null\" class=\"dropdown-item\" @click=\"handleClick(item.action, item.label)\">\n\t\t\t\t\t\t\t\t{{ item.label }}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<a v-else-if=\"item.link != null\" :href=\"item.link\"\n\t\t\t\t\t\t\t\t><button class=\"dropdown-item\">{{ item.label }}</button></a\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\n\nimport type { ActionElements } from '../types'\n\nconst { elements = [] } = defineProps<{ elements?: ActionElements[] }>()\nconst emit = defineEmits<{\n\tactionClick: [label: string, action: (() => void | Promise<void>) | undefined]\n}>()\n\n// Track dropdown open state separately (index -> boolean)\nconst dropdownStates = ref<Record<number, boolean>>({})\n\nconst isOpen = ref(true)\nconst dropdownOpen = ref<boolean[]>([])\n\nonMounted(() => {\n\tcloseDropdowns()\n})\n\nconst closeDropdowns = () => {\n\tdropdownStates.value = {}\n\tdropdownOpen.value = []\n}\n\nconst onClick = () => {\n\tisOpen.value = !isOpen.value\n}\n\nconst toggleDropdown = (index: number) => {\n\tconst showDropdown = !dropdownStates.value[index]\n\tcloseDropdowns()\n\tif (showDropdown) {\n\t\tdropdownStates.value[index] = true\n\t\tdropdownOpen.value[index] = true\n\t}\n}\n\nconst handleClick = (action: (() => void | Promise<void>) | undefined, label: string) => {\n\t// Emit event to parent - parent will handle execution\n\temit('actionClick', label, action)\n}\n</script>\n\n<style scoped>\n#cross {\n\tposition: relative;\n\ttransform: rotate(45deg);\n\tcursor: pointer;\n\ttransition: all 0.2s ease-in-out;\n\tuser-select: none;\n\tline-height: 1rem;\n}\n#cross.rotated,\n.cross.rotated {\n\ttransform: rotate(0deg);\n}\n#cross svg {\n\twidth: 1.5em;\n\theight: 1.5em;\n}\n\n.action-set {\n\tposition: fixed;\n\ttop: 300px;\n\tright: 10px;\n\tpadding: 10px;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-end;\n\tbackground: var(--sc-form-background);\n\tborder: 1px solid var(--sc-gray-20);\n\tborder-left: 4px solid var(--sc-gray-20);\n\tborder-radius: 0;\n\toverflow: hidden;\n\tz-index: 1001; /* Above SheetNav (100) and operation log button (999) */\n\t-webkit-transition: all 0.5s ease-in-out;\n\t-moz-transition: all 0.5s ease-in-out;\n\t-o-transition: all 0.5s ease-in-out;\n\ttransition: all 0.5s ease-in-out;\n}\n.action-menu-icon {\n\tposition: relative;\n\tfont-size: 2rem;\n\tdisplay: inline-block;\n\tcolor: var(--sc-gray-60);\n\ttransition: all 0.2s ease-in-out;\n}\n.action-set.collapsed {\n\tmax-width: 46px;\n\tmax-height: 40px;\n\toverflow: hidden;\n}\n.action-set.collapsed .action-element {\n\topacity: 0;\n\t-webkit-transition: opacity 0.25s ease-in-out;\n\t-moz-transition: opacity 0.25s ease-in-out;\n\t-o-transition: opacity 0.25s ease-in-out;\n\ttransition: opacity 0.25s ease-in-out;\n}\n\n.action-element {\n\twidth: 100%;\n\ttext-align: right;\n\tborder: 1px solid var(--sc-gray-20);\n\tbackground: none;\n\tfont-size: 1.5rem;\n\tfont-family: var(--sc-font-family);\n\tfont-weight: 600;\n\tmargin-top: 10px;\n\tposition: relative; /* Make this the positioning context for absolute children */\n}\n.action-element-header {\n\tdisplay: flex;\n\tjustify-content: end;\n}\nbutton.button-default {\n\tbackground-color: transparent;\n\tpadding: 5px 12px;\n\tborder-radius: 0px;\n\tbox-shadow: none;\n\tborder: none;\n\tcursor: pointer;\n\twhite-space: nowrap;\n\tfont-weight: bold;\n\tfont-size: 1rem;\n\ttext-align: right;\n\tcolor: var(--sc-gray-80);\n\tpadding-left: 50px;\n\tfont-family: var(--sc-font-family);\n}\n.dropdown-header:hover button.button-default,\n.dropdown-header:hover,\n.action-element-header:hover {\n\tbackground-color: #f2f2f2;\n}\n\n.dropdown-title {\n\tposition: relative;\n}\n.dropdown-header {\n\tdisplay: flex;\n\talign-items: center;\n}\n.cross {\n\tpointer-events: all;\n\tmargin-left: 5px;\n\tfont-family: var(--sc-font-family);\n\tcolor: var(--sc-gray-60);\n\tcursor: pointer;\n\ttransform: rotate(45deg);\n\tuser-select: none;\n\ttransition: all 0.2s ease-in-out;\n\tline-height: 1rem;\n}\n\nbutton.dropdown-item {\n\twidth: 100%;\n\tpadding: 5px 12px;\n\ttext-align: right;\n\tborder: none;\n\tbackground-color: #ffffff;\n\tcursor: pointer;\n\tborder-radius: 5px;\n\tfont-size: 1rem;\n}\n\nbutton.dropdown-item:hover {\n\tbackground-color: #f2f2f2;\n}\n</style>\n","<template>\n\t<Teleport to=\"body\">\n\t\t<Transition name=\"fade\">\n\t\t\t<div v-if=\"isOpen\" class=\"command-palette-overlay\" @click=\"closeModal\">\n\t\t\t\t<div class=\"command-palette\" @click.stop>\n\t\t\t\t\t<div class=\"command-palette-header\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tref=\"input\"\n\t\t\t\t\t\t\tv-model=\"query\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tclass=\"command-palette-input\"\n\t\t\t\t\t\t\t:placeholder=\"placeholder\"\n\t\t\t\t\t\t\tautofocus\n\t\t\t\t\t\t\t@keydown=\"handleKeydown\" />\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div v-if=\"results.length\" class=\"command-palette-results\">\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tv-for=\"(result, index) in results\"\n\t\t\t\t\t\t\t:key=\"index\"\n\t\t\t\t\t\t\tclass=\"command-palette-result\"\n\t\t\t\t\t\t\t:class=\"{ selected: index === selectedIndex }\"\n\t\t\t\t\t\t\t@click=\"selectResult(result)\"\n\t\t\t\t\t\t\t@mouseover=\"selectedIndex = index\">\n\t\t\t\t\t\t\t<div class=\"result-title\">\n\t\t\t\t\t\t\t\t<slot name=\"title\" :result=\"result\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div class=\"result-content\">\n\t\t\t\t\t\t\t\t<slot name=\"content\" :result=\"result\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div v-else-if=\"query && !results.length\" class=\"command-palette-no-results\">\n\t\t\t\t\t\t<slot name=\"empty\"> No results found for \"{{ query }}\" </slot>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</Transition>\n\t</Teleport>\n</template>\n\n<script setup lang=\"ts\" generic=\"T\">\nimport { ref, computed, watch, nextTick, useTemplateRef } from 'vue'\n\ndefineSlots<{\n\ttitle?: { result: T }\n\tcontent?: { result: T }\n\tempty?: null\n}>()\n\nconst {\n\tsearch,\n\tisOpen = false,\n\tplaceholder = 'Type a command or search...',\n\tmaxResults = 10,\n} = defineProps<{\n\tsearch: (query: string) => T[]\n\tisOpen?: boolean\n\tplaceholder?: string\n\tmaxResults?: number\n}>()\n\nconst emit = defineEmits<{\n\tselect: [T]\n\tclose: []\n}>()\n\nconst query = ref('')\nconst selectedIndex = ref(0)\nconst inputRef = useTemplateRef('input')\n\nconst results = computed(() => {\n\tif (!query.value) return []\n\tconst searchResults = search(query.value)\n\treturn searchResults.slice(0, maxResults)\n})\n\n// reset search query when modal opens\nwatch(\n\t() => isOpen,\n\tasync open => {\n\t\tif (open) {\n\t\t\tquery.value = ''\n\t\t\tselectedIndex.value = 0\n\t\t\tawait nextTick()\n\t\t\t;(inputRef.value as HTMLInputElement)?.focus()\n\t\t}\n\t}\n)\n\n// reset selected index when results change\nwatch(results, () => {\n\tselectedIndex.value = 0\n})\n\nconst closeModal = () => {\n\temit('close')\n}\n\nconst handleKeydown = (e: KeyboardEvent) => {\n\tswitch (e.key) {\n\t\tcase 'Escape':\n\t\t\tcloseModal()\n\t\t\tbreak\n\t\tcase 'ArrowDown':\n\t\t\te.preventDefault()\n\t\t\tif (results.value.length) {\n\t\t\t\tselectedIndex.value = (selectedIndex.value + 1) % results.value.length\n\t\t\t}\n\t\t\tbreak\n\t\tcase 'ArrowUp':\n\t\t\te.preventDefault()\n\t\t\tif (results.value.length) {\n\t\t\t\tselectedIndex.value = (selectedIndex.value - 1 + results.value.length) % results.value.length\n\t\t\t}\n\t\t\tbreak\n\t\tcase 'Enter':\n\t\t\tif (results.value.length && selectedIndex.value >= 0) {\n\t\t\t\tselectResult(results.value[selectedIndex.value])\n\t\t\t}\n\t\t\tbreak\n\t}\n}\n\nconst selectResult = (result: T) => {\n\temit('select', result)\n\tcloseModal()\n}\n</script>\n\n<style>\n.fade-enter-active,\n.fade-leave-active {\n\ttransition: opacity 0.2s ease;\n}\n\n.fade-enter-from,\n.fade-leave-to {\n\topacity: 0;\n}\n\n.command-palette-overlay {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n\tbackground-color: rgba(0, 0, 0, 0.5);\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: center;\n\tz-index: 300;\n\tpadding-top: 100px;\n}\n\n.command-palette {\n\twidth: 600px;\n\tmax-width: 90%;\n\tbackground-color: white;\n\tborder-radius: 8px;\n\tbox-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);\n\toverflow: hidden;\n\tmax-height: 80vh;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.command-palette-header {\n\tdisplay: flex;\n\tborder-bottom: 1px solid #eaeaea;\n\tpadding: 12px;\n}\n\n.command-palette-input {\n\tflex: 1;\n\tborder: none;\n\toutline: none;\n\tfont-size: 16px;\n\tpadding: 8px 12px;\n\tbackground-color: transparent;\n}\n\n.command-palette-close {\n\tbackground: transparent;\n\tborder: none;\n\tfont-size: 24px;\n\tcursor: pointer;\n\tcolor: #666;\n\tpadding: 0 8px;\n}\n\n.command-palette-close:hover {\n\tcolor: #333;\n}\n\n.command-palette-results {\n\toverflow-y: auto;\n\tmax-height: 60vh;\n}\n\n.command-palette-result {\n\tpadding: 12px 16px;\n\tcursor: pointer;\n\tborder-bottom: 1px solid #f0f0f0;\n}\n\n.command-palette-result:hover,\n.command-palette-result.selected {\n\tbackground-color: #f5f5f5;\n}\n\n.command-palette-result.selected {\n\tbackground-color: rgba(132, 60, 3, 0.1);\n}\n\n.result-title {\n\tfont-weight: 500;\n\tmargin-bottom: 4px;\n\tcolor: #333;\n}\n\n.result-content {\n\tfont-size: 14px;\n\tcolor: #666;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.command-palette-no-results {\n\tpadding: 20px 16px;\n\ttext-align: center;\n\tcolor: #666;\n}\n</style>\n","<template>\n\t<Teleport to=\"body\">\n\t\t<Transition name=\"fade\">\n\t\t\t<div v-if=\"isOpen\" class=\"command-palette-overlay\" @click=\"closeModal\">\n\t\t\t\t<div class=\"command-palette\" @click.stop>\n\t\t\t\t\t<div class=\"command-palette-header\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tref=\"input\"\n\t\t\t\t\t\t\tv-model=\"query\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tclass=\"command-palette-input\"\n\t\t\t\t\t\t\t:placeholder=\"placeholder\"\n\t\t\t\t\t\t\tautofocus\n\t\t\t\t\t\t\t@keydown=\"handleKeydown\" />\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div v-if=\"results.length\" class=\"command-palette-results\">\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tv-for=\"(result, index) in results\"\n\t\t\t\t\t\t\t:key=\"index\"\n\t\t\t\t\t\t\tclass=\"command-palette-result\"\n\t\t\t\t\t\t\t:class=\"{ selected: index === selectedIndex }\"\n\t\t\t\t\t\t\t@click=\"selectResult(result)\"\n\t\t\t\t\t\t\t@mouseover=\"selectedIndex = index\">\n\t\t\t\t\t\t\t<div class=\"result-title\">\n\t\t\t\t\t\t\t\t<slot name=\"title\" :result=\"result\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div class=\"result-content\">\n\t\t\t\t\t\t\t\t<slot name=\"content\" :result=\"result\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div v-else-if=\"query && !results.length\" class=\"command-palette-no-results\">\n\t\t\t\t\t\t<slot name=\"empty\"> No results found for \"{{ query }}\" </slot>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</Transition>\n\t</Teleport>\n</template>\n\n<script setup lang=\"ts\" generic=\"T\">\nimport { ref, computed, watch, nextTick, useTemplateRef } from 'vue'\n\ndefineSlots<{\n\ttitle?: { result: T }\n\tcontent?: { result: T }\n\tempty?: null\n}>()\n\nconst {\n\tsearch,\n\tisOpen = false,\n\tplaceholder = 'Type a command or search...',\n\tmaxResults = 10,\n} = defineProps<{\n\tsearch: (query: string) => T[]\n\tisOpen?: boolean\n\tplaceholder?: string\n\tmaxResults?: number\n}>()\n\nconst emit = defineEmits<{\n\tselect: [T]\n\tclose: []\n}>()\n\nconst query = ref('')\nconst selectedIndex = ref(0)\nconst inputRef = useTemplateRef('input')\n\nconst results = computed(() => {\n\tif (!query.value) return []\n\tconst searchResults = search(query.value)\n\treturn searchResults.slice(0, maxResults)\n})\n\n// reset search query when modal opens\nwatch(\n\t() => isOpen,\n\tasync open => {\n\t\tif (open) {\n\t\t\tquery.value = ''\n\t\t\tselectedIndex.value = 0\n\t\t\tawait nextTick()\n\t\t\t;(inputRef.value as HTMLInputElement)?.focus()\n\t\t}\n\t}\n)\n\n// reset selected index when results change\nwatch(results, () => {\n\tselectedIndex.value = 0\n})\n\nconst closeModal = () => {\n\temit('close')\n}\n\nconst handleKeydown = (e: KeyboardEvent) => {\n\tswitch (e.key) {\n\t\tcase 'Escape':\n\t\t\tcloseModal()\n\t\t\tbreak\n\t\tcase 'ArrowDown':\n\t\t\te.preventDefault()\n\t\t\tif (results.value.length) {\n\t\t\t\tselectedIndex.value = (selectedIndex.value + 1) % results.value.length\n\t\t\t}\n\t\t\tbreak\n\t\tcase 'ArrowUp':\n\t\t\te.preventDefault()\n\t\t\tif (results.value.length) {\n\t\t\t\tselectedIndex.value = (selectedIndex.value - 1 + results.value.length) % results.value.length\n\t\t\t}\n\t\t\tbreak\n\t\tcase 'Enter':\n\t\t\tif (results.value.length && selectedIndex.value >= 0) {\n\t\t\t\tselectResult(results.value[selectedIndex.value])\n\t\t\t}\n\t\t\tbreak\n\t}\n}\n\nconst selectResult = (result: T) => {\n\temit('select', result)\n\tcloseModal()\n}\n</script>\n\n<style>\n.fade-enter-active,\n.fade-leave-active {\n\ttransition: opacity 0.2s ease;\n}\n\n.fade-enter-from,\n.fade-leave-to {\n\topacity: 0;\n}\n\n.command-palette-overlay {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n\tbackground-color: rgba(0, 0, 0, 0.5);\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: center;\n\tz-index: 300;\n\tpadding-top: 100px;\n}\n\n.command-palette {\n\twidth: 600px;\n\tmax-width: 90%;\n\tbackground-color: white;\n\tborder-radius: 8px;\n\tbox-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);\n\toverflow: hidden;\n\tmax-height: 80vh;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.command-palette-header {\n\tdisplay: flex;\n\tborder-bottom: 1px solid #eaeaea;\n\tpadding: 12px;\n}\n\n.command-palette-input {\n\tflex: 1;\n\tborder: none;\n\toutline: none;\n\tfont-size: 16px;\n\tpadding: 8px 12px;\n\tbackground-color: transparent;\n}\n\n.command-palette-close {\n\tbackground: transparent;\n\tborder: none;\n\tfont-size: 24px;\n\tcursor: pointer;\n\tcolor: #666;\n\tpadding: 0 8px;\n}\n\n.command-palette-close:hover {\n\tcolor: #333;\n}\n\n.command-palette-results {\n\toverflow-y: auto;\n\tmax-height: 60vh;\n}\n\n.command-palette-result {\n\tpadding: 12px 16px;\n\tcursor: pointer;\n\tborder-bottom: 1px solid #f0f0f0;\n}\n\n.command-palette-result:hover,\n.command-palette-result.selected {\n\tbackground-color: #f5f5f5;\n}\n\n.command-palette-result.selected {\n\tbackground-color: rgba(132, 60, 3, 0.1);\n}\n\n.result-title {\n\tfont-weight: 500;\n\tmargin-bottom: 4px;\n\tcolor: #333;\n}\n\n.result-content {\n\tfont-size: 14px;\n\tcolor: #666;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.command-palette-no-results {\n\tpadding: 20px 16px;\n\ttext-align: center;\n\tcolor: #666;\n}\n</style>\n","/** The id of the element SheetNav renders for footer controls, left of its tabs. */\nexport const SHEET_NAV_TOOLBAR_ID = 'sheetnav-toolbar'\n\n/** Teleport target for footer controls rendered anywhere on the page: `<Teleport :to=\"SHEET_NAV_TOOLBAR_SELECTOR\">`. */\nexport const SHEET_NAV_TOOLBAR_SELECTOR = `#${SHEET_NAV_TOOLBAR_ID}`\n","<template>\n\t<footer>\n\t\t<div class=\"sheetnav-footer-cluster\">\n\t\t\t<div :id=\"SHEET_NAV_TOOLBAR_ID\" class=\"sheetnav-toolbar\">\n\t\t\t\t<slot name=\"toolbar\" />\n\t\t\t</div>\n\t\t\t<ul class=\"tabs\">\n\t\t\t\t<li class=\"hidebreadcrumbs\" @click=\"toggleBreadcrumbs\" @keydown.enter=\"toggleBreadcrumbs\">\n\t\t\t\t\t<a tabindex=\"0\"><div :class=\"rotateHideTabIcon\">×</div></a>\n\t\t\t\t</li>\n\t\t\t\t<li\n\t\t\t\t\tclass=\"hometab\"\n\t\t\t\t\t:style=\"{ display: breadcrumbsVisibile ? 'flex' : 'none' }\"\n\t\t\t\t\t@click=\"navigateHome\"\n\t\t\t\t\t@keydown.enter=\"navigateHome\">\n\t\t\t\t\t<router-link to=\"/\" tabindex=\"0\">\n\t\t\t\t\t\t<svg class=\"icon\" aria-label=\"Home\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n\t\t\t\t\t\t\t<path d=\"M3 12l9-9 9 9\" />\n\t\t\t\t\t\t\t<path d=\"M9 21V12h6v9\" />\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t</router-link>\n\t\t\t\t</li>\n\t\t\t\t<li\n\t\t\t\t\t:class=\"['searchtab', { 'search-active': searchVisible }]\"\n\t\t\t\t\t:style=\"{ display: breadcrumbsVisibile ? 'flex' : 'none' }\">\n\t\t\t\t\t<a tabindex=\"0\">\n\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\tv-show=\"!searchVisible\"\n\t\t\t\t\t\t\tclass=\"icon search-icon\"\n\t\t\t\t\t\t\trole=\"button\"\n\t\t\t\t\t\t\taria-label=\"Search\"\n\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\t\tstroke-width=\"2\"\n\t\t\t\t\t\t\t@click=\"toggleSearch\"\n\t\t\t\t\t\t\t@keydown.enter=\"toggleSearch\">\n\t\t\t\t\t\t\t<circle cx=\"11\" cy=\"11\" r=\"7\" />\n\t\t\t\t\t\t\t<path d=\"M21 21l-4.35-4.35\" />\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tv-show=\"searchVisible\"\n\t\t\t\t\t\t\tref=\"searchinput\"\n\t\t\t\t\t\t\tv-model=\"searchText\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Search...\"\n\t\t\t\t\t\t\t@click.stop\n\t\t\t\t\t\t\t@input=\"handleSearchInput($event)\"\n\t\t\t\t\t\t\t@blur=\"handleSearch($event)\"\n\t\t\t\t\t\t\t@keydown.enter=\"handleSearch($event)\"\n\t\t\t\t\t\t\t@keydown.escape=\"toggleSearch\" />\n\t\t\t\t\t</a>\n\t\t\t\t</li>\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"breadcrumb in breadcrumbs\"\n\t\t\t\t\t:key=\"breadcrumb.title\"\n\t\t\t\t\t:style=\"{ display: breadcrumbsVisibile ? 'flex' : 'none' }\">\n\t\t\t\t\t<router-link tabindex=\"0\" :to=\"breadcrumb.to\"> {{ breadcrumb.title }} </router-link>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</div>\n\t</footer>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue'\n\nimport { SHEET_NAV_TOOLBAR_ID, SHEET_NAV_TOOLBAR_SELECTOR } from '../sheet-nav-toolbar'\n\nconst { breadcrumbs = [] } = defineProps<{ breadcrumbs?: { title: string; to: string }[] }>()\n\nonMounted(() => {\n\tif (document.querySelectorAll(SHEET_NAV_TOOLBAR_SELECTOR).length > 1) {\n\t\tconsole.warn(\n\t\t\t`More than one SheetNav is mounted: content teleported to ${SHEET_NAV_TOOLBAR_SELECTOR} lands in the first one only.`\n\t\t)\n\t}\n})\n\nconst breadcrumbsVisibile = ref(true)\nconst searchVisible = ref(false)\nconst searchText = ref('')\nconst inputRef = useTemplateRef<HTMLInputElement>('searchinput')\n\nconst rotateHideTabIcon = computed(() => {\n\treturn breadcrumbsVisibile.value ? 'unrotated' : 'rotated'\n})\n\nconst toggleBreadcrumbs = () => {\n\tbreadcrumbsVisibile.value = !breadcrumbsVisibile.value\n}\n\nconst toggleSearch = async () => {\n\tsearchVisible.value = !searchVisible.value\n\tawait nextTick(() => {\n\t\tinputRef.value?.focus()\n\t})\n}\n\nconst handleSearchInput = (event: Event | MouseEvent) => {\n\tevent.preventDefault()\n\tevent.stopPropagation()\n}\n\nconst handleSearch = async (event: FocusEvent | KeyboardEvent) => {\n\tevent.preventDefault()\n\tevent.stopPropagation()\n\tawait toggleSearch()\n}\n\nconst navigateHome = (/* event: MouseEvent | KeyboardEvent */) => {\n\t// navigate home\n}\n</script>\n\n<style scoped>\n/* Pinned to both edges: without `left`, a fixed box takes its container's offset and overflows the\n viewport. Only the toolbar and the tabs take pointer events; the empty strip passes clicks through. */\nfooter {\n\tposition: fixed;\n\tright: 0;\n\tbottom: 0;\n\tleft: 0;\n\tpointer-events: none;\n\tbackground-color: transparent;\n\tz-index: 100;\n\ttext-align: left;\n\tfont-size: 100%;\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\talign-items: flex-end;\n\tpadding: 0 0.75rem 0 0;\n\tbox-sizing: border-box;\n}\n\n/* When the toolbar and the tabs do not fit on one row, the toolbar wraps onto its own row above. */\n.sheetnav-footer-cluster {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tjustify-content: flex-end;\n\talign-items: flex-end;\n\tcolumn-gap: 0.5rem;\n\tmax-width: 100%;\n\tmin-width: 0;\n}\n\n.sheetnav-toolbar {\n\tpointer-events: auto;\n\tmin-width: 0;\n\tmin-height: 2.4rem;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tjustify-content: flex-end;\n\talign-items: center;\n}\n\n.tabs {\n\tpointer-events: auto;\n\tflex: 0 0 auto;\n\tdisplay: flex;\n\tflex-direction: row-reverse;\n\talign-items: stretch;\n\theight: 2.4rem;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.tabs li {\n\tdisplay: flex;\n\talign-items: stretch;\n\tlist-style-type: none;\n\tposition: relative;\n\tmargin-left: -1px;\n}\n\n/* Base tab styling */\n.tabs a {\n\theight: 100%;\n\tmin-height: 2.4rem;\n\tpadding: 0 1rem;\n\tbox-sizing: border-box;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\ttext-decoration: none;\n\tcolor: var(--sc-gray-60);\n\tbackground: var(--sc-btn-color);\n\tborder: 1px solid var(--sc-btn-border);\n\tfont-size: 0.85rem;\n\tfont-family: var(--sc-font-family);\n\ttransition: all 0.15s ease;\n\n\t/* Minimal ornamentation - subtle top radius only */\n\tborder-top-left-radius: 2px;\n\tborder-top-right-radius: 2px;\n}\n\n.tabs a:hover {\n\tbackground: var(--sc-btn-hover);\n}\n\n.tabs .router-link-active {\n\tz-index: 3;\n\tbackground: var(--sc-primary-color) !important;\n\tborder-color: var(--sc-primary-color) !important;\n\tcolor: var(--sc-primary-text-color) !important;\n}\n\n/* Pseudo-elements removed - minimal ornamentation style */\n\n/* Hide breadcrumbs tab */\n.hidebreadcrumbs a {\n\tmin-width: 2.4rem;\n\twidth: 2.4rem;\n\tpadding: 0.5rem;\n}\n\n.hidebreadcrumbs a div {\n\tfont-size: 1.2rem;\n}\n\n.rotated {\n\ttransform: rotate(45deg);\n\t-webkit-transform: rotate(45deg);\n\t-moz-transform: rotate(45deg);\n\t-ms-transform: rotate(45deg);\n\t-o-transform: rotate(45deg);\n\ttransition: transform 250ms ease;\n}\n.unrotated {\n\ttransform: rotate(0deg);\n\t-webkit-transform: rotate(0deg);\n\t-moz-transform: rotate(0deg);\n\t-ms-transform: rotate(0deg);\n\t-o-transform: rotate(0deg);\n\ttransition: transform 250ms ease;\n}\n\nli:active,\nli:hover,\nli:focus,\nli > a:active,\nli > a:hover,\nli > a:focus {\n\tz-index: 3;\n}\n\na:active,\na:hover,\na:focus {\n\toutline: 2px solid var(--sc-input-active-border-color);\n\tz-index: 3;\n}\n\n/* Home tab */\n.hometab a {\n\tmin-width: 2.4rem;\n\twidth: 2.4rem;\n\tpadding: 0.5rem;\n}\n\n/* SVG icon styling */\n.icon {\n\twidth: 1rem;\n\theight: 1rem;\n\tstroke: currentColor;\n\tflex-shrink: 0;\n}\n\n/* Search tab with animation */\n.searchtab {\n\toverflow: hidden;\n}\n\n.searchtab a {\n\tmin-width: 2.4rem;\n\tpadding: 0.5rem;\n\toverflow: hidden;\n\t/* Animation for smooth expand/collapse */\n\tmax-width: 2.4rem;\n\ttransition:\n\t\tmax-width 0.35s ease-in-out,\n\t\tpadding 0.35s ease-in-out,\n\t\tbackground 0.2s ease;\n}\n\n.searchtab .search-icon {\n\tcursor: pointer;\n}\n\n.searchtab input {\n\toutline: none;\n\tborder: 1px solid var(--sc-input-border-color);\n\tborder-radius: 0.25rem;\n\tbackground-color: var(--sc-form-background);\n\tcolor: var(--sc-gray-80);\n\ttext-align: left;\n\tfont-size: 0.875rem;\n\tpadding: 0.25rem 0.5rem;\n\twidth: 180px;\n\theight: 1.5rem;\n\tflex-shrink: 0;\n\topacity: 0;\n\ttransition: opacity 0.2s ease-in-out;\n\ttransition-delay: 0s;\n}\n\n.searchtab input:focus {\n\tborder-color: var(--sc-input-active-border-color);\n\toutline: none;\n}\n\n.searchtab input::placeholder {\n\tcolor: var(--sc-input-label-color);\n}\n\n/* Search active state - expanded with animation */\n.searchtab.search-active a {\n\tmax-width: 200px;\n\tmin-width: auto;\n\twidth: auto;\n\tpadding: 0.5rem 0.75rem;\n}\n\n.searchtab.search-active input {\n\topacity: 1;\n\ttransition-delay: 0.15s;\n}\n</style>\n","<template>\n\t<footer>\n\t\t<div class=\"sheetnav-footer-cluster\">\n\t\t\t<div :id=\"SHEET_NAV_TOOLBAR_ID\" class=\"sheetnav-toolbar\">\n\t\t\t\t<slot name=\"toolbar\" />\n\t\t\t</div>\n\t\t\t<ul class=\"tabs\">\n\t\t\t\t<li class=\"hidebreadcrumbs\" @click=\"toggleBreadcrumbs\" @keydown.enter=\"toggleBreadcrumbs\">\n\t\t\t\t\t<a tabindex=\"0\"><div :class=\"rotateHideTabIcon\">×</div></a>\n\t\t\t\t</li>\n\t\t\t\t<li\n\t\t\t\t\tclass=\"hometab\"\n\t\t\t\t\t:style=\"{ display: breadcrumbsVisibile ? 'flex' : 'none' }\"\n\t\t\t\t\t@click=\"navigateHome\"\n\t\t\t\t\t@keydown.enter=\"navigateHome\">\n\t\t\t\t\t<router-link to=\"/\" tabindex=\"0\">\n\t\t\t\t\t\t<svg class=\"icon\" aria-label=\"Home\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n\t\t\t\t\t\t\t<path d=\"M3 12l9-9 9 9\" />\n\t\t\t\t\t\t\t<path d=\"M9 21V12h6v9\" />\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t</router-link>\n\t\t\t\t</li>\n\t\t\t\t<li\n\t\t\t\t\t:class=\"['searchtab', { 'search-active': searchVisible }]\"\n\t\t\t\t\t:style=\"{ display: breadcrumbsVisibile ? 'flex' : 'none' }\">\n\t\t\t\t\t<a tabindex=\"0\">\n\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\tv-show=\"!searchVisible\"\n\t\t\t\t\t\t\tclass=\"icon search-icon\"\n\t\t\t\t\t\t\trole=\"button\"\n\t\t\t\t\t\t\taria-label=\"Search\"\n\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\t\tstroke-width=\"2\"\n\t\t\t\t\t\t\t@click=\"toggleSearch\"\n\t\t\t\t\t\t\t@keydown.enter=\"toggleSearch\">\n\t\t\t\t\t\t\t<circle cx=\"11\" cy=\"11\" r=\"7\" />\n\t\t\t\t\t\t\t<path d=\"M21 21l-4.35-4.35\" />\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tv-show=\"searchVisible\"\n\t\t\t\t\t\t\tref=\"searchinput\"\n\t\t\t\t\t\t\tv-model=\"searchText\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Search...\"\n\t\t\t\t\t\t\t@click.stop\n\t\t\t\t\t\t\t@input=\"handleSearchInput($event)\"\n\t\t\t\t\t\t\t@blur=\"handleSearch($event)\"\n\t\t\t\t\t\t\t@keydown.enter=\"handleSearch($event)\"\n\t\t\t\t\t\t\t@keydown.escape=\"toggleSearch\" />\n\t\t\t\t\t</a>\n\t\t\t\t</li>\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"breadcrumb in breadcrumbs\"\n\t\t\t\t\t:key=\"breadcrumb.title\"\n\t\t\t\t\t:style=\"{ display: breadcrumbsVisibile ? 'flex' : 'none' }\">\n\t\t\t\t\t<router-link tabindex=\"0\" :to=\"breadcrumb.to\"> {{ breadcrumb.title }} </router-link>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</div>\n\t</footer>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue'\n\nimport { SHEET_NAV_TOOLBAR_ID, SHEET_NAV_TOOLBAR_SELECTOR } from '../sheet-nav-toolbar'\n\nconst { breadcrumbs = [] } = defineProps<{ breadcrumbs?: { title: string; to: string }[] }>()\n\nonMounted(() => {\n\tif (document.querySelectorAll(SHEET_NAV_TOOLBAR_SELECTOR).length > 1) {\n\t\tconsole.warn(\n\t\t\t`More than one SheetNav is mounted: content teleported to ${SHEET_NAV_TOOLBAR_SELECTOR} lands in the first one only.`\n\t\t)\n\t}\n})\n\nconst breadcrumbsVisibile = ref(true)\nconst searchVisible = ref(false)\nconst searchText = ref('')\nconst inputRef = useTemplateRef<HTMLInputElement>('searchinput')\n\nconst rotateHideTabIcon = computed(() => {\n\treturn breadcrumbsVisibile.value ? 'unrotated' : 'rotated'\n})\n\nconst toggleBreadcrumbs = () => {\n\tbreadcrumbsVisibile.value = !breadcrumbsVisibile.value\n}\n\nconst toggleSearch = async () => {\n\tsearchVisible.value = !searchVisible.value\n\tawait nextTick(() => {\n\t\tinputRef.value?.focus()\n\t})\n}\n\nconst handleSearchInput = (event: Event | MouseEvent) => {\n\tevent.preventDefault()\n\tevent.stopPropagation()\n}\n\nconst handleSearch = async (event: FocusEvent | KeyboardEvent) => {\n\tevent.preventDefault()\n\tevent.stopPropagation()\n\tawait toggleSearch()\n}\n\nconst navigateHome = (/* event: MouseEvent | KeyboardEvent */) => {\n\t// navigate home\n}\n</script>\n\n<style scoped>\n/* Pinned to both edges: without `left`, a fixed box takes its container's offset and overflows the\n viewport. Only the toolbar and the tabs take pointer events; the empty strip passes clicks through. */\nfooter {\n\tposition: fixed;\n\tright: 0;\n\tbottom: 0;\n\tleft: 0;\n\tpointer-events: none;\n\tbackground-color: transparent;\n\tz-index: 100;\n\ttext-align: left;\n\tfont-size: 100%;\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\talign-items: flex-end;\n\tpadding: 0 0.75rem 0 0;\n\tbox-sizing: border-box;\n}\n\n/* When the toolbar and the tabs do not fit on one row, the toolbar wraps onto its own row above. */\n.sheetnav-footer-cluster {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tjustify-content: flex-end;\n\talign-items: flex-end;\n\tcolumn-gap: 0.5rem;\n\tmax-width: 100%;\n\tmin-width: 0;\n}\n\n.sheetnav-toolbar {\n\tpointer-events: auto;\n\tmin-width: 0;\n\tmin-height: 2.4rem;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tjustify-content: flex-end;\n\talign-items: center;\n}\n\n.tabs {\n\tpointer-events: auto;\n\tflex: 0 0 auto;\n\tdisplay: flex;\n\tflex-direction: row-reverse;\n\talign-items: stretch;\n\theight: 2.4rem;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.tabs li {\n\tdisplay: flex;\n\talign-items: stretch;\n\tlist-style-type: none;\n\tposition: relative;\n\tmargin-left: -1px;\n}\n\n/* Base tab styling */\n.tabs a {\n\theight: 100%;\n\tmin-height: 2.4rem;\n\tpadding: 0 1rem;\n\tbox-sizing: border-box;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\ttext-decoration: none;\n\tcolor: var(--sc-gray-60);\n\tbackground: var(--sc-btn-color);\n\tborder: 1px solid var(--sc-btn-border);\n\tfont-size: 0.85rem;\n\tfont-family: var(--sc-font-family);\n\ttransition: all 0.15s ease;\n\n\t/* Minimal ornamentation - subtle top radius only */\n\tborder-top-left-radius: 2px;\n\tborder-top-right-radius: 2px;\n}\n\n.tabs a:hover {\n\tbackground: var(--sc-btn-hover);\n}\n\n.tabs .router-link-active {\n\tz-index: 3;\n\tbackground: var(--sc-primary-color) !important;\n\tborder-color: var(--sc-primary-color) !important;\n\tcolor: var(--sc-primary-text-color) !important;\n}\n\n/* Pseudo-elements removed - minimal ornamentation style */\n\n/* Hide breadcrumbs tab */\n.hidebreadcrumbs a {\n\tmin-width: 2.4rem;\n\twidth: 2.4rem;\n\tpadding: 0.5rem;\n}\n\n.hidebreadcrumbs a div {\n\tfont-size: 1.2rem;\n}\n\n.rotated {\n\ttransform: rotate(45deg);\n\t-webkit-transform: rotate(45deg);\n\t-moz-transform: rotate(45deg);\n\t-ms-transform: rotate(45deg);\n\t-o-transform: rotate(45deg);\n\ttransition: transform 250ms ease;\n}\n.unrotated {\n\ttransform: rotate(0deg);\n\t-webkit-transform: rotate(0deg);\n\t-moz-transform: rotate(0deg);\n\t-ms-transform: rotate(0deg);\n\t-o-transform: rotate(0deg);\n\ttransition: transform 250ms ease;\n}\n\nli:active,\nli:hover,\nli:focus,\nli > a:active,\nli > a:hover,\nli > a:focus {\n\tz-index: 3;\n}\n\na:active,\na:hover,\na:focus {\n\toutline: 2px solid var(--sc-input-active-border-color);\n\tz-index: 3;\n}\n\n/* Home tab */\n.hometab a {\n\tmin-width: 2.4rem;\n\twidth: 2.4rem;\n\tpadding: 0.5rem;\n}\n\n/* SVG icon styling */\n.icon {\n\twidth: 1rem;\n\theight: 1rem;\n\tstroke: currentColor;\n\tflex-shrink: 0;\n}\n\n/* Search tab with animation */\n.searchtab {\n\toverflow: hidden;\n}\n\n.searchtab a {\n\tmin-width: 2.4rem;\n\tpadding: 0.5rem;\n\toverflow: hidden;\n\t/* Animation for smooth expand/collapse */\n\tmax-width: 2.4rem;\n\ttransition:\n\t\tmax-width 0.35s ease-in-out,\n\t\tpadding 0.35s ease-in-out,\n\t\tbackground 0.2s ease;\n}\n\n.searchtab .search-icon {\n\tcursor: pointer;\n}\n\n.searchtab input {\n\toutline: none;\n\tborder: 1px solid var(--sc-input-border-color);\n\tborder-radius: 0.25rem;\n\tbackground-color: var(--sc-form-background);\n\tcolor: var(--sc-gray-80);\n\ttext-align: left;\n\tfont-size: 0.875rem;\n\tpadding: 0.25rem 0.5rem;\n\twidth: 180px;\n\theight: 1.5rem;\n\tflex-shrink: 0;\n\topacity: 0;\n\ttransition: opacity 0.2s ease-in-out;\n\ttransition-delay: 0s;\n}\n\n.searchtab input:focus {\n\tborder-color: var(--sc-input-active-border-color);\n\toutline: none;\n}\n\n.searchtab input::placeholder {\n\tcolor: var(--sc-input-label-color);\n}\n\n/* Search active state - expanded with animation */\n.searchtab.search-active a {\n\tmax-width: 200px;\n\tmin-width: auto;\n\twidth: auto;\n\tpadding: 0.5rem 0.75rem;\n}\n\n.searchtab.search-active input {\n\topacity: 1;\n\ttransition-delay: 0.15s;\n}\n</style>\n","<template>\n\t<div class=\"desktop\" @click=\"handleClick\">\n\t\t<!-- Action Set -->\n\t\t<ActionSet :elements=\"actionElements\" @action-click=\"handleActionClick\" />\n\n\t\t<!-- Main content using AForm -->\n\t\t<AForm\n\t\t\tv-if=\"currentViewSchema.length > 0\"\n\t\t\tv-model:data=\"currentViewData\"\n\t\t\t:schema=\"currentViewSchema\"\n\t\t\t:errors=\"fieldErrors\" />\n\t\t<div v-else-if=\"!stonecrop\" class=\"loading\"><p>Initializing Stonecrop...</p></div>\n\t\t<div v-else class=\"loading\">\n\t\t\t<p>Loading {{ currentView }} data...</p>\n\t\t</div>\n\n\t\t<!-- Sheet Navigation -->\n\t\t<SheetNav :breadcrumbs=\"navigationBreadcrumbs\">\n\t\t\t<template #toolbar>\n\t\t\t\t<slot name=\"sheetnav-toolbar\" />\n\t\t\t</template>\n\t\t</SheetNav>\n\n\t\t<!-- Command Palette -->\n\t\t<CommandPalette\n\t\t\t:is-open=\"commandPaletteOpen\"\n\t\t\t:search=\"searchCommands\"\n\t\t\tplaceholder=\"Type a command or search...\"\n\t\t\t@select=\"executeCommand\"\n\t\t\t@close=\"commandPaletteOpen = false\">\n\t\t\t<template #title=\"{ result }\">\n\t\t\t\t{{ result.title }}\n\t\t\t</template>\n\t\t\t<template #content=\"{ result }\">\n\t\t\t\t{{ result.description }}\n\t\t\t</template>\n\t\t</CommandPalette>\n\t</div>\n</template>\n\n<script setup lang=\"ts\">\n// The draft segment comes from @stonecrop/stonecrop rather than being spelled out here: that\n// package guards fetching, field initialization and workflow readiness on the same question, and\n// when the two were written separately they disagreed and every guard over there went dead.\nimport { DRAFT_RECORD_ID, isDraftRecordId, useStonecrop, useValidationStore } from '@stonecrop/stonecrop'\nimport {\n\tAForm,\n\ttype AFormLinkNavigator,\n\tresolvedFieldsToColumns,\n\ttype ResolvedField,\n\ttype ResolvedTable,\n} from '@stonecrop/aform'\nimport { computed, onMounted, onUnmounted, provide, ref, unref, watch } from 'vue'\n\nimport ActionSet from './ActionSet.vue'\nimport SheetNav from './SheetNav.vue'\nimport CommandPalette from './CommandPalette.vue'\nimport type {\n\tActionElements,\n\tRouteAdapter,\n\tNavigationTarget,\n\tActionEventPayload,\n\tRecordOpenEventPayload,\n\tLoadRecordsEventPayload,\n\tLoadRecordEventPayload,\n} from '../types'\n\nconst { availableDoctypes = [], routeAdapter } = defineProps<{\n\tavailableDoctypes?: string[]\n\t/**\n\t * Pluggable router adapter. When provided, Desktop uses these functions for all\n\t * routing instead of reaching into the registry's internal Vue Router instance.\n\t * Nuxt hosts (or any host with custom route conventions) should supply this.\n\t */\n\trouteAdapter?: RouteAdapter\n}>()\n\nconst emit = defineEmits<{\n\t/**\n\t * Fired when the user triggers an FSM transition (action button click).\n\t * The host app is responsible for calling the server, persisting state, etc.\n\t */\n\taction: [payload: ActionEventPayload]\n\t/**\n\t * Fired when Desktop wants to navigate to a different view.\n\t * Also calls routeAdapter.navigate() if an adapter is provided.\n\t */\n\tnavigate: [target: NavigationTarget]\n\t/**\n\t * Fired when the user opens a specific record.\n\t */\n\t'record:open': [payload: RecordOpenEventPayload]\n\t/**\n\t * Fired when Desktop is about to read records for a list view. A notification, not a request:\n\t * Desktop performs the read itself through `Stonecrop.getRecords`. A host that fetches here\n\t * races that read into the same HST key.\n\t */\n\t'load-records': [payload: LoadRecordsEventPayload]\n\t/**\n\t * Fired when Desktop is about to read a single record for a form view. A notification, not a\n\t * request — see `load-records`. Not emitted for a draft, which has nothing to fetch.\n\t */\n\t'load-record': [payload: LoadRecordEventPayload]\n}>()\n\nconst { stonecrop } = useStonecrop()\n\n// Field-validation store (advisory, client-side). Pinia is a declared peerDependency, kept external\n// in this package's build (rollupOptions), so `useValidationStore()` resolves the host app's single\n// active Pinia directly — the old `getCurrentInstance().$pinia` workaround for the bundled-Pinia bug\n// is no longer needed. Still guarded: validation is optional and Desktop predates it, so a host that\n// mounts Desktop without Pinia disables validation gracefully instead of crashing on mount.\nlet validationStore: ReturnType<typeof useValidationStore> | null = null\ntry {\n\tvalidationStore = useValidationStore()\n} catch {\n\tvalidationStore = null\n}\n\n// Inline field errors handed to AForm. Only surfaced in the record form view (currentView is\n// defined below); empty elsewhere so a previous record's errors never bleed into a list view.\nconst fieldErrors = computed<Record<string, string[]>>(() =>\n\tcurrentView.value === 'record' ? (validationStore?.errorsByField ?? {}) : {}\n)\n\n// State\nconst loading = ref(false)\nconst commandPaletteOpen = ref(false)\n\n// The record being composed on a `/{doctype}/new` route. Deliberately not in HST: a draft has no\n// identity to be keyed by, and both ways of faking one fail — see `DRAFT_RECORD_ID`.\nconst draftRecord = ref<Record<string, any>>({})\n\n// Form/list data management — each view produces a different data shape.\n// List views (doctypes, records) return table row data keyed by fieldname.\n// Record view returns the record's fields for two-way binding — from HST, or from `draftRecord`\n// when the route is a draft.\nconst currentViewData = computed<Record<string, any>>({\n\tget() {\n\t\t// Doctypes list — rows come from availableDoctypes prop (reactive via availableDoctypes)\n\t\tif (currentView.value === 'doctypes') {\n\t\t\treturn {\n\t\t\t\tdoctypes_table:\n\t\t\t\t\tavailableDoctypes?.map(doctype => ({\n\t\t\t\t\t\tid: doctype,\n\t\t\t\t\t\tdoctype,\n\t\t\t\t\t\tdisplay_name: formatDoctypeName(doctype),\n\t\t\t\t\t\tactions: 'View Records',\n\t\t\t\t\t})) ?? [],\n\t\t\t}\n\t\t}\n\n\t\t// Records list — rows come from HST store (reactive because HST is Vue reactive())\n\t\tif (currentView.value === 'records') {\n\t\t\treturn {\n\t\t\t\trecords_table: getRecords().map(record =>\n\t\t\t\t\tObject.assign({}, record, {\n\t\t\t\t\t\tid: resolveRecordId(record) ?? '',\n\t\t\t\t\t\t// A list row is navigation. Actions live on the record view, where the Actions\n\t\t\t\t\t\t// dropdown is built from what the doctype declares and what the record's\n\t\t\t\t\t\t// current state allows. This cell used to also offer Delete, which dispatched\n\t\t\t\t\t\t// an action named `DELETE` that Desktop invented — no doctype in a\n\t\t\t\t\t\t// WorkflowMeta app declares it, so it failed on every click. Removal is a\n\t\t\t\t\t\t// workflow outcome (`archive`, `cancel`) and belongs in the doctype.\n\t\t\t\t\t\tactions: 'Edit',\n\t\t\t\t\t})\n\t\t\t\t),\n\t\t\t}\n\t\t}\n\n\t\t// Record form — read single record from HST\n\t\tif (!stonecrop.value || !currentDoctype.value || !currentRecordId.value) {\n\t\t\treturn {}\n\t\t}\n\n\t\ttry {\n\t\t\tconst source = isNewRecord.value\n\t\t\t\t? draftRecord.value\n\t\t\t\t: (stonecrop.value.getRecordById(currentDoctype.value, currentRecordId.value)?.get('') as\n\t\t\t\t\t\t| Record<string, any>\n\t\t\t\t\t\t| undefined)\n\t\t\t// Return a plain shallow copy so AForm mutations don't propagate directly into\n\t\t\t// the HST reactive object, which would bypass field-trigger diffing and cause\n\t\t\t// setupDeepReactivity to fire triggers for all fields on every keystroke.\n\t\t\tconst flat: Record<string, any> = { ...source }\n\n\t\t\t// AFieldset receives data[fieldsetFieldname] as its data prop, so the fieldset's\n\t\t\t// children must be grouped under the fieldset key. The server returns flat SQL rows,\n\t\t\t// so we nest fieldset children here before AForm renders them.\n\t\t\tconst doctype = stonecrop.value.registry.registry[currentDoctype.value]\n\t\t\tif (doctype) {\n\t\t\t\tfor (const field of doctype.getSchemaArray()) {\n\t\t\t\t\tif (field.kind === 'fieldset') {\n\t\t\t\t\t\tconst nested: Record<string, any> = {}\n\t\t\t\t\t\tfor (const child of field.schema) {\n\t\t\t\t\t\t\tif (child.fieldname) nested[child.fieldname] = flat[child.fieldname]\n\t\t\t\t\t\t}\n\t\t\t\t\t\tflat[field.fieldname] = nested\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn flat\n\t\t} catch {\n\t\t\treturn {}\n\t\t}\n\t},\n\tset(newData: Record<string, any>) {\n\t\t// List views are read-only from AForm's perspective — HST writes only apply to record form\n\t\tif (currentView.value !== 'record') return\n\t\tif (!stonecrop.value || !currentDoctype.value || !currentRecordId.value) {\n\t\t\treturn\n\t\t}\n\n\t\ttry {\n\t\t\t// AForm emits nested data for fieldsets: { fieldsetKey: { childA: val } }.\n\t\t\t// HST and the server both expect flat rows, so flatten fieldset values back before writing.\n\t\t\tconst doctype = stonecrop.value.registry.registry[currentDoctype.value]\n\t\t\tconst fieldsetNames = new Set<string>()\n\t\t\tif (doctype) {\n\t\t\t\tfor (const field of doctype.getSchemaArray()) {\n\t\t\t\t\tif (field.kind === 'fieldset') {\n\t\t\t\t\t\tfieldsetNames.add(field.fieldname)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Two-pass flatten: non-fieldset keys first, then fieldset children.\n\t\t\t// Fieldset children must be applied last — AForm may emit stale flat copies\n\t\t\t// of fieldset children alongside the updated nested value, and the nested\n\t\t\t// value must win regardless of key insertion order.\n\t\t\tconst flatData: Record<string, any> = {}\n\t\t\tconst fieldsetValues: Record<string, any>[] = []\n\t\t\tfor (const [key, value] of Object.entries(newData)) {\n\t\t\t\tif (fieldsetNames.has(key) && value && typeof value === 'object' && !Array.isArray(value)) {\n\t\t\t\t\tfieldsetValues.push(value)\n\t\t\t\t} else {\n\t\t\t\t\tflatData[key] = value\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const nestedValue of fieldsetValues) {\n\t\t\t\tObject.assign(flatData, nestedValue)\n\t\t\t}\n\n\t\t\t// Only update fields that actually changed. Never write undefined — AForm may emit\n\t\t\t// schema fields absent from the record as undefined; writing them would silently\n\t\t\t// clear values that exist. Explicit null is allowed (intentional clear).\n\t\t\tconst changedFields: string[] = []\n\t\t\tif (isNewRecord.value) {\n\t\t\t\t// Reassigned, not mutated, so the getter re-runs. Relying on in-place mutation of the\n\t\t\t\t// cached object is what made a draft's edits vanish on any invalidation.\n\t\t\t\tconst next = { ...draftRecord.value }\n\t\t\t\tfor (const [fieldname, value] of Object.entries(flatData)) {\n\t\t\t\t\tif (value === undefined) continue\n\t\t\t\t\tif (next[fieldname] !== value) {\n\t\t\t\t\t\tnext[fieldname] = value\n\t\t\t\t\t\tchangedFields.push(fieldname)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdraftRecord.value = next\n\t\t\t} else {\n\t\t\t\tconst hstStore = stonecrop.value.getStore()\n\t\t\t\tfor (const [fieldname, value] of Object.entries(flatData)) {\n\t\t\t\t\tif (value === undefined) continue\n\t\t\t\t\tconst fieldPath = `${currentDoctype.value}.${currentRecordId.value}.${fieldname}`\n\t\t\t\t\tconst currentValue = hstStore.has(fieldPath) ? hstStore.get(fieldPath) : undefined\n\t\t\t\t\tif (currentValue !== value) {\n\t\t\t\t\t\thstStore.set(fieldPath, value)\n\t\t\t\t\t\tchangedFields.push(fieldname)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Advisory field-validation runs on the fields that actually changed (honors each\n\t\t\t// trigger's `on` set). Driven here, after the HST writes, so HST stays value-only.\n\t\t\tif (changedFields.length > 0) {\n\t\t\t\tdriveFieldValidation(changedFields)\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.warn('HST update failed:', error)\n\t\t}\n\t},\n})\n\n// Advisory field-validation: run the doctype's triggers for the fields that changed on this edit.\n// Snapshots the post-edit record as a plain, flat object so validators read siblings read-only\n// (the core store freezes it). Fire-and-forget — the reactive error store repaints AForm on resolve.\nfunction driveFieldValidation(changedFields: string[]) {\n\tif (!validationStore || !stonecrop.value || !currentDoctype.value || !currentRecordId.value) return\n\n\tconst doctype = stonecrop.value.registry.getDoctype(currentDoctype.value)\n\tconst triggers = doctype?.getTriggers()\n\tif (!triggers || Object.keys(triggers).length === 0) return\n\n\t// A draft's siblings come from the buffer; reading HST would hand every validator an empty record.\n\tconst record = isNewRecord.value\n\t\t? { ...draftRecord.value }\n\t\t: {\n\t\t\t\t...(stonecrop.value.getRecordById(currentDoctype.value, currentRecordId.value)?.get('') as Record<\n\t\t\t\t\tstring,\n\t\t\t\t\tunknown\n\t\t\t\t>),\n\t\t\t}\n\n\tfor (const field of changedFields) {\n\t\tvoid validationStore.validateField(triggers, field, record)\n\t}\n}\n\n// Computed properties for current route context.\n// When a routeAdapter is provided it takes full precedence over the registry's internal router.\nconst route = computed(() => (routeAdapter ? null : unref(stonecrop.value?.registry.router?.currentRoute)))\nconst router = computed(() => (routeAdapter ? null : stonecrop.value?.registry.router))\nconst currentDoctype = computed(() => {\n\tif (routeAdapter) return routeAdapter.getCurrentDoctype()\n\tif (!route.value) return ''\n\n\t// First check if we have actualDoctype in meta (from registered routes)\n\tif (route.value.meta?.actualDoctype) {\n\t\treturn route.value.meta.actualDoctype as string\n\t}\n\n\t// For named routes, use params.doctype\n\tif (route.value.params.doctype) {\n\t\treturn route.value.params.doctype.toString()\n\t}\n\n\t// For catch-all routes that haven't been registered yet, extract from path\n\tconst pathMatch = route.value.params.pathMatch as string[] | undefined\n\tif (pathMatch && pathMatch.length > 0) {\n\t\treturn pathMatch[0]\n\t}\n\n\treturn ''\n})\n\n// The route doctype for display and navigation (e.g., 'todo')\nconst routeDoctype = computed(() => {\n\tif (routeAdapter) return routeAdapter.getCurrentDoctype()\n\tif (!route.value) return ''\n\n\t// Check route meta first\n\tif (route.value.meta?.doctype) {\n\t\treturn route.value.meta.doctype as string\n\t}\n\n\t// For named routes, use params.doctype\n\tif (route.value.params.doctype) {\n\t\treturn route.value.params.doctype.toString()\n\t}\n\n\t// For catch-all routes, extract from path\n\tconst pathMatch = route.value.params.pathMatch as string[] | undefined\n\tif (pathMatch && pathMatch.length > 0) {\n\t\treturn pathMatch[0]\n\t}\n\n\treturn ''\n})\n\nconst currentRecordId = computed(() => {\n\tif (routeAdapter) return routeAdapter.getCurrentRecordId()\n\tif (!route.value) return ''\n\n\t// For named routes, use params.recordId\n\tif (route.value.params.recordId) {\n\t\treturn route.value.params.recordId.toString()\n\t}\n\n\t// For catch-all routes that haven't been registered yet, extract from path\n\tconst pathMatch = route.value.params.pathMatch as string[] | undefined\n\tif (pathMatch && pathMatch.length > 1) {\n\t\treturn pathMatch[1]\n\t}\n\n\treturn ''\n})\nconst isNewRecord = computed(() => isDraftRecordId(currentRecordId.value))\n\n// Determine current view based on route\nconst currentView = computed(() => {\n\tif (routeAdapter) return routeAdapter.getCurrentView()\n\tif (!route.value) {\n\t\treturn 'doctypes'\n\t}\n\n\t// Home route\n\tif (route.value.name === 'home' || route.value.path === '/') {\n\t\treturn 'doctypes'\n\t}\n\n\t// Named routes from registered doctypes\n\tif (route.value.name && route.value.name !== 'catch-all') {\n\t\tconst routeName = route.value.name as string\n\t\tif (routeName.includes('form') || route.value.params.recordId) {\n\t\t\treturn 'record'\n\t\t} else if (routeName.includes('list') || route.value.params.doctype) {\n\t\t\treturn 'records'\n\t\t}\n\t}\n\n\t// Catch-all route - determine from path structure\n\tconst pathMatch = route.value.params.pathMatch as string[] | undefined\n\tif (pathMatch && pathMatch.length > 0) {\n\t\tconst view = pathMatch.length === 1 ? 'records' : 'record'\n\t\treturn view\n\t}\n\n\treturn 'doctypes'\n})\n\n// Computed properties (now that all helper functions are defined)\n// Helper function to get available transitions for current record.\n// Reads the actual FSM state from the record's `status` field (or falls back to the\n// workflow initial state) so the available action buttons always reflect reality.\nconst getAvailableTransitions = () => {\n\tif (!stonecrop.value || !currentDoctype.value || !currentRecordId.value) {\n\t\treturn []\n\t}\n\n\ttry {\n\t\tconst doctype = stonecrop.value.registry.getDoctype(currentDoctype.value)\n\t\tif (!doctype?.workflow) return []\n\n\t\t// Delegate state resolution to Stonecrop — reads record 'status', falls back to workflow.initial\n\t\tconst currentState = stonecrop.value.getRecordState(currentDoctype.value, currentRecordId.value)\n\n\t\t// Delegate transition lookup to Doctype — no more manual workflow introspection\n\t\tconst transitions = doctype.getAvailableTransitions(currentState)\n\n\t\tconst recordData = currentViewData.value || {}\n\n\t\t// Each transition emits an 'action' event. The host app decides what to do\n\t\t// (call the server, trigger an FSM actor, update HST, etc.).\n\t\treturn transitions.map(({ name }) => ({\n\t\t\t// Prefer the workflow action's human-readable label (WorkflowMeta format);\n\t\t\t// fall back to the raw transition name for XState workflows with no action meta.\n\t\t\tlabel: doctype.getActionMeta(name)?.label ?? name,\n\t\t\taction: () => {\n\t\t\t\temit('action', {\n\t\t\t\t\tname,\n\t\t\t\t\tdoctype: currentDoctype.value,\n\t\t\t\t\trecordId: currentRecordId.value,\n\t\t\t\t\tdata: recordData,\n\t\t\t\t})\n\t\t\t},\n\t\t}))\n\t} catch (error) {\n\t\tconsole.warn('Error getting available transitions:', error)\n\t\treturn []\n\t}\n}\n\n// Helper: stateless Commands available for the current record — side-effect actions that\n// change no workflow state. Surfaced in the same Actions dropdown as transitions; each emits\n// the same 'action' event, so the host's handler runs a Command's clientHandler identically.\nconst getAvailableCommands = () => {\n\tif (!stonecrop.value || !currentDoctype.value || !currentRecordId.value) {\n\t\treturn []\n\t}\n\n\ttry {\n\t\tconst doctype = stonecrop.value.registry.getDoctype(currentDoctype.value)\n\t\tif (!doctype?.workflow) return []\n\n\t\tconst currentState = stonecrop.value.getRecordState(currentDoctype.value, currentRecordId.value)\n\t\tconst commands = doctype.getAvailableCommands(currentState)\n\t\tconst recordData = currentViewData.value || {}\n\n\t\treturn commands.map(({ name }) => ({\n\t\t\tlabel: doctype.getActionMeta(name)?.label ?? name,\n\t\t\taction: () => {\n\t\t\t\temit('action', {\n\t\t\t\t\tname,\n\t\t\t\t\tdoctype: currentDoctype.value,\n\t\t\t\t\trecordId: currentRecordId.value,\n\t\t\t\t\tdata: recordData,\n\t\t\t\t})\n\t\t\t},\n\t\t}))\n\t} catch (error) {\n\t\tconsole.warn('Error getting available commands:', error)\n\t\treturn []\n\t}\n}\n\nconst actionElements = computed(() => {\n\tconst elements: ActionElements[] = []\n\n\tswitch (currentView.value) {\n\t\tcase 'records':\n\t\t\telements.push({\n\t\t\t\ttype: 'button',\n\t\t\t\tlabel: 'New Record',\n\t\t\t\taction: () => void createNewRecord(),\n\t\t\t})\n\t\t\tbreak\n\t\tcase 'record': {\n\t\t\t// Populate the Actions dropdown with every FSM transition AND stateless Command\n\t\t\t// available in the record's current state. Clicking either emits 'action'.\n\t\t\tconst recordActions = [...getAvailableTransitions(), ...getAvailableCommands()]\n\t\t\tif (recordActions.length > 0) {\n\t\t\t\telements.push({\n\t\t\t\t\ttype: 'dropdown',\n\t\t\t\t\tlabel: 'Actions',\n\t\t\t\t\tactions: recordActions,\n\t\t\t\t})\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn elements\n})\n\nconst navigationBreadcrumbs = computed(() => {\n\tconst breadcrumbs: { title: string; to: string }[] = []\n\n\tif (currentView.value === 'records' && routeDoctype.value) {\n\t\tbreadcrumbs.push(\n\t\t\t{ title: 'Home', to: '/' },\n\t\t\t{ title: formatDoctypeName(routeDoctype.value), to: `/${routeDoctype.value}` }\n\t\t)\n\t} else if (currentView.value === 'record' && routeDoctype.value) {\n\t\tconst recordPath = currentRecordId.value\n\t\t\t? `/${routeDoctype.value}/${currentRecordId.value}`\n\t\t\t: (route.value?.fullPath ?? '')\n\t\tbreadcrumbs.push(\n\t\t\t{ title: 'Home', to: '/' },\n\t\t\t{ title: formatDoctypeName(routeDoctype.value), to: `/${routeDoctype.value}` },\n\t\t\t{ title: isNewRecord.value ? 'New Record' : 'Edit Record', to: recordPath }\n\t\t)\n\t}\n\n\treturn breadcrumbs\n})\n\n// Command palette functionality\ntype Command = {\n\ttitle: string\n\tdescription: string\n\taction: () => void\n}\n\nconst searchCommands = (query: string): Command[] => {\n\tconst commands: Command[] = [\n\t\t{\n\t\t\ttitle: 'Go Home',\n\t\t\tdescription: 'Navigate to the home page',\n\t\t\taction: () => void doNavigate({ view: 'doctypes' }),\n\t\t},\n\t\t{\n\t\t\ttitle: 'Toggle Command Palette',\n\t\t\tdescription: 'Open/close the command palette',\n\t\t\taction: () => (commandPaletteOpen.value = !commandPaletteOpen.value),\n\t\t},\n\t]\n\n\t// Add doctype-specific commands\n\tif (routeDoctype.value) {\n\t\tcommands.push({\n\t\t\ttitle: `View ${formatDoctypeName(routeDoctype.value)} Records`,\n\t\t\tdescription: `Navigate to ${routeDoctype.value} list`,\n\t\t\taction: () => void doNavigate({ view: 'records', doctype: routeDoctype.value }),\n\t\t})\n\n\t\tcommands.push({\n\t\t\ttitle: `Create New ${formatDoctypeName(routeDoctype.value)}`,\n\t\t\tdescription: `Create a new ${routeDoctype.value} record`,\n\t\t\taction: () => void createNewRecord(),\n\t\t})\n\t}\n\n\t// Add available doctypes as commands\n\tavailableDoctypes.forEach(doctype => {\n\t\tcommands.push({\n\t\t\ttitle: `View ${formatDoctypeName(doctype)}`,\n\t\t\tdescription: `Navigate to ${doctype} list`,\n\t\t\taction: () => void doNavigate({ view: 'records', doctype }),\n\t\t})\n\t})\n\n\t// Filter commands based on query\n\tif (!query) return commands\n\n\treturn commands.filter(\n\t\tcmd =>\n\t\t\tcmd.title.toLowerCase().includes(query.toLowerCase()) ||\n\t\t\tcmd.description.toLowerCase().includes(query.toLowerCase())\n\t)\n}\n\nconst executeCommand = (command: Command) => {\n\tcommand.action()\n\tcommandPaletteOpen.value = false\n}\n\n// List reads are wired on the records table; ATable owns the fetch via getRecords.\nconst listRecordsFetcher = (options?: import('@stonecrop/schema').GetRecordsOptions) => {\n\tif (!stonecrop.value || !currentDoctype.value) {\n\t\treturn Promise.resolve({ data: [], hasMore: false })\n\t}\n\tconst doctype = stonecrop.value.registry.getDoctype(currentDoctype.value)\n\tif (!doctype) {\n\t\treturn Promise.resolve({ data: [], hasMore: false })\n\t}\n\treturn stonecrop.value.getRecords(doctype, options)\n}\n\n// Helper functions - moved here to avoid \"before initialization\" errors\nconst formatDoctypeName = (doctype: string): string => {\n\treturn doctype\n\t\t.split('-')\n\t\t.map(word => word.charAt(0).toUpperCase() + word.slice(1))\n\t\t.join(' ')\n}\n\n// Internal navigation helper: emits 'navigate', then calls the adapter (if any)\n// or falls back to the registry's Vue Router instance.\nconst doNavigate = async (target: NavigationTarget) => {\n\temit('navigate', target)\n\tif (routeAdapter) {\n\t\tawait routeAdapter.navigate(target)\n\t} else {\n\t\tif (target.view === 'doctypes') {\n\t\t\tawait router.value?.push('/')\n\t\t} else if (target.view === 'records' && target.doctype) {\n\t\t\tawait router.value?.push(`/${target.doctype}`)\n\t\t} else if (target.view === 'record' && target.doctype && target.recordId) {\n\t\t\tawait router.value?.push(`/${target.doctype}/${target.recordId}`)\n\t\t}\n\t}\n}\n\nconst navigateToDoctype = async (doctype: string) => {\n\tawait doNavigate({ view: 'records', doctype })\n}\n\nconst openRecord = async (recordId: string) => {\n\tconst doctype = routeDoctype.value\n\temit('record:open', { doctype, recordId })\n\tawait doNavigate({ view: 'record', doctype, recordId })\n}\n\nconst createNewRecord = async () => {\n\tawait doNavigate({ view: 'record', doctype: routeDoctype.value, recordId: DRAFT_RECORD_ID })\n}\n\n// Schema generator functions - moved here to be available to computed properties\nconst getDoctypesSchema = (): ResolvedField[] => {\n\tif (!availableDoctypes?.length) return []\n\n\treturn [\n\t\t{\n\t\t\tkind: 'table' as const,\n\t\t\tfieldname: 'doctypes_table',\n\t\t\tcomponent: 'ATable',\n\t\t\tlabel: 'Doctypes',\n\t\t\tschema: [\n\t\t\t\t{\n\t\t\t\t\tfieldname: 'doctype',\n\t\t\t\t\tlabel: 'Doctype',\n\t\t\t\t\tcomponent: 'ATextInput',\n\t\t\t\t\talign: 'left' as const,\n\t\t\t\t\tedit: false,\n\t\t\t\t\twidth: '20ch',\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tfieldname: 'display_name',\n\t\t\t\t\tlabel: 'Name',\n\t\t\t\t\tcomponent: 'ATextInput',\n\t\t\t\t\talign: 'left' as const,\n\t\t\t\t\tedit: false,\n\t\t\t\t\twidth: '30ch',\n\t\t\t\t},\n\t\t\t\t// No record count column. It read `getRecordIds(doctype).length`, which is how many\n\t\t\t\t// records HST happens to hold — zero for a doctype never opened, and the page size\n\t\t\t\t// for one that was. The real total belongs to the backend and Desktop never asks\n\t\t\t\t// for it, so this shell cannot answer it. Same reason the `recordIdField` prop went.\n\t\t\t\t{\n\t\t\t\t\tfieldname: 'actions',\n\t\t\t\t\tlabel: 'Actions',\n\t\t\t\t\tcomponent: 'ATextInput',\n\t\t\t\t\talign: 'center' as const,\n\t\t\t\t\tedit: false,\n\t\t\t\t\twidth: '20ch',\n\t\t\t\t},\n\t\t\t],\n\t\t\tconfig: { view: 'list' as const, fullWidth: true },\n\t\t} satisfies ResolvedTable,\n\t]\n}\n\nconst getRecordsSchema = (): ResolvedField[] => {\n\tif (!currentDoctype.value) return []\n\tif (!stonecrop.value) return []\n\n\tconst registry = stonecrop.value.registry\n\tconst doctype = registry.registry[currentDoctype.value]\n\n\tif (!doctype) return []\n\n\tconst schema = registry.resolveSchema(doctype)\n\n\t// If no schema is available, let the template fallback handle the loading state\n\tif (schema.length === 0) return []\n\n\tconst doctypeSlug = currentDoctype.value\n\tconst clientConfigured = Boolean(stonecrop.value.getClient())\n\n\treturn [\n\t\t{\n\t\t\tkind: 'table' as const,\n\t\t\tfieldname: 'records_table',\n\t\t\tcomponent: 'ATable',\n\t\t\t// Which resolved fields a cell can render is answered once, in @stonecrop/aform, and\n\t\t\t// called by the child-table builder too. Flattening alone is not the rule: it kept the\n\t\t\t// expanding links, whose value is a nested record or an array of them.\n\t\t\tschema: [...resolvedFieldsToColumns(schema), { fieldname: 'actions', label: 'Actions', component: 'ATextInput' }],\n\t\t\tconfig: { view: 'list' as const, fullWidth: true },\n\t\t\t...(clientConfigured ? { getRecords: listRecordsFetcher, sourceKey: doctypeSlug } : {}),\n\t\t} satisfies ResolvedTable,\n\t]\n}\n\nconst getRecordFormSchema = (): ResolvedField[] => {\n\tif (!currentDoctype.value) return []\n\tif (!stonecrop.value) return []\n\n\ttry {\n\t\tconst registry = stonecrop.value?.registry\n\t\tconst doctype = registry?.registry[currentDoctype.value]\n\n\t\tif (!doctype?.schema) {\n\t\t\t// Let the template fallback handle the loading state\n\t\t\treturn []\n\t\t}\n\n\t\treturn registry.resolveSchema(doctype)\n\t} catch {\n\t\treturn []\n\t}\n}\n\n// Additional data helper functions\nconst getRecords = () => {\n\tif (!stonecrop.value || !currentDoctype.value) {\n\t\treturn []\n\t}\n\n\tconst recordsNode = stonecrop.value.records(currentDoctype.value)\n\tconst recordsData = recordsNode?.get('')\n\n\tif (recordsData && typeof recordsData === 'object' && !Array.isArray(recordsData)) {\n\t\treturn Object.values(recordsData as Record<string, any>)\n\t}\n\n\treturn []\n}\n\n// Schema for different views - defined here after all helper functions are available\nconst currentViewSchema = computed(() => {\n\tswitch (currentView.value) {\n\t\tcase 'doctypes':\n\t\t\treturn getDoctypesSchema()\n\t\tcase 'records':\n\t\t\treturn getRecordsSchema()\n\t\tcase 'record':\n\t\t\treturn getRecordFormSchema()\n\t\tdefault:\n\t\t\treturn []\n\t}\n})\n\nconst handleActionClick = (_label: string, action: (() => void | Promise<void>) | undefined) => {\n\tif (action) {\n\t\tvoid action()\n\t}\n}\n\n/**\n * Resolve a record's identity for links and navigation.\n *\n * Identity is declared once, on the doctype: `primaryKey`, or `id` when nothing is declared.\n * Delegating to `Doctype.getRecordId` is what guarantees this matches the key\n * `Stonecrop.getRecords` stored the record under — resolving it independently here would let a\n * row render a link to an HST path that does not exist.\n *\n * There is deliberately no per-shell override. Identity is a per-doctype fact and one shell\n * renders many doctypes, so a single prop cannot answer it; a shell that named a field would\n * also be overriding the very declaration the store keyed on, which is the bug above.\n */\nconst resolveRecordId = (record: Record<string, unknown>): string | undefined => {\n\tif (!stonecrop.value || !currentDoctype.value) return undefined\n\treturn stonecrop.value.registry.registry[currentDoctype.value]?.getRecordId(record)\n}\n\n// Event handlers\nconst getRecordIdFromRow = (rowElement: HTMLTableRowElement): string | null => {\n\tconst cell = rowElement.querySelector('td[data-rowindex]')\n\tif (!cell) return null\n\n\tconst rowIndexAttr = cell.getAttribute('data-rowindex')\n\tif (rowIndexAttr === null) return null\n\n\tconst rowIndex = parseInt(rowIndexAttr, 10)\n\tif (isNaN(rowIndex)) return null\n\n\tconst records = getRecords()\n\tconst record = records[rowIndex]\n\tif (!record) return null\n\n\treturn resolveRecordId(record) ?? null\n}\n\nconst handleClick = async (event: Event) => {\n\tconst target = event.target as HTMLElement\n\tconst action = target.getAttribute('data-action')\n\n\tif (action === 'create') {\n\t\tawait createNewRecord()\n\t}\n\n\tconst cell = target.closest('td, th')\n\tif (cell) {\n\t\tconst cellText = cell.textContent?.trim()\n\t\tconst row = cell.closest('tr')\n\n\t\tif (cellText === 'View Records' && row) {\n\t\t\t// Get the doctype from the row data\n\t\t\tconst cells = row.querySelectorAll('td')\n\t\t\tif (cells.length > 0) {\n\t\t\t\tconst doctypeCell = cells[1] // Assuming doctype is in second column (first column is index)\n\t\t\t\tconst doctype = doctypeCell.textContent?.trim()\n\t\t\t\tif (doctype) {\n\t\t\t\t\tawait navigateToDoctype(doctype)\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (cellText === 'Edit' && row) {\n\t\t\t// Matched exactly, not by substring. This handler is bound to the whole desktop, so a\n\t\t\t// substring match fired on any cell whose *data* happened to contain the word — a task\n\t\t\t// titled \"Delete old backups\" popped a delete confirmation when you clicked it.\n\t\t\tconst recordId = getRecordIdFromRow(row)\n\t\t\tif (recordId) {\n\t\t\t\tawait openRecord(recordId)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Reads go through Stonecrop, which owns whether to fetch, how to key the result, and where to\n// put it. Desktop asks for data and renders what arrives; it decides none of that itself.\n//\n// Both loaders are no-ops without a client, so a host that populates HST some other way keeps\n// working unchanged rather than taking a thrown error on every navigation.\nconst loadRecordData = async () => {\n\tif (!stonecrop.value || !currentDoctype.value || !stonecrop.value.getClient()) return\n\n\tloading.value = true\n\ttry {\n\t\tawait stonecrop.value.getRecord(currentDoctype.value, currentRecordId.value)\n\t} catch (error) {\n\t\tconsole.warn('Error fetching record:', error)\n\t} finally {\n\t\tloading.value = false\n\t}\n}\n\n// Watch for route changes to load appropriate data\nwatch(\n\t[currentView, currentDoctype, currentRecordId],\n\t() => {\n\t\t// The events are notifications, not fetch requests: they announce what Desktop is about to\n\t\t// read so a host can hang analytics or a prefetch off them. The read itself is Stonecrop's.\n\t\tif (currentView.value === 'records' && currentDoctype.value) {\n\t\t\temit('load-records', { doctype: currentDoctype.value })\n\t\t} else if (currentView.value === 'record' && currentDoctype.value && currentRecordId.value) {\n\t\t\t// A draft has nothing to fetch — the record does not exist on the server yet. Desktop\n\t\t\t// used to emit anyway and leave the host to work it out, which meant every host had to\n\t\t\t// recognise the private draft-id scheme above just to suppress a doomed request; all of\n\t\t\t// them did, identically. `getRecord` declines the same case for the same reason.\n\t\t\tif (isNewRecord.value) return\n\n\t\t\temit('load-record', { doctype: currentDoctype.value, recordId: currentRecordId.value })\n\t\t\tvoid loadRecordData()\n\t\t}\n\t},\n\t{ immediate: true }\n)\n\n// Clear advisory validation errors when the target record changes, so errors from a previously\n// edited record never bleed into a newly opened one.\nwatch([currentDoctype, currentRecordId], () => {\n\tvalidationStore?.clearAll()\n})\n\n// Seeding on entry gives a new record the doctype's declared defaults, which it never used to get.\n// Discarding on exit matters as much: the draft segment is one shared literal, so a stale buffer\n// would open the next New Record pre-filled with the abandoned one's values.\nwatch(\n\t[currentDoctype, currentRecordId],\n\t() => {\n\t\tif (!isNewRecord.value) {\n\t\t\tdraftRecord.value = {}\n\t\t\treturn\n\t\t}\n\t\tconst registry = stonecrop.value?.registry\n\t\tdraftRecord.value = registry ? registry.initializeRecord(getRecordFormSchema()) : {}\n\t},\n\t{ immediate: true }\n)\n\n// Stonecrop reactive computed properties update automatically when the instance\n// becomes available — no manual watcher needed.\n\n// Provide navigation helpers and an emitAction convenience function to child components.\nconst desktopMethods = {\n\tnavigateToDoctype,\n\topenRecord,\n\tcreateNewRecord,\n\t/**\n\t * Convenience wrapper so child components (e.g. slot content) can emit\n\t * an action event without needing a direct reference to the emit function.\n\t */\n\temitAction: (name: string, data?: Record<string, any>) => {\n\t\temit('action', {\n\t\t\tname,\n\t\t\tdoctype: currentDoctype.value,\n\t\t\trecordId: currentRecordId.value,\n\t\t\tdata: data ?? currentViewData.value ?? {},\n\t\t})\n\t},\n}\n\nprovide('desktopMethods', desktopMethods)\n\n// Provide a navigator for AFormLink so the arrow button navigates to the linked record.\nprovide('aformLinkNavigator', {\n\tnavigate: (doctype: string, id: string | number) => {\n\t\tvoid doNavigate({ view: 'record', doctype, recordId: String(id) })\n\t},\n} satisfies AFormLinkNavigator)\n\n// Provide a resolver for AFormLink to look up display text by doctype + id.\n// Checks HST first (sync); falls back to an async client fetch if not cached.\n// Uses the target doctype's declared displayField — no heuristic field guessing.\nprovide('aformLinkResolver', async (doctypeSlug: string, id: string): Promise<string | undefined> => {\n\tif (!stonecrop.value) return undefined\n\ttry {\n\t\tconst meta = await stonecrop.value.getMeta({ path: `/${doctypeSlug}`, segments: [doctypeSlug] })\n\t\tconst displayField = meta?.displayField\n\t\tif (!displayField) return undefined\n\n\t\tconst readDisplay = (rec: Record<string, unknown> | undefined): string | undefined => {\n\t\t\tif (!rec) return undefined\n\t\t\tconst val = rec[displayField]\n\t\t\treturn typeof val === 'string' || typeof val === 'number' ? String(val) : undefined\n\t\t}\n\n\t\tconst cached = stonecrop.value.getRecordById(doctypeSlug, id)?.get('') as Record<string, unknown> | undefined\n\t\tconst cachedDisplay = readDisplay(cached)\n\t\tif (cachedDisplay != null) return cachedDisplay\n\n\t\tawait stonecrop.value.getRecord(doctypeSlug, id)\n\t\tconst fetched = stonecrop.value.getRecordById(doctypeSlug, id)?.get('') as Record<string, unknown> | undefined\n\t\treturn readDisplay(fetched)\n\t} catch {\n\t\treturn undefined\n\t}\n})\n\nconst handleKeydown = (event: KeyboardEvent) => {\n\tif ((event.ctrlKey || event.metaKey) && event.key === 'k') {\n\t\tevent.preventDefault()\n\t\tcommandPaletteOpen.value = true\n\t}\n\tif (event.key === 'Escape' && commandPaletteOpen.value) {\n\t\tcommandPaletteOpen.value = false\n\t}\n}\n\nonMounted(() => {\n\tdocument.addEventListener('keydown', handleKeydown)\n})\n\nonUnmounted(() => {\n\tdocument.removeEventListener('keydown', handleKeydown)\n})\n</script>\n","<template>\n\t<div class=\"desktop\" @click=\"handleClick\">\n\t\t<!-- Action Set -->\n\t\t<ActionSet :elements=\"actionElements\" @action-click=\"handleActionClick\" />\n\n\t\t<!-- Main content using AForm -->\n\t\t<AForm\n\t\t\tv-if=\"currentViewSchema.length > 0\"\n\t\t\tv-model:data=\"currentViewData\"\n\t\t\t:schema=\"currentViewSchema\"\n\t\t\t:errors=\"fieldErrors\" />\n\t\t<div v-else-if=\"!stonecrop\" class=\"loading\"><p>Initializing Stonecrop...</p></div>\n\t\t<div v-else class=\"loading\">\n\t\t\t<p>Loading {{ currentView }} data...</p>\n\t\t</div>\n\n\t\t<!-- Sheet Navigation -->\n\t\t<SheetNav :breadcrumbs=\"navigationBreadcrumbs\">\n\t\t\t<template #toolbar>\n\t\t\t\t<slot name=\"sheetnav-toolbar\" />\n\t\t\t</template>\n\t\t</SheetNav>\n\n\t\t<!-- Command Palette -->\n\t\t<CommandPalette\n\t\t\t:is-open=\"commandPaletteOpen\"\n\t\t\t:search=\"searchCommands\"\n\t\t\tplaceholder=\"Type a command or search...\"\n\t\t\t@select=\"executeCommand\"\n\t\t\t@close=\"commandPaletteOpen = false\">\n\t\t\t<template #title=\"{ result }\">\n\t\t\t\t{{ result.title }}\n\t\t\t</template>\n\t\t\t<template #content=\"{ result }\">\n\t\t\t\t{{ result.description }}\n\t\t\t</template>\n\t\t</CommandPalette>\n\t</div>\n</template>\n\n<script setup lang=\"ts\">\n// The draft segment comes from @stonecrop/stonecrop rather than being spelled out here: that\n// package guards fetching, field initialization and workflow readiness on the same question, and\n// when the two were written separately they disagreed and every guard over there went dead.\nimport { DRAFT_RECORD_ID, isDraftRecordId, useStonecrop, useValidationStore } from '@stonecrop/stonecrop'\nimport {\n\tAForm,\n\ttype AFormLinkNavigator,\n\tresolvedFieldsToColumns,\n\ttype ResolvedField,\n\ttype ResolvedTable,\n} from '@stonecrop/aform'\nimport { computed, onMounted, onUnmounted, provide, ref, unref, watch } from 'vue'\n\nimport ActionSet from './ActionSet.vue'\nimport SheetNav from './SheetNav.vue'\nimport CommandPalette from './CommandPalette.vue'\nimport type {\n\tActionElements,\n\tRouteAdapter,\n\tNavigationTarget,\n\tActionEventPayload,\n\tRecordOpenEventPayload,\n\tLoadRecordsEventPayload,\n\tLoadRecordEventPayload,\n} from '../types'\n\nconst { availableDoctypes = [], routeAdapter } = defineProps<{\n\tavailableDoctypes?: string[]\n\t/**\n\t * Pluggable router adapter. When provided, Desktop uses these functions for all\n\t * routing instead of reaching into the registry's internal Vue Router instance.\n\t * Nuxt hosts (or any host with custom route conventions) should supply this.\n\t */\n\trouteAdapter?: RouteAdapter\n}>()\n\nconst emit = defineEmits<{\n\t/**\n\t * Fired when the user triggers an FSM transition (action button click).\n\t * The host app is responsible for calling the server, persisting state, etc.\n\t */\n\taction: [payload: ActionEventPayload]\n\t/**\n\t * Fired when Desktop wants to navigate to a different view.\n\t * Also calls routeAdapter.navigate() if an adapter is provided.\n\t */\n\tnavigate: [target: NavigationTarget]\n\t/**\n\t * Fired when the user opens a specific record.\n\t */\n\t'record:open': [payload: RecordOpenEventPayload]\n\t/**\n\t * Fired when Desktop is about to read records for a list view. A notification, not a request:\n\t * Desktop performs the read itself through `Stonecrop.getRecords`. A host that fetches here\n\t * races that read into the same HST key.\n\t */\n\t'load-records': [payload: LoadRecordsEventPayload]\n\t/**\n\t * Fired when Desktop is about to read a single record for a form view. A notification, not a\n\t * request — see `load-records`. Not emitted for a draft, which has nothing to fetch.\n\t */\n\t'load-record': [payload: LoadRecordEventPayload]\n}>()\n\nconst { stonecrop } = useStonecrop()\n\n// Field-validation store (advisory, client-side). Pinia is a declared peerDependency, kept external\n// in this package's build (rollupOptions), so `useValidationStore()` resolves the host app's single\n// active Pinia directly — the old `getCurrentInstance().$pinia` workaround for the bundled-Pinia bug\n// is no longer needed. Still guarded: validation is optional and Desktop predates it, so a host that\n// mounts Desktop without Pinia disables validation gracefully instead of crashing on mount.\nlet validationStore: ReturnType<typeof useValidationStore> | null = null\ntry {\n\tvalidationStore = useValidationStore()\n} catch {\n\tvalidationStore = null\n}\n\n// Inline field errors handed to AForm. Only surfaced in the record form view (currentView is\n// defined below); empty elsewhere so a previous record's errors never bleed into a list view.\nconst fieldErrors = computed<Record<string, string[]>>(() =>\n\tcurrentView.value === 'record' ? (validationStore?.errorsByField ?? {}) : {}\n)\n\n// State\nconst loading = ref(false)\nconst commandPaletteOpen = ref(false)\n\n// The record being composed on a `/{doctype}/new` route. Deliberately not in HST: a draft has no\n// identity to be keyed by, and both ways of faking one fail — see `DRAFT_RECORD_ID`.\nconst draftRecord = ref<Record<string, any>>({})\n\n// Form/list data management — each view produces a different data shape.\n// List views (doctypes, records) return table row data keyed by fieldname.\n// Record view returns the record's fields for two-way binding — from HST, or from `draftRecord`\n// when the route is a draft.\nconst currentViewData = computed<Record<string, any>>({\n\tget() {\n\t\t// Doctypes list — rows come from availableDoctypes prop (reactive via availableDoctypes)\n\t\tif (currentView.value === 'doctypes') {\n\t\t\treturn {\n\t\t\t\tdoctypes_table:\n\t\t\t\t\tavailableDoctypes?.map(doctype => ({\n\t\t\t\t\t\tid: doctype,\n\t\t\t\t\t\tdoctype,\n\t\t\t\t\t\tdisplay_name: formatDoctypeName(doctype),\n\t\t\t\t\t\tactions: 'View Records',\n\t\t\t\t\t})) ?? [],\n\t\t\t}\n\t\t}\n\n\t\t// Records list — rows come from HST store (reactive because HST is Vue reactive())\n\t\tif (currentView.value === 'records') {\n\t\t\treturn {\n\t\t\t\trecords_table: getRecords().map(record =>\n\t\t\t\t\tObject.assign({}, record, {\n\t\t\t\t\t\tid: resolveRecordId(record) ?? '',\n\t\t\t\t\t\t// A list row is navigation. Actions live on the record view, where the Actions\n\t\t\t\t\t\t// dropdown is built from what the doctype declares and what the record's\n\t\t\t\t\t\t// current state allows. This cell used to also offer Delete, which dispatched\n\t\t\t\t\t\t// an action named `DELETE` that Desktop invented — no doctype in a\n\t\t\t\t\t\t// WorkflowMeta app declares it, so it failed on every click. Removal is a\n\t\t\t\t\t\t// workflow outcome (`archive`, `cancel`) and belongs in the doctype.\n\t\t\t\t\t\tactions: 'Edit',\n\t\t\t\t\t})\n\t\t\t\t),\n\t\t\t}\n\t\t}\n\n\t\t// Record form — read single record from HST\n\t\tif (!stonecrop.value || !currentDoctype.value || !currentRecordId.value) {\n\t\t\treturn {}\n\t\t}\n\n\t\ttry {\n\t\t\tconst source = isNewRecord.value\n\t\t\t\t? draftRecord.value\n\t\t\t\t: (stonecrop.value.getRecordById(currentDoctype.value, currentRecordId.value)?.get('') as\n\t\t\t\t\t\t| Record<string, any>\n\t\t\t\t\t\t| undefined)\n\t\t\t// Return a plain shallow copy so AForm mutations don't propagate directly into\n\t\t\t// the HST reactive object, which would bypass field-trigger diffing and cause\n\t\t\t// setupDeepReactivity to fire triggers for all fields on every keystroke.\n\t\t\tconst flat: Record<string, any> = { ...source }\n\n\t\t\t// AFieldset receives data[fieldsetFieldname] as its data prop, so the fieldset's\n\t\t\t// children must be grouped under the fieldset key. The server returns flat SQL rows,\n\t\t\t// so we nest fieldset children here before AForm renders them.\n\t\t\tconst doctype = stonecrop.value.registry.registry[currentDoctype.value]\n\t\t\tif (doctype) {\n\t\t\t\tfor (const field of doctype.getSchemaArray()) {\n\t\t\t\t\tif (field.kind === 'fieldset') {\n\t\t\t\t\t\tconst nested: Record<string, any> = {}\n\t\t\t\t\t\tfor (const child of field.schema) {\n\t\t\t\t\t\t\tif (child.fieldname) nested[child.fieldname] = flat[child.fieldname]\n\t\t\t\t\t\t}\n\t\t\t\t\t\tflat[field.fieldname] = nested\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn flat\n\t\t} catch {\n\t\t\treturn {}\n\t\t}\n\t},\n\tset(newData: Record<string, any>) {\n\t\t// List views are read-only from AForm's perspective — HST writes only apply to record form\n\t\tif (currentView.value !== 'record') return\n\t\tif (!stonecrop.value || !currentDoctype.value || !currentRecordId.value) {\n\t\t\treturn\n\t\t}\n\n\t\ttry {\n\t\t\t// AForm emits nested data for fieldsets: { fieldsetKey: { childA: val } }.\n\t\t\t// HST and the server both expect flat rows, so flatten fieldset values back before writing.\n\t\t\tconst doctype = stonecrop.value.registry.registry[currentDoctype.value]\n\t\t\tconst fieldsetNames = new Set<string>()\n\t\t\tif (doctype) {\n\t\t\t\tfor (const field of doctype.getSchemaArray()) {\n\t\t\t\t\tif (field.kind === 'fieldset') {\n\t\t\t\t\t\tfieldsetNames.add(field.fieldname)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Two-pass flatten: non-fieldset keys first, then fieldset children.\n\t\t\t// Fieldset children must be applied last — AForm may emit stale flat copies\n\t\t\t// of fieldset children alongside the updated nested value, and the nested\n\t\t\t// value must win regardless of key insertion order.\n\t\t\tconst flatData: Record<string, any> = {}\n\t\t\tconst fieldsetValues: Record<string, any>[] = []\n\t\t\tfor (const [key, value] of Object.entries(newData)) {\n\t\t\t\tif (fieldsetNames.has(key) && value && typeof value === 'object' && !Array.isArray(value)) {\n\t\t\t\t\tfieldsetValues.push(value)\n\t\t\t\t} else {\n\t\t\t\t\tflatData[key] = value\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const nestedValue of fieldsetValues) {\n\t\t\t\tObject.assign(flatData, nestedValue)\n\t\t\t}\n\n\t\t\t// Only update fields that actually changed. Never write undefined — AForm may emit\n\t\t\t// schema fields absent from the record as undefined; writing them would silently\n\t\t\t// clear values that exist. Explicit null is allowed (intentional clear).\n\t\t\tconst changedFields: string[] = []\n\t\t\tif (isNewRecord.value) {\n\t\t\t\t// Reassigned, not mutated, so the getter re-runs. Relying on in-place mutation of the\n\t\t\t\t// cached object is what made a draft's edits vanish on any invalidation.\n\t\t\t\tconst next = { ...draftRecord.value }\n\t\t\t\tfor (const [fieldname, value] of Object.entries(flatData)) {\n\t\t\t\t\tif (value === undefined) continue\n\t\t\t\t\tif (next[fieldname] !== value) {\n\t\t\t\t\t\tnext[fieldname] = value\n\t\t\t\t\t\tchangedFields.push(fieldname)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdraftRecord.value = next\n\t\t\t} else {\n\t\t\t\tconst hstStore = stonecrop.value.getStore()\n\t\t\t\tfor (const [fieldname, value] of Object.entries(flatData)) {\n\t\t\t\t\tif (value === undefined) continue\n\t\t\t\t\tconst fieldPath = `${currentDoctype.value}.${currentRecordId.value}.${fieldname}`\n\t\t\t\t\tconst currentValue = hstStore.has(fieldPath) ? hstStore.get(fieldPath) : undefined\n\t\t\t\t\tif (currentValue !== value) {\n\t\t\t\t\t\thstStore.set(fieldPath, value)\n\t\t\t\t\t\tchangedFields.push(fieldname)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Advisory field-validation runs on the fields that actually changed (honors each\n\t\t\t// trigger's `on` set). Driven here, after the HST writes, so HST stays value-only.\n\t\t\tif (changedFields.length > 0) {\n\t\t\t\tdriveFieldValidation(changedFields)\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.warn('HST update failed:', error)\n\t\t}\n\t},\n})\n\n// Advisory field-validation: run the doctype's triggers for the fields that changed on this edit.\n// Snapshots the post-edit record as a plain, flat object so validators read siblings read-only\n// (the core store freezes it). Fire-and-forget — the reactive error store repaints AForm on resolve.\nfunction driveFieldValidation(changedFields: string[]) {\n\tif (!validationStore || !stonecrop.value || !currentDoctype.value || !currentRecordId.value) return\n\n\tconst doctype = stonecrop.value.registry.getDoctype(currentDoctype.value)\n\tconst triggers = doctype?.getTriggers()\n\tif (!triggers || Object.keys(triggers).length === 0) return\n\n\t// A draft's siblings come from the buffer; reading HST would hand every validator an empty record.\n\tconst record = isNewRecord.value\n\t\t? { ...draftRecord.value }\n\t\t: {\n\t\t\t\t...(stonecrop.value.getRecordById(currentDoctype.value, currentRecordId.value)?.get('') as Record<\n\t\t\t\t\tstring,\n\t\t\t\t\tunknown\n\t\t\t\t>),\n\t\t\t}\n\n\tfor (const field of changedFields) {\n\t\tvoid validationStore.validateField(triggers, field, record)\n\t}\n}\n\n// Computed properties for current route context.\n// When a routeAdapter is provided it takes full precedence over the registry's internal router.\nconst route = computed(() => (routeAdapter ? null : unref(stonecrop.value?.registry.router?.currentRoute)))\nconst router = computed(() => (routeAdapter ? null : stonecrop.value?.registry.router))\nconst currentDoctype = computed(() => {\n\tif (routeAdapter) return routeAdapter.getCurrentDoctype()\n\tif (!route.value) return ''\n\n\t// First check if we have actualDoctype in meta (from registered routes)\n\tif (route.value.meta?.actualDoctype) {\n\t\treturn route.value.meta.actualDoctype as string\n\t}\n\n\t// For named routes, use params.doctype\n\tif (route.value.params.doctype) {\n\t\treturn route.value.params.doctype.toString()\n\t}\n\n\t// For catch-all routes that haven't been registered yet, extract from path\n\tconst pathMatch = route.value.params.pathMatch as string[] | undefined\n\tif (pathMatch && pathMatch.length > 0) {\n\t\treturn pathMatch[0]\n\t}\n\n\treturn ''\n})\n\n// The route doctype for display and navigation (e.g., 'todo')\nconst routeDoctype = computed(() => {\n\tif (routeAdapter) return routeAdapter.getCurrentDoctype()\n\tif (!route.value) return ''\n\n\t// Check route meta first\n\tif (route.value.meta?.doctype) {\n\t\treturn route.value.meta.doctype as string\n\t}\n\n\t// For named routes, use params.doctype\n\tif (route.value.params.doctype) {\n\t\treturn route.value.params.doctype.toString()\n\t}\n\n\t// For catch-all routes, extract from path\n\tconst pathMatch = route.value.params.pathMatch as string[] | undefined\n\tif (pathMatch && pathMatch.length > 0) {\n\t\treturn pathMatch[0]\n\t}\n\n\treturn ''\n})\n\nconst currentRecordId = computed(() => {\n\tif (routeAdapter) return routeAdapter.getCurrentRecordId()\n\tif (!route.value) return ''\n\n\t// For named routes, use params.recordId\n\tif (route.value.params.recordId) {\n\t\treturn route.value.params.recordId.toString()\n\t}\n\n\t// For catch-all routes that haven't been registered yet, extract from path\n\tconst pathMatch = route.value.params.pathMatch as string[] | undefined\n\tif (pathMatch && pathMatch.length > 1) {\n\t\treturn pathMatch[1]\n\t}\n\n\treturn ''\n})\nconst isNewRecord = computed(() => isDraftRecordId(currentRecordId.value))\n\n// Determine current view based on route\nconst currentView = computed(() => {\n\tif (routeAdapter) return routeAdapter.getCurrentView()\n\tif (!route.value) {\n\t\treturn 'doctypes'\n\t}\n\n\t// Home route\n\tif (route.value.name === 'home' || route.value.path === '/') {\n\t\treturn 'doctypes'\n\t}\n\n\t// Named routes from registered doctypes\n\tif (route.value.name && route.value.name !== 'catch-all') {\n\t\tconst routeName = route.value.name as string\n\t\tif (routeName.includes('form') || route.value.params.recordId) {\n\t\t\treturn 'record'\n\t\t} else if (routeName.includes('list') || route.value.params.doctype) {\n\t\t\treturn 'records'\n\t\t}\n\t}\n\n\t// Catch-all route - determine from path structure\n\tconst pathMatch = route.value.params.pathMatch as string[] | undefined\n\tif (pathMatch && pathMatch.length > 0) {\n\t\tconst view = pathMatch.length === 1 ? 'records' : 'record'\n\t\treturn view\n\t}\n\n\treturn 'doctypes'\n})\n\n// Computed properties (now that all helper functions are defined)\n// Helper function to get available transitions for current record.\n// Reads the actual FSM state from the record's `status` field (or falls back to the\n// workflow initial state) so the available action buttons always reflect reality.\nconst getAvailableTransitions = () => {\n\tif (!stonecrop.value || !currentDoctype.value || !currentRecordId.value) {\n\t\treturn []\n\t}\n\n\ttry {\n\t\tconst doctype = stonecrop.value.registry.getDoctype(currentDoctype.value)\n\t\tif (!doctype?.workflow) return []\n\n\t\t// Delegate state resolution to Stonecrop — reads record 'status', falls back to workflow.initial\n\t\tconst currentState = stonecrop.value.getRecordState(currentDoctype.value, currentRecordId.value)\n\n\t\t// Delegate transition lookup to Doctype — no more manual workflow introspection\n\t\tconst transitions = doctype.getAvailableTransitions(currentState)\n\n\t\tconst recordData = currentViewData.value || {}\n\n\t\t// Each transition emits an 'action' event. The host app decides what to do\n\t\t// (call the server, trigger an FSM actor, update HST, etc.).\n\t\treturn transitions.map(({ name }) => ({\n\t\t\t// Prefer the workflow action's human-readable label (WorkflowMeta format);\n\t\t\t// fall back to the raw transition name for XState workflows with no action meta.\n\t\t\tlabel: doctype.getActionMeta(name)?.label ?? name,\n\t\t\taction: () => {\n\t\t\t\temit('action', {\n\t\t\t\t\tname,\n\t\t\t\t\tdoctype: currentDoctype.value,\n\t\t\t\t\trecordId: currentRecordId.value,\n\t\t\t\t\tdata: recordData,\n\t\t\t\t})\n\t\t\t},\n\t\t}))\n\t} catch (error) {\n\t\tconsole.warn('Error getting available transitions:', error)\n\t\treturn []\n\t}\n}\n\n// Helper: stateless Commands available for the current record — side-effect actions that\n// change no workflow state. Surfaced in the same Actions dropdown as transitions; each emits\n// the same 'action' event, so the host's handler runs a Command's clientHandler identically.\nconst getAvailableCommands = () => {\n\tif (!stonecrop.value || !currentDoctype.value || !currentRecordId.value) {\n\t\treturn []\n\t}\n\n\ttry {\n\t\tconst doctype = stonecrop.value.registry.getDoctype(currentDoctype.value)\n\t\tif (!doctype?.workflow) return []\n\n\t\tconst currentState = stonecrop.value.getRecordState(currentDoctype.value, currentRecordId.value)\n\t\tconst commands = doctype.getAvailableCommands(currentState)\n\t\tconst recordData = currentViewData.value || {}\n\n\t\treturn commands.map(({ name }) => ({\n\t\t\tlabel: doctype.getActionMeta(name)?.label ?? name,\n\t\t\taction: () => {\n\t\t\t\temit('action', {\n\t\t\t\t\tname,\n\t\t\t\t\tdoctype: currentDoctype.value,\n\t\t\t\t\trecordId: currentRecordId.value,\n\t\t\t\t\tdata: recordData,\n\t\t\t\t})\n\t\t\t},\n\t\t}))\n\t} catch (error) {\n\t\tconsole.warn('Error getting available commands:', error)\n\t\treturn []\n\t}\n}\n\nconst actionElements = computed(() => {\n\tconst elements: ActionElements[] = []\n\n\tswitch (currentView.value) {\n\t\tcase 'records':\n\t\t\telements.push({\n\t\t\t\ttype: 'button',\n\t\t\t\tlabel: 'New Record',\n\t\t\t\taction: () => void createNewRecord(),\n\t\t\t})\n\t\t\tbreak\n\t\tcase 'record': {\n\t\t\t// Populate the Actions dropdown with every FSM transition AND stateless Command\n\t\t\t// available in the record's current state. Clicking either emits 'action'.\n\t\t\tconst recordActions = [...getAvailableTransitions(), ...getAvailableCommands()]\n\t\t\tif (recordActions.length > 0) {\n\t\t\t\telements.push({\n\t\t\t\t\ttype: 'dropdown',\n\t\t\t\t\tlabel: 'Actions',\n\t\t\t\t\tactions: recordActions,\n\t\t\t\t})\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn elements\n})\n\nconst navigationBreadcrumbs = computed(() => {\n\tconst breadcrumbs: { title: string; to: string }[] = []\n\n\tif (currentView.value === 'records' && routeDoctype.value) {\n\t\tbreadcrumbs.push(\n\t\t\t{ title: 'Home', to: '/' },\n\t\t\t{ title: formatDoctypeName(routeDoctype.value), to: `/${routeDoctype.value}` }\n\t\t)\n\t} else if (currentView.value === 'record' && routeDoctype.value) {\n\t\tconst recordPath = currentRecordId.value\n\t\t\t? `/${routeDoctype.value}/${currentRecordId.value}`\n\t\t\t: (route.value?.fullPath ?? '')\n\t\tbreadcrumbs.push(\n\t\t\t{ title: 'Home', to: '/' },\n\t\t\t{ title: formatDoctypeName(routeDoctype.value), to: `/${routeDoctype.value}` },\n\t\t\t{ title: isNewRecord.value ? 'New Record' : 'Edit Record', to: recordPath }\n\t\t)\n\t}\n\n\treturn breadcrumbs\n})\n\n// Command palette functionality\ntype Command = {\n\ttitle: string\n\tdescription: string\n\taction: () => void\n}\n\nconst searchCommands = (query: string): Command[] => {\n\tconst commands: Command[] = [\n\t\t{\n\t\t\ttitle: 'Go Home',\n\t\t\tdescription: 'Navigate to the home page',\n\t\t\taction: () => void doNavigate({ view: 'doctypes' }),\n\t\t},\n\t\t{\n\t\t\ttitle: 'Toggle Command Palette',\n\t\t\tdescription: 'Open/close the command palette',\n\t\t\taction: () => (commandPaletteOpen.value = !commandPaletteOpen.value),\n\t\t},\n\t]\n\n\t// Add doctype-specific commands\n\tif (routeDoctype.value) {\n\t\tcommands.push({\n\t\t\ttitle: `View ${formatDoctypeName(routeDoctype.value)} Records`,\n\t\t\tdescription: `Navigate to ${routeDoctype.value} list`,\n\t\t\taction: () => void doNavigate({ view: 'records', doctype: routeDoctype.value }),\n\t\t})\n\n\t\tcommands.push({\n\t\t\ttitle: `Create New ${formatDoctypeName(routeDoctype.value)}`,\n\t\t\tdescription: `Create a new ${routeDoctype.value} record`,\n\t\t\taction: () => void createNewRecord(),\n\t\t})\n\t}\n\n\t// Add available doctypes as commands\n\tavailableDoctypes.forEach(doctype => {\n\t\tcommands.push({\n\t\t\ttitle: `View ${formatDoctypeName(doctype)}`,\n\t\t\tdescription: `Navigate to ${doctype} list`,\n\t\t\taction: () => void doNavigate({ view: 'records', doctype }),\n\t\t})\n\t})\n\n\t// Filter commands based on query\n\tif (!query) return commands\n\n\treturn commands.filter(\n\t\tcmd =>\n\t\t\tcmd.title.toLowerCase().includes(query.toLowerCase()) ||\n\t\t\tcmd.description.toLowerCase().includes(query.toLowerCase())\n\t)\n}\n\nconst executeCommand = (command: Command) => {\n\tcommand.action()\n\tcommandPaletteOpen.value = false\n}\n\n// List reads are wired on the records table; ATable owns the fetch via getRecords.\nconst listRecordsFetcher = (options?: import('@stonecrop/schema').GetRecordsOptions) => {\n\tif (!stonecrop.value || !currentDoctype.value) {\n\t\treturn Promise.resolve({ data: [], hasMore: false })\n\t}\n\tconst doctype = stonecrop.value.registry.getDoctype(currentDoctype.value)\n\tif (!doctype) {\n\t\treturn Promise.resolve({ data: [], hasMore: false })\n\t}\n\treturn stonecrop.value.getRecords(doctype, options)\n}\n\n// Helper functions - moved here to avoid \"before initialization\" errors\nconst formatDoctypeName = (doctype: string): string => {\n\treturn doctype\n\t\t.split('-')\n\t\t.map(word => word.charAt(0).toUpperCase() + word.slice(1))\n\t\t.join(' ')\n}\n\n// Internal navigation helper: emits 'navigate', then calls the adapter (if any)\n// or falls back to the registry's Vue Router instance.\nconst doNavigate = async (target: NavigationTarget) => {\n\temit('navigate', target)\n\tif (routeAdapter) {\n\t\tawait routeAdapter.navigate(target)\n\t} else {\n\t\tif (target.view === 'doctypes') {\n\t\t\tawait router.value?.push('/')\n\t\t} else if (target.view === 'records' && target.doctype) {\n\t\t\tawait router.value?.push(`/${target.doctype}`)\n\t\t} else if (target.view === 'record' && target.doctype && target.recordId) {\n\t\t\tawait router.value?.push(`/${target.doctype}/${target.recordId}`)\n\t\t}\n\t}\n}\n\nconst navigateToDoctype = async (doctype: string) => {\n\tawait doNavigate({ view: 'records', doctype })\n}\n\nconst openRecord = async (recordId: string) => {\n\tconst doctype = routeDoctype.value\n\temit('record:open', { doctype, recordId })\n\tawait doNavigate({ view: 'record', doctype, recordId })\n}\n\nconst createNewRecord = async () => {\n\tawait doNavigate({ view: 'record', doctype: routeDoctype.value, recordId: DRAFT_RECORD_ID })\n}\n\n// Schema generator functions - moved here to be available to computed properties\nconst getDoctypesSchema = (): ResolvedField[] => {\n\tif (!availableDoctypes?.length) return []\n\n\treturn [\n\t\t{\n\t\t\tkind: 'table' as const,\n\t\t\tfieldname: 'doctypes_table',\n\t\t\tcomponent: 'ATable',\n\t\t\tlabel: 'Doctypes',\n\t\t\tschema: [\n\t\t\t\t{\n\t\t\t\t\tfieldname: 'doctype',\n\t\t\t\t\tlabel: 'Doctype',\n\t\t\t\t\tcomponent: 'ATextInput',\n\t\t\t\t\talign: 'left' as const,\n\t\t\t\t\tedit: false,\n\t\t\t\t\twidth: '20ch',\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tfieldname: 'display_name',\n\t\t\t\t\tlabel: 'Name',\n\t\t\t\t\tcomponent: 'ATextInput',\n\t\t\t\t\talign: 'left' as const,\n\t\t\t\t\tedit: false,\n\t\t\t\t\twidth: '30ch',\n\t\t\t\t},\n\t\t\t\t// No record count column. It read `getRecordIds(doctype).length`, which is how many\n\t\t\t\t// records HST happens to hold — zero for a doctype never opened, and the page size\n\t\t\t\t// for one that was. The real total belongs to the backend and Desktop never asks\n\t\t\t\t// for it, so this shell cannot answer it. Same reason the `recordIdField` prop went.\n\t\t\t\t{\n\t\t\t\t\tfieldname: 'actions',\n\t\t\t\t\tlabel: 'Actions',\n\t\t\t\t\tcomponent: 'ATextInput',\n\t\t\t\t\talign: 'center' as const,\n\t\t\t\t\tedit: false,\n\t\t\t\t\twidth: '20ch',\n\t\t\t\t},\n\t\t\t],\n\t\t\tconfig: { view: 'list' as const, fullWidth: true },\n\t\t} satisfies ResolvedTable,\n\t]\n}\n\nconst getRecordsSchema = (): ResolvedField[] => {\n\tif (!currentDoctype.value) return []\n\tif (!stonecrop.value) return []\n\n\tconst registry = stonecrop.value.registry\n\tconst doctype = registry.registry[currentDoctype.value]\n\n\tif (!doctype) return []\n\n\tconst schema = registry.resolveSchema(doctype)\n\n\t// If no schema is available, let the template fallback handle the loading state\n\tif (schema.length === 0) return []\n\n\tconst doctypeSlug = currentDoctype.value\n\tconst clientConfigured = Boolean(stonecrop.value.getClient())\n\n\treturn [\n\t\t{\n\t\t\tkind: 'table' as const,\n\t\t\tfieldname: 'records_table',\n\t\t\tcomponent: 'ATable',\n\t\t\t// Which resolved fields a cell can render is answered once, in @stonecrop/aform, and\n\t\t\t// called by the child-table builder too. Flattening alone is not the rule: it kept the\n\t\t\t// expanding links, whose value is a nested record or an array of them.\n\t\t\tschema: [...resolvedFieldsToColumns(schema), { fieldname: 'actions', label: 'Actions', component: 'ATextInput' }],\n\t\t\tconfig: { view: 'list' as const, fullWidth: true },\n\t\t\t...(clientConfigured ? { getRecords: listRecordsFetcher, sourceKey: doctypeSlug } : {}),\n\t\t} satisfies ResolvedTable,\n\t]\n}\n\nconst getRecordFormSchema = (): ResolvedField[] => {\n\tif (!currentDoctype.value) return []\n\tif (!stonecrop.value) return []\n\n\ttry {\n\t\tconst registry = stonecrop.value?.registry\n\t\tconst doctype = registry?.registry[currentDoctype.value]\n\n\t\tif (!doctype?.schema) {\n\t\t\t// Let the template fallback handle the loading state\n\t\t\treturn []\n\t\t}\n\n\t\treturn registry.resolveSchema(doctype)\n\t} catch {\n\t\treturn []\n\t}\n}\n\n// Additional data helper functions\nconst getRecords = () => {\n\tif (!stonecrop.value || !currentDoctype.value) {\n\t\treturn []\n\t}\n\n\tconst recordsNode = stonecrop.value.records(currentDoctype.value)\n\tconst recordsData = recordsNode?.get('')\n\n\tif (recordsData && typeof recordsData === 'object' && !Array.isArray(recordsData)) {\n\t\treturn Object.values(recordsData as Record<string, any>)\n\t}\n\n\treturn []\n}\n\n// Schema for different views - defined here after all helper functions are available\nconst currentViewSchema = computed(() => {\n\tswitch (currentView.value) {\n\t\tcase 'doctypes':\n\t\t\treturn getDoctypesSchema()\n\t\tcase 'records':\n\t\t\treturn getRecordsSchema()\n\t\tcase 'record':\n\t\t\treturn getRecordFormSchema()\n\t\tdefault:\n\t\t\treturn []\n\t}\n})\n\nconst handleActionClick = (_label: string, action: (() => void | Promise<void>) | undefined) => {\n\tif (action) {\n\t\tvoid action()\n\t}\n}\n\n/**\n * Resolve a record's identity for links and navigation.\n *\n * Identity is declared once, on the doctype: `primaryKey`, or `id` when nothing is declared.\n * Delegating to `Doctype.getRecordId` is what guarantees this matches the key\n * `Stonecrop.getRecords` stored the record under — resolving it independently here would let a\n * row render a link to an HST path that does not exist.\n *\n * There is deliberately no per-shell override. Identity is a per-doctype fact and one shell\n * renders many doctypes, so a single prop cannot answer it; a shell that named a field would\n * also be overriding the very declaration the store keyed on, which is the bug above.\n */\nconst resolveRecordId = (record: Record<string, unknown>): string | undefined => {\n\tif (!stonecrop.value || !currentDoctype.value) return undefined\n\treturn stonecrop.value.registry.registry[currentDoctype.value]?.getRecordId(record)\n}\n\n// Event handlers\nconst getRecordIdFromRow = (rowElement: HTMLTableRowElement): string | null => {\n\tconst cell = rowElement.querySelector('td[data-rowindex]')\n\tif (!cell) return null\n\n\tconst rowIndexAttr = cell.getAttribute('data-rowindex')\n\tif (rowIndexAttr === null) return null\n\n\tconst rowIndex = parseInt(rowIndexAttr, 10)\n\tif (isNaN(rowIndex)) return null\n\n\tconst records = getRecords()\n\tconst record = records[rowIndex]\n\tif (!record) return null\n\n\treturn resolveRecordId(record) ?? null\n}\n\nconst handleClick = async (event: Event) => {\n\tconst target = event.target as HTMLElement\n\tconst action = target.getAttribute('data-action')\n\n\tif (action === 'create') {\n\t\tawait createNewRecord()\n\t}\n\n\tconst cell = target.closest('td, th')\n\tif (cell) {\n\t\tconst cellText = cell.textContent?.trim()\n\t\tconst row = cell.closest('tr')\n\n\t\tif (cellText === 'View Records' && row) {\n\t\t\t// Get the doctype from the row data\n\t\t\tconst cells = row.querySelectorAll('td')\n\t\t\tif (cells.length > 0) {\n\t\t\t\tconst doctypeCell = cells[1] // Assuming doctype is in second column (first column is index)\n\t\t\t\tconst doctype = doctypeCell.textContent?.trim()\n\t\t\t\tif (doctype) {\n\t\t\t\t\tawait navigateToDoctype(doctype)\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (cellText === 'Edit' && row) {\n\t\t\t// Matched exactly, not by substring. This handler is bound to the whole desktop, so a\n\t\t\t// substring match fired on any cell whose *data* happened to contain the word — a task\n\t\t\t// titled \"Delete old backups\" popped a delete confirmation when you clicked it.\n\t\t\tconst recordId = getRecordIdFromRow(row)\n\t\t\tif (recordId) {\n\t\t\t\tawait openRecord(recordId)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Reads go through Stonecrop, which owns whether to fetch, how to key the result, and where to\n// put it. Desktop asks for data and renders what arrives; it decides none of that itself.\n//\n// Both loaders are no-ops without a client, so a host that populates HST some other way keeps\n// working unchanged rather than taking a thrown error on every navigation.\nconst loadRecordData = async () => {\n\tif (!stonecrop.value || !currentDoctype.value || !stonecrop.value.getClient()) return\n\n\tloading.value = true\n\ttry {\n\t\tawait stonecrop.value.getRecord(currentDoctype.value, currentRecordId.value)\n\t} catch (error) {\n\t\tconsole.warn('Error fetching record:', error)\n\t} finally {\n\t\tloading.value = false\n\t}\n}\n\n// Watch for route changes to load appropriate data\nwatch(\n\t[currentView, currentDoctype, currentRecordId],\n\t() => {\n\t\t// The events are notifications, not fetch requests: they announce what Desktop is about to\n\t\t// read so a host can hang analytics or a prefetch off them. The read itself is Stonecrop's.\n\t\tif (currentView.value === 'records' && currentDoctype.value) {\n\t\t\temit('load-records', { doctype: currentDoctype.value })\n\t\t} else if (currentView.value === 'record' && currentDoctype.value && currentRecordId.value) {\n\t\t\t// A draft has nothing to fetch — the record does not exist on the server yet. Desktop\n\t\t\t// used to emit anyway and leave the host to work it out, which meant every host had to\n\t\t\t// recognise the private draft-id scheme above just to suppress a doomed request; all of\n\t\t\t// them did, identically. `getRecord` declines the same case for the same reason.\n\t\t\tif (isNewRecord.value) return\n\n\t\t\temit('load-record', { doctype: currentDoctype.value, recordId: currentRecordId.value })\n\t\t\tvoid loadRecordData()\n\t\t}\n\t},\n\t{ immediate: true }\n)\n\n// Clear advisory validation errors when the target record changes, so errors from a previously\n// edited record never bleed into a newly opened one.\nwatch([currentDoctype, currentRecordId], () => {\n\tvalidationStore?.clearAll()\n})\n\n// Seeding on entry gives a new record the doctype's declared defaults, which it never used to get.\n// Discarding on exit matters as much: the draft segment is one shared literal, so a stale buffer\n// would open the next New Record pre-filled with the abandoned one's values.\nwatch(\n\t[currentDoctype, currentRecordId],\n\t() => {\n\t\tif (!isNewRecord.value) {\n\t\t\tdraftRecord.value = {}\n\t\t\treturn\n\t\t}\n\t\tconst registry = stonecrop.value?.registry\n\t\tdraftRecord.value = registry ? registry.initializeRecord(getRecordFormSchema()) : {}\n\t},\n\t{ immediate: true }\n)\n\n// Stonecrop reactive computed properties update automatically when the instance\n// becomes available — no manual watcher needed.\n\n// Provide navigation helpers and an emitAction convenience function to child components.\nconst desktopMethods = {\n\tnavigateToDoctype,\n\topenRecord,\n\tcreateNewRecord,\n\t/**\n\t * Convenience wrapper so child components (e.g. slot content) can emit\n\t * an action event without needing a direct reference to the emit function.\n\t */\n\temitAction: (name: string, data?: Record<string, any>) => {\n\t\temit('action', {\n\t\t\tname,\n\t\t\tdoctype: currentDoctype.value,\n\t\t\trecordId: currentRecordId.value,\n\t\t\tdata: data ?? currentViewData.value ?? {},\n\t\t})\n\t},\n}\n\nprovide('desktopMethods', desktopMethods)\n\n// Provide a navigator for AFormLink so the arrow button navigates to the linked record.\nprovide('aformLinkNavigator', {\n\tnavigate: (doctype: string, id: string | number) => {\n\t\tvoid doNavigate({ view: 'record', doctype, recordId: String(id) })\n\t},\n} satisfies AFormLinkNavigator)\n\n// Provide a resolver for AFormLink to look up display text by doctype + id.\n// Checks HST first (sync); falls back to an async client fetch if not cached.\n// Uses the target doctype's declared displayField — no heuristic field guessing.\nprovide('aformLinkResolver', async (doctypeSlug: string, id: string): Promise<string | undefined> => {\n\tif (!stonecrop.value) return undefined\n\ttry {\n\t\tconst meta = await stonecrop.value.getMeta({ path: `/${doctypeSlug}`, segments: [doctypeSlug] })\n\t\tconst displayField = meta?.displayField\n\t\tif (!displayField) return undefined\n\n\t\tconst readDisplay = (rec: Record<string, unknown> | undefined): string | undefined => {\n\t\t\tif (!rec) return undefined\n\t\t\tconst val = rec[displayField]\n\t\t\treturn typeof val === 'string' || typeof val === 'number' ? String(val) : undefined\n\t\t}\n\n\t\tconst cached = stonecrop.value.getRecordById(doctypeSlug, id)?.get('') as Record<string, unknown> | undefined\n\t\tconst cachedDisplay = readDisplay(cached)\n\t\tif (cachedDisplay != null) return cachedDisplay\n\n\t\tawait stonecrop.value.getRecord(doctypeSlug, id)\n\t\tconst fetched = stonecrop.value.getRecordById(doctypeSlug, id)?.get('') as Record<string, unknown> | undefined\n\t\treturn readDisplay(fetched)\n\t} catch {\n\t\treturn undefined\n\t}\n})\n\nconst handleKeydown = (event: KeyboardEvent) => {\n\tif ((event.ctrlKey || event.metaKey) && event.key === 'k') {\n\t\tevent.preventDefault()\n\t\tcommandPaletteOpen.value = true\n\t}\n\tif (event.key === 'Escape' && commandPaletteOpen.value) {\n\t\tcommandPaletteOpen.value = false\n\t}\n}\n\nonMounted(() => {\n\tdocument.addEventListener('keydown', handleKeydown)\n})\n\nonUnmounted(() => {\n\tdocument.removeEventListener('keydown', handleKeydown)\n})\n</script>\n","import { App, type Plugin } from 'vue'\n\nimport ActionSet from '../components/ActionSet.vue'\nimport CommandPalette from '../components/CommandPalette.vue'\nimport Desktop from '../components/Desktop.vue'\nimport SheetNav from '../components/SheetNav.vue'\n\n/**\n * This is the main plugin that will be used to register all the desktop components.\n * @public\n */\nconst plugin: Plugin = {\n\tinstall: (app: App) => {\n\t\tapp.component('ActionSet', ActionSet)\n\t\tapp.component('CommandPalette', CommandPalette)\n\t\tapp.component('Desktop', Desktop)\n\t\tapp.component('SheetNav', SheetNav)\n\t},\n}\n\nexport default plugin\n"],"mappings":"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"}
1
+ {"version":3,"file":"desktop.js","names":["$slots"],"sources":["../src/components/CommandPalette.vue","../src/components/CommandPalette.vue","../src/icons/index.ts","../src/components/ActionSet.vue","../src/components/ActionSet.vue","../src/sheet-nav-toolbar.ts","../src/components/SheetNav.vue","../src/components/SheetNav.vue","../src/composables/useActionSet.ts","../src/components/Desktop.vue","../src/components/Desktop.vue","../src/plugins/index.ts"],"sourcesContent":["<template>\n\t<Teleport to=\"body\">\n\t\t<Transition name=\"fade\">\n\t\t\t<div v-if=\"isOpen\" class=\"command-palette-overlay\" @click=\"closeModal\">\n\t\t\t\t<div class=\"command-palette\" @click.stop>\n\t\t\t\t\t<div class=\"command-palette-header\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tref=\"input\"\n\t\t\t\t\t\t\tv-model=\"query\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tclass=\"command-palette-input\"\n\t\t\t\t\t\t\t:placeholder=\"placeholder\"\n\t\t\t\t\t\t\tautofocus\n\t\t\t\t\t\t\t@keydown=\"handleKeydown\" />\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div v-if=\"results.length\" class=\"command-palette-results\">\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tv-for=\"(result, index) in results\"\n\t\t\t\t\t\t\t:key=\"index\"\n\t\t\t\t\t\t\tclass=\"command-palette-result\"\n\t\t\t\t\t\t\t:class=\"{ selected: index === selectedIndex }\"\n\t\t\t\t\t\t\t@click=\"selectResult(result)\"\n\t\t\t\t\t\t\t@mouseover=\"selectedIndex = index\">\n\t\t\t\t\t\t\t<div class=\"result-title\">\n\t\t\t\t\t\t\t\t<slot name=\"title\" :result=\"result\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div class=\"result-content\">\n\t\t\t\t\t\t\t\t<slot name=\"content\" :result=\"result\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div v-else-if=\"query && !results.length\" class=\"command-palette-no-results\">\n\t\t\t\t\t\t<slot name=\"empty\"> No results found for \"{{ query }}\" </slot>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</Transition>\n\t</Teleport>\n</template>\n\n<script setup lang=\"ts\" generic=\"T\">\nimport { ref, computed, watch, nextTick, useTemplateRef } from 'vue'\n\ndefineSlots<{\n\ttitle?: { result: T }\n\tcontent?: { result: T }\n\tempty?: null\n}>()\n\nconst {\n\tsearch,\n\tisOpen = false,\n\tplaceholder = 'Type a command or search...',\n\tmaxResults = 10,\n} = defineProps<{\n\tsearch: (query: string) => T[]\n\tisOpen?: boolean\n\tplaceholder?: string\n\tmaxResults?: number\n}>()\n\nconst emit = defineEmits<{\n\tselect: [T]\n\tclose: []\n}>()\n\nconst query = ref('')\nconst selectedIndex = ref(0)\nconst inputRef = useTemplateRef('input')\n\nconst results = computed(() => {\n\tif (!query.value) return []\n\tconst searchResults = search(query.value)\n\treturn searchResults.slice(0, maxResults)\n})\n\n// reset search query when modal opens\nwatch(\n\t() => isOpen,\n\tasync open => {\n\t\tif (open) {\n\t\t\tquery.value = ''\n\t\t\tselectedIndex.value = 0\n\t\t\tawait nextTick()\n\t\t\t;(inputRef.value as HTMLInputElement)?.focus()\n\t\t}\n\t}\n)\n\n// reset selected index when results change\nwatch(results, () => {\n\tselectedIndex.value = 0\n})\n\nconst closeModal = () => {\n\temit('close')\n}\n\nconst handleKeydown = (e: KeyboardEvent) => {\n\tswitch (e.key) {\n\t\tcase 'Escape':\n\t\t\tcloseModal()\n\t\t\tbreak\n\t\tcase 'ArrowDown':\n\t\t\te.preventDefault()\n\t\t\tif (results.value.length) {\n\t\t\t\tselectedIndex.value = (selectedIndex.value + 1) % results.value.length\n\t\t\t}\n\t\t\tbreak\n\t\tcase 'ArrowUp':\n\t\t\te.preventDefault()\n\t\t\tif (results.value.length) {\n\t\t\t\tselectedIndex.value = (selectedIndex.value - 1 + results.value.length) % results.value.length\n\t\t\t}\n\t\t\tbreak\n\t\tcase 'Enter':\n\t\t\tif (results.value.length && selectedIndex.value >= 0) {\n\t\t\t\tselectResult(results.value[selectedIndex.value])\n\t\t\t}\n\t\t\tbreak\n\t}\n}\n\nconst selectResult = (result: T) => {\n\temit('select', result)\n\tcloseModal()\n}\n</script>\n\n<style>\n.fade-enter-active,\n.fade-leave-active {\n\ttransition: opacity 0.2s ease;\n}\n\n.fade-enter-from,\n.fade-leave-to {\n\topacity: 0;\n}\n\n.command-palette-overlay {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n\tbackground-color: rgba(0, 0, 0, 0.5);\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: center;\n\tz-index: 300;\n\tpadding-top: 100px;\n}\n\n.command-palette {\n\twidth: 600px;\n\tmax-width: 90%;\n\tbackground-color: white;\n\tborder-radius: 8px;\n\tbox-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);\n\toverflow: hidden;\n\tmax-height: 80vh;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.command-palette-header {\n\tdisplay: flex;\n\tborder-bottom: 1px solid #eaeaea;\n\tpadding: 12px;\n}\n\n.command-palette-input {\n\tflex: 1;\n\tborder: none;\n\toutline: none;\n\tfont-size: 16px;\n\tpadding: 8px 12px;\n\tbackground-color: transparent;\n}\n\n.command-palette-close {\n\tbackground: transparent;\n\tborder: none;\n\tfont-size: 24px;\n\tcursor: pointer;\n\tcolor: #666;\n\tpadding: 0 8px;\n}\n\n.command-palette-close:hover {\n\tcolor: #333;\n}\n\n.command-palette-results {\n\toverflow-y: auto;\n\tmax-height: 60vh;\n}\n\n.command-palette-result {\n\tpadding: 12px 16px;\n\tcursor: pointer;\n\tborder-bottom: 1px solid #f0f0f0;\n}\n\n.command-palette-result:hover,\n.command-palette-result.selected {\n\tbackground-color: #f5f5f5;\n}\n\n.command-palette-result.selected {\n\tbackground-color: rgba(132, 60, 3, 0.1);\n}\n\n.result-title {\n\tfont-weight: 500;\n\tmargin-bottom: 4px;\n\tcolor: #333;\n}\n\n.result-content {\n\tfont-size: 14px;\n\tcolor: #666;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.command-palette-no-results {\n\tpadding: 20px 16px;\n\ttext-align: center;\n\tcolor: #666;\n}\n</style>\n","<template>\n\t<Teleport to=\"body\">\n\t\t<Transition name=\"fade\">\n\t\t\t<div v-if=\"isOpen\" class=\"command-palette-overlay\" @click=\"closeModal\">\n\t\t\t\t<div class=\"command-palette\" @click.stop>\n\t\t\t\t\t<div class=\"command-palette-header\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tref=\"input\"\n\t\t\t\t\t\t\tv-model=\"query\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tclass=\"command-palette-input\"\n\t\t\t\t\t\t\t:placeholder=\"placeholder\"\n\t\t\t\t\t\t\tautofocus\n\t\t\t\t\t\t\t@keydown=\"handleKeydown\" />\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div v-if=\"results.length\" class=\"command-palette-results\">\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tv-for=\"(result, index) in results\"\n\t\t\t\t\t\t\t:key=\"index\"\n\t\t\t\t\t\t\tclass=\"command-palette-result\"\n\t\t\t\t\t\t\t:class=\"{ selected: index === selectedIndex }\"\n\t\t\t\t\t\t\t@click=\"selectResult(result)\"\n\t\t\t\t\t\t\t@mouseover=\"selectedIndex = index\">\n\t\t\t\t\t\t\t<div class=\"result-title\">\n\t\t\t\t\t\t\t\t<slot name=\"title\" :result=\"result\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div class=\"result-content\">\n\t\t\t\t\t\t\t\t<slot name=\"content\" :result=\"result\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div v-else-if=\"query && !results.length\" class=\"command-palette-no-results\">\n\t\t\t\t\t\t<slot name=\"empty\"> No results found for \"{{ query }}\" </slot>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</Transition>\n\t</Teleport>\n</template>\n\n<script setup lang=\"ts\" generic=\"T\">\nimport { ref, computed, watch, nextTick, useTemplateRef } from 'vue'\n\ndefineSlots<{\n\ttitle?: { result: T }\n\tcontent?: { result: T }\n\tempty?: null\n}>()\n\nconst {\n\tsearch,\n\tisOpen = false,\n\tplaceholder = 'Type a command or search...',\n\tmaxResults = 10,\n} = defineProps<{\n\tsearch: (query: string) => T[]\n\tisOpen?: boolean\n\tplaceholder?: string\n\tmaxResults?: number\n}>()\n\nconst emit = defineEmits<{\n\tselect: [T]\n\tclose: []\n}>()\n\nconst query = ref('')\nconst selectedIndex = ref(0)\nconst inputRef = useTemplateRef('input')\n\nconst results = computed(() => {\n\tif (!query.value) return []\n\tconst searchResults = search(query.value)\n\treturn searchResults.slice(0, maxResults)\n})\n\n// reset search query when modal opens\nwatch(\n\t() => isOpen,\n\tasync open => {\n\t\tif (open) {\n\t\t\tquery.value = ''\n\t\t\tselectedIndex.value = 0\n\t\t\tawait nextTick()\n\t\t\t;(inputRef.value as HTMLInputElement)?.focus()\n\t\t}\n\t}\n)\n\n// reset selected index when results change\nwatch(results, () => {\n\tselectedIndex.value = 0\n})\n\nconst closeModal = () => {\n\temit('close')\n}\n\nconst handleKeydown = (e: KeyboardEvent) => {\n\tswitch (e.key) {\n\t\tcase 'Escape':\n\t\t\tcloseModal()\n\t\t\tbreak\n\t\tcase 'ArrowDown':\n\t\t\te.preventDefault()\n\t\t\tif (results.value.length) {\n\t\t\t\tselectedIndex.value = (selectedIndex.value + 1) % results.value.length\n\t\t\t}\n\t\t\tbreak\n\t\tcase 'ArrowUp':\n\t\t\te.preventDefault()\n\t\t\tif (results.value.length) {\n\t\t\t\tselectedIndex.value = (selectedIndex.value - 1 + results.value.length) % results.value.length\n\t\t\t}\n\t\t\tbreak\n\t\tcase 'Enter':\n\t\t\tif (results.value.length && selectedIndex.value >= 0) {\n\t\t\t\tselectResult(results.value[selectedIndex.value])\n\t\t\t}\n\t\t\tbreak\n\t}\n}\n\nconst selectResult = (result: T) => {\n\temit('select', result)\n\tcloseModal()\n}\n</script>\n\n<style>\n.fade-enter-active,\n.fade-leave-active {\n\ttransition: opacity 0.2s ease;\n}\n\n.fade-enter-from,\n.fade-leave-to {\n\topacity: 0;\n}\n\n.command-palette-overlay {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n\tbackground-color: rgba(0, 0, 0, 0.5);\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: center;\n\tz-index: 300;\n\tpadding-top: 100px;\n}\n\n.command-palette {\n\twidth: 600px;\n\tmax-width: 90%;\n\tbackground-color: white;\n\tborder-radius: 8px;\n\tbox-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);\n\toverflow: hidden;\n\tmax-height: 80vh;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.command-palette-header {\n\tdisplay: flex;\n\tborder-bottom: 1px solid #eaeaea;\n\tpadding: 12px;\n}\n\n.command-palette-input {\n\tflex: 1;\n\tborder: none;\n\toutline: none;\n\tfont-size: 16px;\n\tpadding: 8px 12px;\n\tbackground-color: transparent;\n}\n\n.command-palette-close {\n\tbackground: transparent;\n\tborder: none;\n\tfont-size: 24px;\n\tcursor: pointer;\n\tcolor: #666;\n\tpadding: 0 8px;\n}\n\n.command-palette-close:hover {\n\tcolor: #333;\n}\n\n.command-palette-results {\n\toverflow-y: auto;\n\tmax-height: 60vh;\n}\n\n.command-palette-result {\n\tpadding: 12px 16px;\n\tcursor: pointer;\n\tborder-bottom: 1px solid #f0f0f0;\n}\n\n.command-palette-result:hover,\n.command-palette-result.selected {\n\tbackground-color: #f5f5f5;\n}\n\n.command-palette-result.selected {\n\tbackground-color: rgba(132, 60, 3, 0.1);\n}\n\n.result-title {\n\tfont-weight: 500;\n\tmargin-bottom: 4px;\n\tcolor: #333;\n}\n\n.result-content {\n\tfont-size: 14px;\n\tcolor: #666;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.command-palette-no-results {\n\tpadding: 20px 16px;\n\ttext-align: center;\n\tcolor: #666;\n}\n</style>\n","import { defineComponent, h, mergeProps, type Component } from 'vue'\n\n/**\n * Shared 24×24 stroke chrome for ActionSet and shell icons.\n * Paths are Lucide (or a reduced Autoreader tray) so 16px tiles stay a distinct silhouette.\n */\nfunction actionSetIcon(name: string, children: () => ReturnType<typeof h>[]): Component {\n\treturn defineComponent({\n\t\tname,\n\t\tinheritAttrs: false,\n\t\tsetup(_, { attrs }) {\n\t\t\treturn () =>\n\t\t\t\th(\n\t\t\t\t\t'svg',\n\t\t\t\t\tmergeProps(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tviewBox: '0 0 24 24',\n\t\t\t\t\t\t\tfill: 'none',\n\t\t\t\t\t\t\tstroke: 'currentColor',\n\t\t\t\t\t\t\t'stroke-width': '2',\n\t\t\t\t\t\t\t'stroke-linecap': 'round',\n\t\t\t\t\t\t\t'stroke-linejoin': 'round',\n\t\t\t\t\t\t\t'aria-hidden': 'true',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tattrs\n\t\t\t\t\t),\n\t\t\t\t\tchildren()\n\t\t\t\t)\n\t\t},\n\t})\n}\n\n/** Two flowchart nodes and a connecting elbow — FSM / BPA “move”. @public */\nexport const ActionSetIconActions = actionSetIcon('ActionSetIconActions', () => [\n\th('rect', { width: '8', height: '8', x: '3', y: '3', rx: '2' }),\n\th('path', { d: 'M7 11v4a2 2 0 0 0 2 2h4' }),\n\th('rect', { width: '8', height: '8', x: '13', y: '13', rx: '2' }),\n])\n\n/** Magnifier. @public */\nexport const ActionSetIconSearch = actionSetIcon('ActionSetIconSearch', () => [\n\th('circle', { cx: '11', cy: '11', r: '8' }),\n\th('path', { d: 'm21 21-4.3-4.3' }),\n])\n\n/** Speech bubble with a tail. @public */\nexport const ActionSetIconChat = actionSetIcon('ActionSetIconChat', () => [\n\th('path', { d: 'M7.9 20A9 9 0 1 0 4 16.1L2 22Z' }),\n])\n\n/** Autoreader stacked trays, reduced to 24 viewBox. @public */\nexport const ActionSetIconEmail = actionSetIcon('ActionSetIconEmail', () => [\n\th('rect', { x: '5', y: '3', width: '14', height: '8', rx: '1.5' }),\n\th('path', { d: 'M8 7h8' }),\n\th('path', { d: 'M3 12h18' }),\n\th('rect', { x: '5', y: '13', width: '14', height: '8', rx: '1.5' }),\n\th('path', { d: 'M8 17h8' }),\n])\n\n/** Paperclip — attachments, not another document. @public */\nexport const ActionSetIconFiles = actionSetIcon('ActionSetIconFiles', () => [\n\th('path', {\n\t\td: 'm16 6-8.414 8.586a2 2 0 0 0 0 2.828 2 2 0 0 0 2.828 0l8.414-8.586a4 4 0 0 0 0-5.656 4 4 0 0 0-5.656 0l-8.415 8.585a6 6 0 1 0 8.486 8.486',\n\t}),\n])\n\n/** Page with a check — the Approvals stamp, simplified. @public */\nexport const ActionSetIconApprovals = actionSetIcon('ActionSetIconApprovals', () => [\n\th('path', {\n\t\td: 'M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.687.687l3.626 3.626A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z',\n\t}),\n\th('path', { d: 'M14 2v5a1 1 0 0 0 1 1h5' }),\n\th('path', { d: 'm9 15 2 2 4-4' }),\n])\n\n/** Three rising bars — reports / BI. @public */\nexport const ActionSetIconReports = actionSetIcon('ActionSetIconReports', () => [\n\th('path', { d: 'M5 21v-6' }),\n\th('path', { d: 'M12 21V3' }),\n\th('path', { d: 'M19 21V9' }),\n])\n\n/** Printer. @public */\nexport const ActionSetIconPrint = actionSetIcon('ActionSetIconPrint', () => [\n\th('path', { d: 'M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2' }),\n\th('path', { d: 'M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6' }),\n\th('rect', { x: '6', y: '14', width: '12', height: '8', rx: '1' }),\n])\n\n/** Gear. @public */\nexport const ActionSetIconSettings = actionSetIcon('ActionSetIconSettings', () => [\n\th('path', {\n\t\td: 'M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915',\n\t}),\n\th('circle', { cx: '12', cy: '12', r: '3' }),\n])\n\n/** Circled question mark. @public */\nexport const ActionSetIconHelp = actionSetIcon('ActionSetIconHelp', () => [\n\th('circle', { cx: '12', cy: '12', r: '10' }),\n\th('path', { d: 'M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3' }),\n\th('path', { d: 'M12 17h.01' }),\n])\n","<template>\n\t<div\n\t\tclass=\"action-set\"\n\t\t:class=\"{\n\t\t\t'action-set--expanded': isExpanded,\n\t\t\t'action-set--drawer-open': drawerOpen,\n\t\t}\">\n\t\t<div class=\"action-set__tile\" role=\"presentation\">\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"action-set__toggle\"\n\t\t\t\t:class=\"{ 'action-set__toggle--expanded': isExpanded }\"\n\t\t\t\taria-label=\"Toggle menu\"\n\t\t\t\t:aria-expanded=\"isExpanded\"\n\t\t\t\t@click=\"onToggle\">\n\t\t\t\t+\n\t\t\t</button>\n\n\t\t\t<template v-if=\"isExpanded\">\n\t\t\t\t<button\n\t\t\t\t\tv-for=\"tab in allTabs\"\n\t\t\t\t\t:key=\"tab.id\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"action-set__item\"\n\t\t\t\t\t:class=\"{ 'action-set__item--active': drawerOpen && activeTabId === tab.id }\"\n\t\t\t\t\t:aria-label=\"tab.label\"\n\t\t\t\t\t:aria-current=\"drawerOpen && activeTabId === tab.id ? 'page' : undefined\"\n\t\t\t\t\t:title=\"tab.label\"\n\t\t\t\t\t@click=\"onTileClick(tab.id)\">\n\t\t\t\t\t<component :is=\"tab.icon\" v-if=\"tab.icon\" class=\"action-set__item-icon\" />\n\t\t\t\t\t<span v-else class=\"action-set__item-fallback\" aria-hidden=\"true\">{{ slotFallback(tab.label) }}</span>\n\t\t\t\t\t<span v-if=\"tabBadge(tab) > 0\" class=\"action-set__item-badge\">{{ tabBadge(tab) }}</span>\n\t\t\t\t</button>\n\t\t\t</template>\n\t\t</div>\n\n\t\t<aside\n\t\t\tv-if=\"drawerOpen\"\n\t\t\tref=\"drawerEl\"\n\t\t\tclass=\"action-set__drawer\"\n\t\t\taria-label=\"Side panel\"\n\t\t\t@keydown=\"onDrawerKeydown\">\n\t\t\t<header class=\"action-set__drawer-header\">\n\t\t\t\t<button type=\"button\" class=\"action-set__drawer-close\" aria-label=\"Close panel\" @click=\"controller.close()\">\n\t\t\t\t\t×\n\t\t\t\t</button>\n\t\t\t</header>\n\t\t\t<div class=\"action-set__drawer-body\">\n\t\t\t\t<template v-if=\"activeTabId === ACTIONS_TAB_ID\">\n\t\t\t\t\t<div class=\"action-set__actions-list\">\n\t\t\t\t\t\t<template v-for=\"el in elements\" :key=\"el.label\">\n\t\t\t\t\t\t\t<div v-if=\"el.type === 'dropdown'\" class=\"action-set__actions-group\" role=\"group\" :aria-label=\"el.label\">\n\t\t\t\t\t\t\t\t<p class=\"action-set__actions-group-label\" aria-hidden=\"true\">{{ el.label }}</p>\n\t\t\t\t\t\t\t\t<template v-for=\"item in el.actions\" :key=\"item.label\">\n\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\tv-if=\"item.action\"\n\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\tclass=\"action-set__actions-list-item action-set__actions-list-item--nested\"\n\t\t\t\t\t\t\t\t\t\t@click=\"onActionClick(item.label, item.action)\">\n\t\t\t\t\t\t\t\t\t\t{{ item.label }}\n\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\t\tv-else-if=\"item.link\"\n\t\t\t\t\t\t\t\t\t\t:href=\"item.link\"\n\t\t\t\t\t\t\t\t\t\tclass=\"action-set__actions-list-item action-set__actions-list-item--nested\">\n\t\t\t\t\t\t\t\t\t\t{{ item.label }}\n\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<a v-else-if=\"!el.action && el.link\" :href=\"el.link\" class=\"action-set__actions-list-item\">\n\t\t\t\t\t\t\t\t{{ el.label }}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tv-else\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclass=\"action-set__actions-list-item\"\n\t\t\t\t\t\t\t\t:disabled=\"el.disabled\"\n\t\t\t\t\t\t\t\t@click=\"onActionClick(el.label, el.action)\">\n\t\t\t\t\t\t\t\t{{ el.label }}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<p v-if=\"elements.length === 0\" class=\"action-set__actions-empty\">No actions available</p>\n\t\t\t\t\t</div>\n\t\t\t\t</template>\n\t\t\t\t<template v-else-if=\"activeSlot\">\n\t\t\t\t\t<component :is=\"activeSlot.component\" v-if=\"activeSlot.component\" :key=\"activeSlot.id\" />\n\t\t\t\t\t<p v-else class=\"action-set__drawer-empty\">No content</p>\n\t\t\t\t</template>\n\t\t\t</div>\n\t\t</aside>\n\t</div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, markRaw, nextTick, ref, unref, watch, type Component } from 'vue'\n\nimport type { ActionSetController } from '../composables/useActionSet'\nimport { ActionSetIconActions, ActionSetIconSearch } from '../icons'\nimport type { ActionElements, ActionSetSlot } from '../types'\n\nconst ACTIONS_TAB_ID = '__actions__'\nconst SEARCH_TAB_ID = '__search__'\n\ntype Tab = {\n\tid: string\n\tlabel: string\n\ticon?: Component\n\tbadge?: number\n}\n\nconst {\n\tslots = [],\n\telements = [],\n\tcontroller,\n} = defineProps<{\n\tslots?: ActionSetSlot[]\n\telements?: ActionElements[]\n\tcontroller: ActionSetController\n}>()\n\nconst emit = defineEmits<{\n\tactionClick: [label: string, action: (() => void | Promise<void>) | undefined]\n\tsearch: []\n}>()\n\nconst isExpanded = ref(true)\nconst drawerEl = ref<HTMLElement | null>(null)\nconst lastFocusedBeforeDrawer = ref<HTMLElement | null>(null)\n\nconst activeSlot = computed(() => slots.find(slot => slot.id === controller.activeSlotId.value) ?? null)\nconst hasActions = computed(() => elements.length > 0)\nconst activeTabId = computed(() => (controller.isActionsOpen.value ? ACTIONS_TAB_ID : controller.activeSlotId.value))\nconst drawerOpen = computed(() => controller.isDrawerOpen.value)\n\nconst allTabs = computed<Tab[]>(() => {\n\tconst tabs: Tab[] = [\n\t\t{\n\t\t\tid: SEARCH_TAB_ID,\n\t\t\tlabel: 'Search',\n\t\t\ticon: markRaw(ActionSetIconSearch),\n\t\t},\n\t]\n\n\tfor (const slot of slots) {\n\t\ttabs.push({\n\t\t\tid: slot.id,\n\t\t\tlabel: slot.label,\n\t\t\ticon: slot.icon,\n\t\t\tbadge: slot.badge !== undefined ? unref(slot.badge) : undefined,\n\t\t})\n\t}\n\n\tif (hasActions.value) {\n\t\ttabs.push({\n\t\t\tid: ACTIONS_TAB_ID,\n\t\t\tlabel: 'Actions',\n\t\t\ticon: markRaw(ActionSetIconActions),\n\t\t})\n\t}\n\n\treturn tabs\n})\n\nfunction slotFallback(label: string): string {\n\treturn label.trim().charAt(0).toUpperCase() || '?'\n}\n\nfunction tabBadge(tab: Tab): number {\n\tconst count = tab.badge\n\treturn typeof count === 'number' && count > 0 ? count : 0\n}\n\n// The drawer sits beside the page rather than over it, so Tab is left to the browser: trapping it\n// would strand keyboard users away from the page and from a preview opened out of the drawer.\nfunction onDrawerKeydown(event: KeyboardEvent) {\n\tif (event.key !== 'Escape') return\n\tevent.preventDefault()\n\tevent.stopPropagation()\n\tcontroller.close()\n}\n\nfunction onToggle() {\n\tisExpanded.value = !isExpanded.value\n}\n\nfunction onTileClick(tabId: string) {\n\tif (tabId === SEARCH_TAB_ID) {\n\t\temit('search')\n\t} else if (drawerOpen.value && activeTabId.value === tabId) {\n\t\tcontroller.close()\n\t} else if (tabId === ACTIONS_TAB_ID) {\n\t\tcontroller.openActions()\n\t} else {\n\t\tcontroller.openSlot(tabId)\n\t}\n}\n\nfunction onActionClick(label: string, action: (() => void) | undefined) {\n\tif (action) {\n\t\temit('actionClick', label, action)\n\t}\n}\n\nwatch(drawerOpen, async open => {\n\tif (open) {\n\t\tlastFocusedBeforeDrawer.value = document.activeElement instanceof HTMLElement ? document.activeElement : null\n\t\tawait nextTick()\n\t\tdrawerEl.value?.querySelector<HTMLElement>('button:not([disabled]), [href], input, select, textarea')?.focus()\n\t\treturn\n\t}\n\tlastFocusedBeforeDrawer.value?.focus()\n\tlastFocusedBeforeDrawer.value = null\n})\n</script>\n\n<style scoped>\n.action-set {\n\t/* Right offset + tile column width + gap: the strip the drawer keeps clear for the tile column. */\n\t--action-set-rail-inset: calc(10px + 2.75rem + 18px + 8px);\n\tposition: fixed;\n\ttop: var(--sc-action-set-offset-top);\n\tright: 10px;\n\tz-index: 1001;\n\tdisplay: flex;\n\tflex-direction: row-reverse;\n\talign-items: flex-start;\n\tgap: 0;\n}\n\n/* Positioned above the drawer, which is a later sibling in the same stacking context. */\n.action-set__tile {\n\tposition: relative;\n\tz-index: 1;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tpadding: 8px;\n\tbackground: var(--sc-form-background);\n\tborder: 1px solid var(--sc-gray-20);\n}\n\n.action-set__toggle {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 2.75rem;\n\theight: 2.75rem;\n\tpadding: 0;\n\tborder: none;\n\tbackground: transparent;\n\tfont-size: 1.5rem;\n\tfont-weight: 300;\n\tline-height: 1;\n\tcolor: var(--sc-gray-60);\n\tcursor: pointer;\n\ttransition: transform 0.2s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n\t.action-set__toggle {\n\t\ttransition: none;\n\t}\n}\n\n.action-set__toggle--expanded {\n\ttransform: rotate(45deg);\n}\n\n.action-set__toggle:focus-visible,\n.action-set__item:focus-visible,\n.action-set__drawer-close:focus-visible,\n.action-set__actions-list-item:focus-visible {\n\toutline: 2px solid var(--sc-primary-color);\n\toutline-offset: 2px;\n}\n\n.action-set__item {\n\tposition: relative;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 2.75rem;\n\theight: 2.75rem;\n\tmargin-top: 8px;\n\tpadding: 0;\n\tborder: 1px solid var(--sc-gray-20);\n\tbackground: transparent;\n\tcursor: pointer;\n\tfont-family: var(--sc-font-family);\n\tcolor: var(--sc-gray-80);\n}\n\n.action-set__item:hover,\n.action-set__item--active {\n\tbackground: var(--sc-btn-hover);\n}\n\n.action-set__item-icon {\n\tdisplay: block;\n\twidth: 1rem;\n\theight: 1rem;\n\tflex-shrink: 0;\n}\n\n.action-set__item-fallback {\n\tfont-size: 0.85rem;\n\tfont-weight: 600;\n\tline-height: 1;\n}\n\n.action-set__item-badge {\n\tposition: absolute;\n\ttop: 2px;\n\tright: 2px;\n\tmin-width: 14px;\n\theight: 14px;\n\tpadding: 0 3px;\n\tborder-radius: 7px;\n\tbackground: var(--sc-badge-danger-accent);\n\tcolor: var(--sc-primary-text-color);\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tline-height: 14px;\n\ttext-align: center;\n\tpointer-events: none;\n}\n\n.action-set__drawer {\n\tposition: fixed;\n\ttop: 0;\n\tright: 0;\n\tbottom: 0;\n\tbox-sizing: border-box;\n\twidth: var(--sc-action-set-drawer-width);\n\tpadding-right: var(--action-set-rail-inset);\n\tbackground: var(--sc-form-background);\n\tborder-left: 1px solid var(--sc-gray-20);\n\tdisplay: flex;\n\tflex-direction: column;\n\tz-index: 0;\n}\n\n.action-set__drawer-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-end;\n\tpadding: 8px 12px;\n\tflex-shrink: 0;\n}\n\n.action-set__drawer-close {\n\tflex-shrink: 0;\n\tborder: none;\n\tbackground: transparent;\n\tfont-size: 1.25rem;\n\tline-height: 1;\n\tcursor: pointer;\n\tcolor: var(--sc-gray-60);\n\tpadding: 0.5rem;\n\tmin-width: 2.75rem;\n\tmin-height: 2.75rem;\n}\n\n.action-set__drawer-body {\n\tflex: 1;\n\tmin-height: 0;\n\toverflow: auto;\n}\n\n.action-set__drawer-empty {\n\tpadding: 16px 12px;\n\tmargin: 0;\n\tcolor: var(--sc-gray-60);\n\tfont-style: italic;\n}\n\n.action-set__actions-list {\n\tpadding: 8px;\n}\n\n.action-set__actions-list-item {\n\tdisplay: block;\n\tbox-sizing: border-box;\n\twidth: 100%;\n\tpadding: 10px 12px;\n\tmargin-bottom: 4px;\n\tborder: 1px solid var(--sc-gray-20);\n\tbackground: transparent;\n\ttext-align: left;\n\tfont-size: 0.9rem;\n\tfont-family: var(--sc-font-family);\n\tfont-weight: 500;\n\tcolor: var(--sc-gray-80);\n\ttext-decoration: none;\n\tcursor: pointer;\n}\n\n.action-set__actions-list-item:hover {\n\tbackground: var(--sc-btn-hover);\n}\n\n.action-set__actions-list-item:disabled {\n\topacity: 0.5;\n\tcursor: not-allowed;\n}\n\n.action-set__actions-list-item--nested {\n\tmargin-left: 0;\n\twidth: 100%;\n\tfont-weight: 400;\n}\n\n.action-set__actions-group {\n\tmargin-bottom: 8px;\n}\n\n.action-set__actions-group-label {\n\tmargin: 4px 0 6px;\n\tpadding: 0 2px;\n\tfont-family: var(--sc-font-family);\n\tfont-size: 0.75rem;\n\tfont-weight: 600;\n\tletter-spacing: 0.04em;\n\ttext-transform: uppercase;\n\tcolor: var(--sc-gray-60);\n}\n\n.action-set__actions-empty {\n\tpadding: 16px 12px;\n\tmargin: 0;\n\tcolor: var(--sc-gray-60);\n\tfont-style: italic;\n}\n</style>\n","<template>\n\t<div\n\t\tclass=\"action-set\"\n\t\t:class=\"{\n\t\t\t'action-set--expanded': isExpanded,\n\t\t\t'action-set--drawer-open': drawerOpen,\n\t\t}\">\n\t\t<div class=\"action-set__tile\" role=\"presentation\">\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"action-set__toggle\"\n\t\t\t\t:class=\"{ 'action-set__toggle--expanded': isExpanded }\"\n\t\t\t\taria-label=\"Toggle menu\"\n\t\t\t\t:aria-expanded=\"isExpanded\"\n\t\t\t\t@click=\"onToggle\">\n\t\t\t\t+\n\t\t\t</button>\n\n\t\t\t<template v-if=\"isExpanded\">\n\t\t\t\t<button\n\t\t\t\t\tv-for=\"tab in allTabs\"\n\t\t\t\t\t:key=\"tab.id\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"action-set__item\"\n\t\t\t\t\t:class=\"{ 'action-set__item--active': drawerOpen && activeTabId === tab.id }\"\n\t\t\t\t\t:aria-label=\"tab.label\"\n\t\t\t\t\t:aria-current=\"drawerOpen && activeTabId === tab.id ? 'page' : undefined\"\n\t\t\t\t\t:title=\"tab.label\"\n\t\t\t\t\t@click=\"onTileClick(tab.id)\">\n\t\t\t\t\t<component :is=\"tab.icon\" v-if=\"tab.icon\" class=\"action-set__item-icon\" />\n\t\t\t\t\t<span v-else class=\"action-set__item-fallback\" aria-hidden=\"true\">{{ slotFallback(tab.label) }}</span>\n\t\t\t\t\t<span v-if=\"tabBadge(tab) > 0\" class=\"action-set__item-badge\">{{ tabBadge(tab) }}</span>\n\t\t\t\t</button>\n\t\t\t</template>\n\t\t</div>\n\n\t\t<aside\n\t\t\tv-if=\"drawerOpen\"\n\t\t\tref=\"drawerEl\"\n\t\t\tclass=\"action-set__drawer\"\n\t\t\taria-label=\"Side panel\"\n\t\t\t@keydown=\"onDrawerKeydown\">\n\t\t\t<header class=\"action-set__drawer-header\">\n\t\t\t\t<button type=\"button\" class=\"action-set__drawer-close\" aria-label=\"Close panel\" @click=\"controller.close()\">\n\t\t\t\t\t×\n\t\t\t\t</button>\n\t\t\t</header>\n\t\t\t<div class=\"action-set__drawer-body\">\n\t\t\t\t<template v-if=\"activeTabId === ACTIONS_TAB_ID\">\n\t\t\t\t\t<div class=\"action-set__actions-list\">\n\t\t\t\t\t\t<template v-for=\"el in elements\" :key=\"el.label\">\n\t\t\t\t\t\t\t<div v-if=\"el.type === 'dropdown'\" class=\"action-set__actions-group\" role=\"group\" :aria-label=\"el.label\">\n\t\t\t\t\t\t\t\t<p class=\"action-set__actions-group-label\" aria-hidden=\"true\">{{ el.label }}</p>\n\t\t\t\t\t\t\t\t<template v-for=\"item in el.actions\" :key=\"item.label\">\n\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\tv-if=\"item.action\"\n\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\tclass=\"action-set__actions-list-item action-set__actions-list-item--nested\"\n\t\t\t\t\t\t\t\t\t\t@click=\"onActionClick(item.label, item.action)\">\n\t\t\t\t\t\t\t\t\t\t{{ item.label }}\n\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\t\tv-else-if=\"item.link\"\n\t\t\t\t\t\t\t\t\t\t:href=\"item.link\"\n\t\t\t\t\t\t\t\t\t\tclass=\"action-set__actions-list-item action-set__actions-list-item--nested\">\n\t\t\t\t\t\t\t\t\t\t{{ item.label }}\n\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<a v-else-if=\"!el.action && el.link\" :href=\"el.link\" class=\"action-set__actions-list-item\">\n\t\t\t\t\t\t\t\t{{ el.label }}\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tv-else\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclass=\"action-set__actions-list-item\"\n\t\t\t\t\t\t\t\t:disabled=\"el.disabled\"\n\t\t\t\t\t\t\t\t@click=\"onActionClick(el.label, el.action)\">\n\t\t\t\t\t\t\t\t{{ el.label }}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<p v-if=\"elements.length === 0\" class=\"action-set__actions-empty\">No actions available</p>\n\t\t\t\t\t</div>\n\t\t\t\t</template>\n\t\t\t\t<template v-else-if=\"activeSlot\">\n\t\t\t\t\t<component :is=\"activeSlot.component\" v-if=\"activeSlot.component\" :key=\"activeSlot.id\" />\n\t\t\t\t\t<p v-else class=\"action-set__drawer-empty\">No content</p>\n\t\t\t\t</template>\n\t\t\t</div>\n\t\t</aside>\n\t</div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, markRaw, nextTick, ref, unref, watch, type Component } from 'vue'\n\nimport type { ActionSetController } from '../composables/useActionSet'\nimport { ActionSetIconActions, ActionSetIconSearch } from '../icons'\nimport type { ActionElements, ActionSetSlot } from '../types'\n\nconst ACTIONS_TAB_ID = '__actions__'\nconst SEARCH_TAB_ID = '__search__'\n\ntype Tab = {\n\tid: string\n\tlabel: string\n\ticon?: Component\n\tbadge?: number\n}\n\nconst {\n\tslots = [],\n\telements = [],\n\tcontroller,\n} = defineProps<{\n\tslots?: ActionSetSlot[]\n\telements?: ActionElements[]\n\tcontroller: ActionSetController\n}>()\n\nconst emit = defineEmits<{\n\tactionClick: [label: string, action: (() => void | Promise<void>) | undefined]\n\tsearch: []\n}>()\n\nconst isExpanded = ref(true)\nconst drawerEl = ref<HTMLElement | null>(null)\nconst lastFocusedBeforeDrawer = ref<HTMLElement | null>(null)\n\nconst activeSlot = computed(() => slots.find(slot => slot.id === controller.activeSlotId.value) ?? null)\nconst hasActions = computed(() => elements.length > 0)\nconst activeTabId = computed(() => (controller.isActionsOpen.value ? ACTIONS_TAB_ID : controller.activeSlotId.value))\nconst drawerOpen = computed(() => controller.isDrawerOpen.value)\n\nconst allTabs = computed<Tab[]>(() => {\n\tconst tabs: Tab[] = [\n\t\t{\n\t\t\tid: SEARCH_TAB_ID,\n\t\t\tlabel: 'Search',\n\t\t\ticon: markRaw(ActionSetIconSearch),\n\t\t},\n\t]\n\n\tfor (const slot of slots) {\n\t\ttabs.push({\n\t\t\tid: slot.id,\n\t\t\tlabel: slot.label,\n\t\t\ticon: slot.icon,\n\t\t\tbadge: slot.badge !== undefined ? unref(slot.badge) : undefined,\n\t\t})\n\t}\n\n\tif (hasActions.value) {\n\t\ttabs.push({\n\t\t\tid: ACTIONS_TAB_ID,\n\t\t\tlabel: 'Actions',\n\t\t\ticon: markRaw(ActionSetIconActions),\n\t\t})\n\t}\n\n\treturn tabs\n})\n\nfunction slotFallback(label: string): string {\n\treturn label.trim().charAt(0).toUpperCase() || '?'\n}\n\nfunction tabBadge(tab: Tab): number {\n\tconst count = tab.badge\n\treturn typeof count === 'number' && count > 0 ? count : 0\n}\n\n// The drawer sits beside the page rather than over it, so Tab is left to the browser: trapping it\n// would strand keyboard users away from the page and from a preview opened out of the drawer.\nfunction onDrawerKeydown(event: KeyboardEvent) {\n\tif (event.key !== 'Escape') return\n\tevent.preventDefault()\n\tevent.stopPropagation()\n\tcontroller.close()\n}\n\nfunction onToggle() {\n\tisExpanded.value = !isExpanded.value\n}\n\nfunction onTileClick(tabId: string) {\n\tif (tabId === SEARCH_TAB_ID) {\n\t\temit('search')\n\t} else if (drawerOpen.value && activeTabId.value === tabId) {\n\t\tcontroller.close()\n\t} else if (tabId === ACTIONS_TAB_ID) {\n\t\tcontroller.openActions()\n\t} else {\n\t\tcontroller.openSlot(tabId)\n\t}\n}\n\nfunction onActionClick(label: string, action: (() => void) | undefined) {\n\tif (action) {\n\t\temit('actionClick', label, action)\n\t}\n}\n\nwatch(drawerOpen, async open => {\n\tif (open) {\n\t\tlastFocusedBeforeDrawer.value = document.activeElement instanceof HTMLElement ? document.activeElement : null\n\t\tawait nextTick()\n\t\tdrawerEl.value?.querySelector<HTMLElement>('button:not([disabled]), [href], input, select, textarea')?.focus()\n\t\treturn\n\t}\n\tlastFocusedBeforeDrawer.value?.focus()\n\tlastFocusedBeforeDrawer.value = null\n})\n</script>\n\n<style scoped>\n.action-set {\n\t/* Right offset + tile column width + gap: the strip the drawer keeps clear for the tile column. */\n\t--action-set-rail-inset: calc(10px + 2.75rem + 18px + 8px);\n\tposition: fixed;\n\ttop: var(--sc-action-set-offset-top);\n\tright: 10px;\n\tz-index: 1001;\n\tdisplay: flex;\n\tflex-direction: row-reverse;\n\talign-items: flex-start;\n\tgap: 0;\n}\n\n/* Positioned above the drawer, which is a later sibling in the same stacking context. */\n.action-set__tile {\n\tposition: relative;\n\tz-index: 1;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tpadding: 8px;\n\tbackground: var(--sc-form-background);\n\tborder: 1px solid var(--sc-gray-20);\n}\n\n.action-set__toggle {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 2.75rem;\n\theight: 2.75rem;\n\tpadding: 0;\n\tborder: none;\n\tbackground: transparent;\n\tfont-size: 1.5rem;\n\tfont-weight: 300;\n\tline-height: 1;\n\tcolor: var(--sc-gray-60);\n\tcursor: pointer;\n\ttransition: transform 0.2s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n\t.action-set__toggle {\n\t\ttransition: none;\n\t}\n}\n\n.action-set__toggle--expanded {\n\ttransform: rotate(45deg);\n}\n\n.action-set__toggle:focus-visible,\n.action-set__item:focus-visible,\n.action-set__drawer-close:focus-visible,\n.action-set__actions-list-item:focus-visible {\n\toutline: 2px solid var(--sc-primary-color);\n\toutline-offset: 2px;\n}\n\n.action-set__item {\n\tposition: relative;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 2.75rem;\n\theight: 2.75rem;\n\tmargin-top: 8px;\n\tpadding: 0;\n\tborder: 1px solid var(--sc-gray-20);\n\tbackground: transparent;\n\tcursor: pointer;\n\tfont-family: var(--sc-font-family);\n\tcolor: var(--sc-gray-80);\n}\n\n.action-set__item:hover,\n.action-set__item--active {\n\tbackground: var(--sc-btn-hover);\n}\n\n.action-set__item-icon {\n\tdisplay: block;\n\twidth: 1rem;\n\theight: 1rem;\n\tflex-shrink: 0;\n}\n\n.action-set__item-fallback {\n\tfont-size: 0.85rem;\n\tfont-weight: 600;\n\tline-height: 1;\n}\n\n.action-set__item-badge {\n\tposition: absolute;\n\ttop: 2px;\n\tright: 2px;\n\tmin-width: 14px;\n\theight: 14px;\n\tpadding: 0 3px;\n\tborder-radius: 7px;\n\tbackground: var(--sc-badge-danger-accent);\n\tcolor: var(--sc-primary-text-color);\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tline-height: 14px;\n\ttext-align: center;\n\tpointer-events: none;\n}\n\n.action-set__drawer {\n\tposition: fixed;\n\ttop: 0;\n\tright: 0;\n\tbottom: 0;\n\tbox-sizing: border-box;\n\twidth: var(--sc-action-set-drawer-width);\n\tpadding-right: var(--action-set-rail-inset);\n\tbackground: var(--sc-form-background);\n\tborder-left: 1px solid var(--sc-gray-20);\n\tdisplay: flex;\n\tflex-direction: column;\n\tz-index: 0;\n}\n\n.action-set__drawer-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-end;\n\tpadding: 8px 12px;\n\tflex-shrink: 0;\n}\n\n.action-set__drawer-close {\n\tflex-shrink: 0;\n\tborder: none;\n\tbackground: transparent;\n\tfont-size: 1.25rem;\n\tline-height: 1;\n\tcursor: pointer;\n\tcolor: var(--sc-gray-60);\n\tpadding: 0.5rem;\n\tmin-width: 2.75rem;\n\tmin-height: 2.75rem;\n}\n\n.action-set__drawer-body {\n\tflex: 1;\n\tmin-height: 0;\n\toverflow: auto;\n}\n\n.action-set__drawer-empty {\n\tpadding: 16px 12px;\n\tmargin: 0;\n\tcolor: var(--sc-gray-60);\n\tfont-style: italic;\n}\n\n.action-set__actions-list {\n\tpadding: 8px;\n}\n\n.action-set__actions-list-item {\n\tdisplay: block;\n\tbox-sizing: border-box;\n\twidth: 100%;\n\tpadding: 10px 12px;\n\tmargin-bottom: 4px;\n\tborder: 1px solid var(--sc-gray-20);\n\tbackground: transparent;\n\ttext-align: left;\n\tfont-size: 0.9rem;\n\tfont-family: var(--sc-font-family);\n\tfont-weight: 500;\n\tcolor: var(--sc-gray-80);\n\ttext-decoration: none;\n\tcursor: pointer;\n}\n\n.action-set__actions-list-item:hover {\n\tbackground: var(--sc-btn-hover);\n}\n\n.action-set__actions-list-item:disabled {\n\topacity: 0.5;\n\tcursor: not-allowed;\n}\n\n.action-set__actions-list-item--nested {\n\tmargin-left: 0;\n\twidth: 100%;\n\tfont-weight: 400;\n}\n\n.action-set__actions-group {\n\tmargin-bottom: 8px;\n}\n\n.action-set__actions-group-label {\n\tmargin: 4px 0 6px;\n\tpadding: 0 2px;\n\tfont-family: var(--sc-font-family);\n\tfont-size: 0.75rem;\n\tfont-weight: 600;\n\tletter-spacing: 0.04em;\n\ttext-transform: uppercase;\n\tcolor: var(--sc-gray-60);\n}\n\n.action-set__actions-empty {\n\tpadding: 16px 12px;\n\tmargin: 0;\n\tcolor: var(--sc-gray-60);\n\tfont-style: italic;\n}\n</style>\n","/** The id of the element SheetNav renders for footer controls, left of its tabs. */\nexport const SHEET_NAV_TOOLBAR_ID = 'sheetnav-toolbar'\n\n/**\n * Teleport target for footer controls rendered anywhere on the page: `<Teleport :to=\"SHEET_NAV_TOOLBAR_SELECTOR\">`.\n * @public\n */\nexport const SHEET_NAV_TOOLBAR_SELECTOR = `#${SHEET_NAV_TOOLBAR_ID}`\n","<template>\n\t<footer>\n\t\t<div class=\"sheetnav-footer-cluster\">\n\t\t\t<div :id=\"SHEET_NAV_TOOLBAR_ID\" class=\"sheetnav-toolbar\">\n\t\t\t\t<slot name=\"toolbar\" />\n\t\t\t</div>\n\t\t\t<ul class=\"tabs\">\n\t\t\t\t<li class=\"hidebreadcrumbs\" @click=\"toggleBreadcrumbs\" @keydown.enter=\"toggleBreadcrumbs\">\n\t\t\t\t\t<a tabindex=\"0\"><div :class=\"rotateHideTabIcon\">×</div></a>\n\t\t\t\t</li>\n\t\t\t\t<li\n\t\t\t\t\tclass=\"hometab\"\n\t\t\t\t\t:style=\"{ display: breadcrumbsVisibile ? 'flex' : 'none' }\"\n\t\t\t\t\t@click=\"navigateHome\"\n\t\t\t\t\t@keydown.enter=\"navigateHome\">\n\t\t\t\t\t<router-link to=\"/\" tabindex=\"0\">\n\t\t\t\t\t\t<svg class=\"icon\" aria-label=\"Home\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n\t\t\t\t\t\t\t<path d=\"M3 12l9-9 9 9\" />\n\t\t\t\t\t\t\t<path d=\"M9 21V12h6v9\" />\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t</router-link>\n\t\t\t\t</li>\n\t\t\t\t<li\n\t\t\t\t\t:class=\"['searchtab', { 'search-active': searchVisible }]\"\n\t\t\t\t\t:style=\"{ display: breadcrumbsVisibile ? 'flex' : 'none' }\">\n\t\t\t\t\t<a tabindex=\"0\">\n\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\tv-show=\"!searchVisible\"\n\t\t\t\t\t\t\tclass=\"icon search-icon\"\n\t\t\t\t\t\t\trole=\"button\"\n\t\t\t\t\t\t\taria-label=\"Search\"\n\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\t\tstroke-width=\"2\"\n\t\t\t\t\t\t\t@click=\"toggleSearch\"\n\t\t\t\t\t\t\t@keydown.enter=\"toggleSearch\">\n\t\t\t\t\t\t\t<circle cx=\"11\" cy=\"11\" r=\"7\" />\n\t\t\t\t\t\t\t<path d=\"M21 21l-4.35-4.35\" />\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tv-show=\"searchVisible\"\n\t\t\t\t\t\t\tref=\"searchinput\"\n\t\t\t\t\t\t\tv-model=\"searchText\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Search...\"\n\t\t\t\t\t\t\t@click.stop\n\t\t\t\t\t\t\t@input=\"handleSearchInput($event)\"\n\t\t\t\t\t\t\t@blur=\"handleSearch($event)\"\n\t\t\t\t\t\t\t@keydown.enter=\"handleSearch($event)\"\n\t\t\t\t\t\t\t@keydown.escape=\"toggleSearch\" />\n\t\t\t\t\t</a>\n\t\t\t\t</li>\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"breadcrumb in breadcrumbs\"\n\t\t\t\t\t:key=\"breadcrumb.title\"\n\t\t\t\t\t:style=\"{ display: breadcrumbsVisibile ? 'flex' : 'none' }\">\n\t\t\t\t\t<router-link tabindex=\"0\" :to=\"breadcrumb.to\"> {{ breadcrumb.title }} </router-link>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</div>\n\t</footer>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue'\n\nimport { SHEET_NAV_TOOLBAR_ID, SHEET_NAV_TOOLBAR_SELECTOR } from '../sheet-nav-toolbar'\n\nconst { breadcrumbs = [] } = defineProps<{ breadcrumbs?: { title: string; to: string }[] }>()\n\nonMounted(() => {\n\tif (document.querySelectorAll(SHEET_NAV_TOOLBAR_SELECTOR).length > 1) {\n\t\tconsole.warn(\n\t\t\t`More than one SheetNav is mounted: content teleported to ${SHEET_NAV_TOOLBAR_SELECTOR} lands in the first one only.`\n\t\t)\n\t}\n})\n\nconst breadcrumbsVisibile = ref(true)\nconst searchVisible = ref(false)\nconst searchText = ref('')\nconst inputRef = useTemplateRef<HTMLInputElement>('searchinput')\n\nconst rotateHideTabIcon = computed(() => {\n\treturn breadcrumbsVisibile.value ? 'unrotated' : 'rotated'\n})\n\nconst toggleBreadcrumbs = () => {\n\tbreadcrumbsVisibile.value = !breadcrumbsVisibile.value\n}\n\nconst toggleSearch = async () => {\n\tsearchVisible.value = !searchVisible.value\n\tawait nextTick(() => {\n\t\tinputRef.value?.focus()\n\t})\n}\n\nconst handleSearchInput = (event: Event | MouseEvent) => {\n\tevent.preventDefault()\n\tevent.stopPropagation()\n}\n\nconst handleSearch = async (event: FocusEvent | KeyboardEvent) => {\n\tevent.preventDefault()\n\tevent.stopPropagation()\n\tawait toggleSearch()\n}\n\nconst navigateHome = (/* event: MouseEvent | KeyboardEvent */) => {\n\t// navigate home\n}\n</script>\n\n<style scoped>\n/* Pinned to both edges: without `left`, a fixed box takes its container's offset and overflows the\n viewport. Only the toolbar and the tabs take pointer events; the empty strip passes clicks through. */\nfooter {\n\tposition: fixed;\n\tright: 0;\n\tbottom: 0;\n\tleft: 0;\n\tpointer-events: none;\n\tbackground-color: transparent;\n\tz-index: 100;\n\ttext-align: left;\n\tfont-size: 100%;\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\talign-items: flex-end;\n\tpadding: 0 0.75rem 0 0;\n\tbox-sizing: border-box;\n}\n\n/* When the toolbar and the tabs do not fit on one row, the toolbar wraps onto its own row above. */\n.sheetnav-footer-cluster {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tjustify-content: flex-end;\n\talign-items: flex-end;\n\tcolumn-gap: 0.5rem;\n\tmax-width: 100%;\n\tmin-width: 0;\n}\n\n.sheetnav-toolbar {\n\tpointer-events: auto;\n\tmin-width: 0;\n\tmin-height: 2.4rem;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tjustify-content: flex-end;\n\talign-items: center;\n}\n\n.tabs {\n\tpointer-events: auto;\n\tflex: 0 0 auto;\n\tdisplay: flex;\n\tflex-direction: row-reverse;\n\talign-items: stretch;\n\theight: 2.4rem;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.tabs li {\n\tdisplay: flex;\n\talign-items: stretch;\n\tlist-style-type: none;\n\tposition: relative;\n\tmargin-left: -1px;\n}\n\n/* Base tab styling */\n.tabs a {\n\theight: 100%;\n\tmin-height: 2.4rem;\n\tpadding: 0 1rem;\n\tbox-sizing: border-box;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\ttext-decoration: none;\n\tcolor: var(--sc-gray-60);\n\tbackground: var(--sc-btn-color);\n\tborder: 1px solid var(--sc-btn-border);\n\tfont-size: 0.85rem;\n\tfont-family: var(--sc-font-family);\n\ttransition: all 0.15s ease;\n\n\t/* Minimal ornamentation - subtle top radius only */\n\tborder-top-left-radius: 2px;\n\tborder-top-right-radius: 2px;\n}\n\n.tabs a:hover {\n\tbackground: var(--sc-btn-hover);\n}\n\n.tabs .router-link-active {\n\tz-index: 3;\n\tbackground: var(--sc-primary-color) !important;\n\tborder-color: var(--sc-primary-color) !important;\n\tcolor: var(--sc-primary-text-color) !important;\n}\n\n/* Pseudo-elements removed - minimal ornamentation style */\n\n/* Hide breadcrumbs tab */\n.hidebreadcrumbs a {\n\tmin-width: 2.4rem;\n\twidth: 2.4rem;\n\tpadding: 0.5rem;\n}\n\n.hidebreadcrumbs a div {\n\tfont-size: 1.2rem;\n}\n\n.rotated {\n\ttransform: rotate(45deg);\n\t-webkit-transform: rotate(45deg);\n\t-moz-transform: rotate(45deg);\n\t-ms-transform: rotate(45deg);\n\t-o-transform: rotate(45deg);\n\ttransition: transform 250ms ease;\n}\n.unrotated {\n\ttransform: rotate(0deg);\n\t-webkit-transform: rotate(0deg);\n\t-moz-transform: rotate(0deg);\n\t-ms-transform: rotate(0deg);\n\t-o-transform: rotate(0deg);\n\ttransition: transform 250ms ease;\n}\n\nli:active,\nli:hover,\nli:focus,\nli > a:active,\nli > a:hover,\nli > a:focus {\n\tz-index: 3;\n}\n\na:active,\na:hover,\na:focus {\n\toutline: 2px solid var(--sc-input-active-border-color);\n\tz-index: 3;\n}\n\n/* Home tab */\n.hometab a {\n\tmin-width: 2.4rem;\n\twidth: 2.4rem;\n\tpadding: 0.5rem;\n}\n\n/* SVG icon styling */\n.icon {\n\twidth: 1rem;\n\theight: 1rem;\n\tstroke: currentColor;\n\tflex-shrink: 0;\n}\n\n/* Search tab with animation */\n.searchtab {\n\toverflow: hidden;\n}\n\n.searchtab a {\n\tmin-width: 2.4rem;\n\tpadding: 0.5rem;\n\toverflow: hidden;\n\t/* Animation for smooth expand/collapse */\n\tmax-width: 2.4rem;\n\ttransition:\n\t\tmax-width 0.35s ease-in-out,\n\t\tpadding 0.35s ease-in-out,\n\t\tbackground 0.2s ease;\n}\n\n.searchtab .search-icon {\n\tcursor: pointer;\n}\n\n.searchtab input {\n\toutline: none;\n\tborder: 1px solid var(--sc-input-border-color);\n\tborder-radius: 0.25rem;\n\tbackground-color: var(--sc-form-background);\n\tcolor: var(--sc-gray-80);\n\ttext-align: left;\n\tfont-size: 0.875rem;\n\tpadding: 0.25rem 0.5rem;\n\twidth: 180px;\n\theight: 1.5rem;\n\tflex-shrink: 0;\n\topacity: 0;\n\ttransition: opacity 0.2s ease-in-out;\n\ttransition-delay: 0s;\n}\n\n.searchtab input:focus {\n\tborder-color: var(--sc-input-active-border-color);\n\toutline: none;\n}\n\n.searchtab input::placeholder {\n\tcolor: var(--sc-input-label-color);\n}\n\n/* Search active state - expanded with animation */\n.searchtab.search-active a {\n\tmax-width: 200px;\n\tmin-width: auto;\n\twidth: auto;\n\tpadding: 0.5rem 0.75rem;\n}\n\n.searchtab.search-active input {\n\topacity: 1;\n\ttransition-delay: 0.15s;\n}\n</style>\n","<template>\n\t<footer>\n\t\t<div class=\"sheetnav-footer-cluster\">\n\t\t\t<div :id=\"SHEET_NAV_TOOLBAR_ID\" class=\"sheetnav-toolbar\">\n\t\t\t\t<slot name=\"toolbar\" />\n\t\t\t</div>\n\t\t\t<ul class=\"tabs\">\n\t\t\t\t<li class=\"hidebreadcrumbs\" @click=\"toggleBreadcrumbs\" @keydown.enter=\"toggleBreadcrumbs\">\n\t\t\t\t\t<a tabindex=\"0\"><div :class=\"rotateHideTabIcon\">×</div></a>\n\t\t\t\t</li>\n\t\t\t\t<li\n\t\t\t\t\tclass=\"hometab\"\n\t\t\t\t\t:style=\"{ display: breadcrumbsVisibile ? 'flex' : 'none' }\"\n\t\t\t\t\t@click=\"navigateHome\"\n\t\t\t\t\t@keydown.enter=\"navigateHome\">\n\t\t\t\t\t<router-link to=\"/\" tabindex=\"0\">\n\t\t\t\t\t\t<svg class=\"icon\" aria-label=\"Home\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n\t\t\t\t\t\t\t<path d=\"M3 12l9-9 9 9\" />\n\t\t\t\t\t\t\t<path d=\"M9 21V12h6v9\" />\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t</router-link>\n\t\t\t\t</li>\n\t\t\t\t<li\n\t\t\t\t\t:class=\"['searchtab', { 'search-active': searchVisible }]\"\n\t\t\t\t\t:style=\"{ display: breadcrumbsVisibile ? 'flex' : 'none' }\">\n\t\t\t\t\t<a tabindex=\"0\">\n\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\tv-show=\"!searchVisible\"\n\t\t\t\t\t\t\tclass=\"icon search-icon\"\n\t\t\t\t\t\t\trole=\"button\"\n\t\t\t\t\t\t\taria-label=\"Search\"\n\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\t\tstroke-width=\"2\"\n\t\t\t\t\t\t\t@click=\"toggleSearch\"\n\t\t\t\t\t\t\t@keydown.enter=\"toggleSearch\">\n\t\t\t\t\t\t\t<circle cx=\"11\" cy=\"11\" r=\"7\" />\n\t\t\t\t\t\t\t<path d=\"M21 21l-4.35-4.35\" />\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tv-show=\"searchVisible\"\n\t\t\t\t\t\t\tref=\"searchinput\"\n\t\t\t\t\t\t\tv-model=\"searchText\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"Search...\"\n\t\t\t\t\t\t\t@click.stop\n\t\t\t\t\t\t\t@input=\"handleSearchInput($event)\"\n\t\t\t\t\t\t\t@blur=\"handleSearch($event)\"\n\t\t\t\t\t\t\t@keydown.enter=\"handleSearch($event)\"\n\t\t\t\t\t\t\t@keydown.escape=\"toggleSearch\" />\n\t\t\t\t\t</a>\n\t\t\t\t</li>\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"breadcrumb in breadcrumbs\"\n\t\t\t\t\t:key=\"breadcrumb.title\"\n\t\t\t\t\t:style=\"{ display: breadcrumbsVisibile ? 'flex' : 'none' }\">\n\t\t\t\t\t<router-link tabindex=\"0\" :to=\"breadcrumb.to\"> {{ breadcrumb.title }} </router-link>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</div>\n\t</footer>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue'\n\nimport { SHEET_NAV_TOOLBAR_ID, SHEET_NAV_TOOLBAR_SELECTOR } from '../sheet-nav-toolbar'\n\nconst { breadcrumbs = [] } = defineProps<{ breadcrumbs?: { title: string; to: string }[] }>()\n\nonMounted(() => {\n\tif (document.querySelectorAll(SHEET_NAV_TOOLBAR_SELECTOR).length > 1) {\n\t\tconsole.warn(\n\t\t\t`More than one SheetNav is mounted: content teleported to ${SHEET_NAV_TOOLBAR_SELECTOR} lands in the first one only.`\n\t\t)\n\t}\n})\n\nconst breadcrumbsVisibile = ref(true)\nconst searchVisible = ref(false)\nconst searchText = ref('')\nconst inputRef = useTemplateRef<HTMLInputElement>('searchinput')\n\nconst rotateHideTabIcon = computed(() => {\n\treturn breadcrumbsVisibile.value ? 'unrotated' : 'rotated'\n})\n\nconst toggleBreadcrumbs = () => {\n\tbreadcrumbsVisibile.value = !breadcrumbsVisibile.value\n}\n\nconst toggleSearch = async () => {\n\tsearchVisible.value = !searchVisible.value\n\tawait nextTick(() => {\n\t\tinputRef.value?.focus()\n\t})\n}\n\nconst handleSearchInput = (event: Event | MouseEvent) => {\n\tevent.preventDefault()\n\tevent.stopPropagation()\n}\n\nconst handleSearch = async (event: FocusEvent | KeyboardEvent) => {\n\tevent.preventDefault()\n\tevent.stopPropagation()\n\tawait toggleSearch()\n}\n\nconst navigateHome = (/* event: MouseEvent | KeyboardEvent */) => {\n\t// navigate home\n}\n</script>\n\n<style scoped>\n/* Pinned to both edges: without `left`, a fixed box takes its container's offset and overflows the\n viewport. Only the toolbar and the tabs take pointer events; the empty strip passes clicks through. */\nfooter {\n\tposition: fixed;\n\tright: 0;\n\tbottom: 0;\n\tleft: 0;\n\tpointer-events: none;\n\tbackground-color: transparent;\n\tz-index: 100;\n\ttext-align: left;\n\tfont-size: 100%;\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\talign-items: flex-end;\n\tpadding: 0 0.75rem 0 0;\n\tbox-sizing: border-box;\n}\n\n/* When the toolbar and the tabs do not fit on one row, the toolbar wraps onto its own row above. */\n.sheetnav-footer-cluster {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tjustify-content: flex-end;\n\talign-items: flex-end;\n\tcolumn-gap: 0.5rem;\n\tmax-width: 100%;\n\tmin-width: 0;\n}\n\n.sheetnav-toolbar {\n\tpointer-events: auto;\n\tmin-width: 0;\n\tmin-height: 2.4rem;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tjustify-content: flex-end;\n\talign-items: center;\n}\n\n.tabs {\n\tpointer-events: auto;\n\tflex: 0 0 auto;\n\tdisplay: flex;\n\tflex-direction: row-reverse;\n\talign-items: stretch;\n\theight: 2.4rem;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.tabs li {\n\tdisplay: flex;\n\talign-items: stretch;\n\tlist-style-type: none;\n\tposition: relative;\n\tmargin-left: -1px;\n}\n\n/* Base tab styling */\n.tabs a {\n\theight: 100%;\n\tmin-height: 2.4rem;\n\tpadding: 0 1rem;\n\tbox-sizing: border-box;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\ttext-decoration: none;\n\tcolor: var(--sc-gray-60);\n\tbackground: var(--sc-btn-color);\n\tborder: 1px solid var(--sc-btn-border);\n\tfont-size: 0.85rem;\n\tfont-family: var(--sc-font-family);\n\ttransition: all 0.15s ease;\n\n\t/* Minimal ornamentation - subtle top radius only */\n\tborder-top-left-radius: 2px;\n\tborder-top-right-radius: 2px;\n}\n\n.tabs a:hover {\n\tbackground: var(--sc-btn-hover);\n}\n\n.tabs .router-link-active {\n\tz-index: 3;\n\tbackground: var(--sc-primary-color) !important;\n\tborder-color: var(--sc-primary-color) !important;\n\tcolor: var(--sc-primary-text-color) !important;\n}\n\n/* Pseudo-elements removed - minimal ornamentation style */\n\n/* Hide breadcrumbs tab */\n.hidebreadcrumbs a {\n\tmin-width: 2.4rem;\n\twidth: 2.4rem;\n\tpadding: 0.5rem;\n}\n\n.hidebreadcrumbs a div {\n\tfont-size: 1.2rem;\n}\n\n.rotated {\n\ttransform: rotate(45deg);\n\t-webkit-transform: rotate(45deg);\n\t-moz-transform: rotate(45deg);\n\t-ms-transform: rotate(45deg);\n\t-o-transform: rotate(45deg);\n\ttransition: transform 250ms ease;\n}\n.unrotated {\n\ttransform: rotate(0deg);\n\t-webkit-transform: rotate(0deg);\n\t-moz-transform: rotate(0deg);\n\t-ms-transform: rotate(0deg);\n\t-o-transform: rotate(0deg);\n\ttransition: transform 250ms ease;\n}\n\nli:active,\nli:hover,\nli:focus,\nli > a:active,\nli > a:hover,\nli > a:focus {\n\tz-index: 3;\n}\n\na:active,\na:hover,\na:focus {\n\toutline: 2px solid var(--sc-input-active-border-color);\n\tz-index: 3;\n}\n\n/* Home tab */\n.hometab a {\n\tmin-width: 2.4rem;\n\twidth: 2.4rem;\n\tpadding: 0.5rem;\n}\n\n/* SVG icon styling */\n.icon {\n\twidth: 1rem;\n\theight: 1rem;\n\tstroke: currentColor;\n\tflex-shrink: 0;\n}\n\n/* Search tab with animation */\n.searchtab {\n\toverflow: hidden;\n}\n\n.searchtab a {\n\tmin-width: 2.4rem;\n\tpadding: 0.5rem;\n\toverflow: hidden;\n\t/* Animation for smooth expand/collapse */\n\tmax-width: 2.4rem;\n\ttransition:\n\t\tmax-width 0.35s ease-in-out,\n\t\tpadding 0.35s ease-in-out,\n\t\tbackground 0.2s ease;\n}\n\n.searchtab .search-icon {\n\tcursor: pointer;\n}\n\n.searchtab input {\n\toutline: none;\n\tborder: 1px solid var(--sc-input-border-color);\n\tborder-radius: 0.25rem;\n\tbackground-color: var(--sc-form-background);\n\tcolor: var(--sc-gray-80);\n\ttext-align: left;\n\tfont-size: 0.875rem;\n\tpadding: 0.25rem 0.5rem;\n\twidth: 180px;\n\theight: 1.5rem;\n\tflex-shrink: 0;\n\topacity: 0;\n\ttransition: opacity 0.2s ease-in-out;\n\ttransition-delay: 0s;\n}\n\n.searchtab input:focus {\n\tborder-color: var(--sc-input-active-border-color);\n\toutline: none;\n}\n\n.searchtab input::placeholder {\n\tcolor: var(--sc-input-label-color);\n}\n\n/* Search active state - expanded with animation */\n.searchtab.search-active a {\n\tmax-width: 200px;\n\tmin-width: auto;\n\twidth: auto;\n\tpadding: 0.5rem 0.75rem;\n}\n\n.searchtab.search-active input {\n\topacity: 1;\n\ttransition-delay: 0.15s;\n}\n</style>\n","import { computed, inject, markRaw, ref, shallowRef, type ComputedRef, type InjectionKey } from 'vue'\n\nimport type { ActionSetContext, ActionSetPreview, ActionSetSlotId } from '../types'\n\nexport type CreateActionSetOptions = {\n\tdoctype: ComputedRef<string>\n\trecordId: ComputedRef<string>\n}\n\n/**\n * The only owner of what the drawer shows. ActionSet and Desktop both read it, so the tile\n * column, the drawer and the workspace margin cannot disagree about whether the drawer is open.\n */\nexport type ActionSetController = ActionSetContext & {\n\tpreviewSubject: ComputedRef<ActionSetPreview | null>\n\tisPreviewOpen: ComputedRef<boolean>\n\tisActionsOpen: ComputedRef<boolean>\n\tisDrawerOpen: ComputedRef<boolean>\n\topenActions: () => void\n\topenSlot: (slotId: ActionSetSlotId) => void\n}\n\nexport const actionSetKey: InjectionKey<ActionSetController> = Symbol('actionSet')\n\nexport function createActionSet(options: CreateActionSetOptions): ActionSetController {\n\tconst activeSlotId = ref<ActionSetSlotId | null>(null)\n\tconst actionsOpen = ref(false)\n\tconst previewSubject = shallowRef<ActionSetPreview | null>(null)\n\tconst previewId = ref<string | undefined>(undefined)\n\n\tfunction openActions() {\n\t\tactiveSlotId.value = null\n\t\tclosePreview()\n\t\tactionsOpen.value = true\n\t}\n\n\tfunction openSlot(slotId: ActionSetSlotId) {\n\t\tactionsOpen.value = false\n\t\tif (activeSlotId.value === slotId) {\n\t\t\treturn\n\t\t}\n\t\tactiveSlotId.value = slotId\n\t\tclosePreview()\n\t}\n\n\tfunction present(subject: ActionSetPreview) {\n\t\tif (subject.id !== undefined && subject.id === previewId.value) {\n\t\t\treturn\n\t\t}\n\t\tpreviewId.value = subject.id\n\t\tpreviewSubject.value = { ...subject, view: markRaw(subject.view) }\n\t}\n\n\tfunction closePreview() {\n\t\tpreviewSubject.value = null\n\t\tpreviewId.value = undefined\n\t}\n\n\tfunction close() {\n\t\tactiveSlotId.value = null\n\t\tactionsOpen.value = false\n\t\tclosePreview()\n\t}\n\n\treturn {\n\t\tdoctype: options.doctype,\n\t\trecordId: options.recordId,\n\t\tactiveSlotId: computed(() => activeSlotId.value),\n\t\tpreviewSubject: computed(() => previewSubject.value),\n\t\tisPreviewOpen: computed(() => previewSubject.value !== null),\n\t\tisActionsOpen: computed(() => actionsOpen.value),\n\t\tisDrawerOpen: computed(() => actionsOpen.value || activeSlotId.value !== null),\n\t\tpresent,\n\t\tclosePreview,\n\t\tclose,\n\t\topenActions,\n\t\topenSlot,\n\t}\n}\n\n/** @public */\nexport function useActionSet(): ActionSetContext {\n\tconst actionSet = inject(actionSetKey, null)\n\tif (!actionSet) {\n\t\tthrow new Error('useActionSet() must be called inside a component rendered by Desktop')\n\t}\n\treturn actionSet\n}\n","<template>\n\t<div\n\t\tclass=\"desktop\"\n\t\t:class=\"{\n\t\t\t'desktop--action-set-open': actionSetDrawerOpen,\n\t\t\t'desktop--preview-open': actionSetPreviewOpen,\n\t\t}\"\n\t\t@click=\"handleClick\">\n\t\t<div class=\"desktop__workspace\">\n\t\t\t<div class=\"desktop__main\">\n\t\t\t\t<slot v-if=\"$slots.default\" />\n\t\t\t\t<AForm\n\t\t\t\t\tv-else-if=\"currentViewSchema.length > 0\"\n\t\t\t\t\tv-model:data=\"currentViewData\"\n\t\t\t\t\t:schema=\"currentViewSchema\"\n\t\t\t\t\t:errors=\"fieldErrors\" />\n\t\t\t\t<div v-else-if=\"!stonecrop\" class=\"loading\"><p>Initializing Stonecrop...</p></div>\n\t\t\t\t<div v-else class=\"loading\">\n\t\t\t\t\t<p>Loading {{ currentView }} data...</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<aside v-if=\"actionSetPreviewSubject\" class=\"desktop__preview\">\n\t\t\t\t<header class=\"desktop__preview-header\">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"desktop__preview-close\"\n\t\t\t\t\t\taria-label=\"Close preview\"\n\t\t\t\t\t\t@click=\"closeActionSetPreview\">\n\t\t\t\t\t\t×\n\t\t\t\t\t</button>\n\t\t\t\t</header>\n\t\t\t\t<div class=\"desktop__preview-body\">\n\t\t\t\t\t<component :is=\"actionSetPreviewSubject.view\" v-bind=\"actionSetPreviewSubject.props ?? {}\" />\n\t\t\t\t</div>\n\t\t\t</aside>\n\t\t</div>\n\n\t\t<ActionSet\n\t\t\t:slots=\"visibleActionSetSlots\"\n\t\t\t:elements=\"actionElements\"\n\t\t\t:controller=\"actionSetController\"\n\t\t\t@action-click=\"handleActionClick\"\n\t\t\t@search=\"commandPaletteOpen = true\" />\n\n\t\t<SheetNav class=\"desktop__sheetnav\" :breadcrumbs=\"navigationBreadcrumbs\">\n\t\t\t<template #toolbar>\n\t\t\t\t<slot name=\"sheetnav-toolbar\" />\n\t\t\t</template>\n\t\t</SheetNav>\n\n\t\t<CommandPalette\n\t\t\t:is-open=\"commandPaletteOpen\"\n\t\t\t:search=\"searchCommands\"\n\t\t\tplaceholder=\"Type a command or search...\"\n\t\t\t@select=\"executeCommand\"\n\t\t\t@close=\"commandPaletteOpen = false\">\n\t\t\t<template #title=\"{ result }\">\n\t\t\t\t{{ result.title }}\n\t\t\t</template>\n\t\t\t<template #content=\"{ result }\">\n\t\t\t\t{{ result.description }}\n\t\t\t</template>\n\t\t</CommandPalette>\n\t</div>\n</template>\n\n<script setup lang=\"ts\">\n// The draft segment comes from @stonecrop/stonecrop rather than being spelled out here: that\n// package guards fetching, field initialization and workflow readiness on the same question, and\n// when the two were written separately they disagreed and every guard over there went dead.\nimport { DRAFT_RECORD_ID, isDraftRecordId, useStonecrop, useValidationStore } from '@stonecrop/stonecrop'\nimport {\n\tAForm,\n\ttype AFormLinkNavigator,\n\tresolvedFieldsToColumns,\n\ttype ResolvedField,\n\ttype ResolvedTable,\n} from '@stonecrop/aform'\nimport { computed, markRaw, onMounted, onUnmounted, provide, ref, unref, watch } from 'vue'\n\nimport ActionSet from './ActionSet.vue'\nimport SheetNav from './SheetNav.vue'\nimport CommandPalette from './CommandPalette.vue'\nimport { createActionSet, actionSetKey } from '../composables/useActionSet'\nimport type {\n\tActionElements,\n\tRouteAdapter,\n\tNavigationTarget,\n\tActionEventPayload,\n\tRecordOpenEventPayload,\n\tLoadRecordsEventPayload,\n\tLoadRecordEventPayload,\n\tActionSetSlot,\n} from '../types'\n\nconst {\n\tavailableDoctypes = [],\n\trouteAdapter,\n\tactionSetSlots,\n\thostActions,\n} = defineProps<{\n\tavailableDoctypes?: string[]\n\t/**\n\t * Pluggable router adapter. When provided, Desktop uses these functions for all\n\t * routing instead of reaching into the registry's internal Vue Router instance.\n\t * Nuxt hosts (or any host with custom route conventions) should supply this.\n\t */\n\trouteAdapter?: RouteAdapter\n\t/** Host drawer slots, each shown as a tile in the ActionSet column. */\n\tactionSetSlots?: ActionSetSlot[]\n\t/** When provided, the Actions drawer lists exactly these, in place of the actions Desktop derives from the doctype. */\n\thostActions?: ActionElements[]\n}>()\n\nconst emit = defineEmits<{\n\t/**\n\t * Fired when the user triggers an FSM transition (action button click).\n\t * The host app is responsible for calling the server, persisting state, etc.\n\t */\n\taction: [payload: ActionEventPayload]\n\t/**\n\t * Fired when Desktop wants to navigate to a different view.\n\t * Also calls routeAdapter.navigate() if an adapter is provided.\n\t */\n\tnavigate: [target: NavigationTarget]\n\t/**\n\t * Fired when the user opens a specific record.\n\t */\n\t'record:open': [payload: RecordOpenEventPayload]\n\t/**\n\t * Fired when Desktop is about to read records for a list view. A notification, not a request:\n\t * Desktop performs the read itself through `Stonecrop.getRecords`. A host that fetches here\n\t * races that read into the same HST key.\n\t */\n\t'load-records': [payload: LoadRecordsEventPayload]\n\t/**\n\t * Fired when Desktop is about to read a single record for a form view. A notification, not a\n\t * request — see `load-records`. Not emitted for a draft, which has nothing to fetch.\n\t */\n\t'load-record': [payload: LoadRecordEventPayload]\n}>()\n\nconst { stonecrop } = useStonecrop()\n\n// Field-validation store (advisory, client-side). Pinia is a declared peerDependency, kept external\n// in this package's build (rollupOptions), so `useValidationStore()` resolves the host app's single\n// active Pinia directly — the old `getCurrentInstance().$pinia` workaround for the bundled-Pinia bug\n// is no longer needed. Still guarded: validation is optional and Desktop predates it, so a host that\n// mounts Desktop without Pinia disables validation gracefully instead of crashing on mount.\nlet validationStore: ReturnType<typeof useValidationStore> | null = null\ntry {\n\tvalidationStore = useValidationStore()\n} catch {\n\tvalidationStore = null\n}\n\n// Inline field errors handed to AForm. Only surfaced in the record form view (currentView is\n// defined below); empty elsewhere so a previous record's errors never bleed into a list view.\nconst fieldErrors = computed<Record<string, string[]>>(() =>\n\tcurrentView.value === 'record' ? (validationStore?.errorsByField ?? {}) : {}\n)\n\n// State\nconst loading = ref(false)\nconst commandPaletteOpen = ref(false)\n\n// The record being composed on a `/{doctype}/new` route. Deliberately not in HST: a draft has no\n// identity to be keyed by, and both ways of faking one fail — see `DRAFT_RECORD_ID`.\nconst draftRecord = ref<Record<string, any>>({})\n\n// Form/list data management — each view produces a different data shape.\n// List views (doctypes, records) return table row data keyed by fieldname.\n// Record view returns the record's fields for two-way binding — from HST, or from `draftRecord`\n// when the route is a draft.\nconst currentViewData = computed<Record<string, any>>({\n\tget() {\n\t\t// Doctypes list — rows come from availableDoctypes prop (reactive via availableDoctypes)\n\t\tif (currentView.value === 'doctypes') {\n\t\t\treturn {\n\t\t\t\tdoctypes_table:\n\t\t\t\t\tavailableDoctypes?.map(doctype => ({\n\t\t\t\t\t\tid: doctype,\n\t\t\t\t\t\tdoctype,\n\t\t\t\t\t\tdisplay_name: formatDoctypeName(doctype),\n\t\t\t\t\t\tactions: 'View Records',\n\t\t\t\t\t})) ?? [],\n\t\t\t}\n\t\t}\n\n\t\t// Records list — rows come from HST store (reactive because HST is Vue reactive())\n\t\tif (currentView.value === 'records') {\n\t\t\treturn {\n\t\t\t\trecords_table: getRecords().map(record =>\n\t\t\t\t\tObject.assign({}, record, {\n\t\t\t\t\t\tid: resolveRecordId(record) ?? '',\n\t\t\t\t\t\t// A list row is navigation. Actions live on the record view, where the Actions\n\t\t\t\t\t\t// dropdown is built from what the doctype declares and what the record's\n\t\t\t\t\t\t// current state allows. This cell used to also offer Delete, which dispatched\n\t\t\t\t\t\t// an action named `DELETE` that Desktop invented — no doctype in a\n\t\t\t\t\t\t// WorkflowMeta app declares it, so it failed on every click. Removal is a\n\t\t\t\t\t\t// workflow outcome (`archive`, `cancel`) and belongs in the doctype.\n\t\t\t\t\t\tactions: 'Edit',\n\t\t\t\t\t})\n\t\t\t\t),\n\t\t\t}\n\t\t}\n\n\t\t// Record form — read single record from HST\n\t\tif (!stonecrop.value || !currentDoctype.value || !currentRecordId.value) {\n\t\t\treturn {}\n\t\t}\n\n\t\ttry {\n\t\t\tconst source = isNewRecord.value\n\t\t\t\t? draftRecord.value\n\t\t\t\t: (stonecrop.value.getRecordById(currentDoctype.value, currentRecordId.value)?.get('') as\n\t\t\t\t\t\t| Record<string, any>\n\t\t\t\t\t\t| undefined)\n\t\t\t// Return a plain shallow copy so AForm mutations don't propagate directly into\n\t\t\t// the HST reactive object, which would bypass field-trigger diffing and cause\n\t\t\t// setupDeepReactivity to fire triggers for all fields on every keystroke.\n\t\t\tconst flat: Record<string, any> = { ...source }\n\n\t\t\t// AFieldset receives data[fieldsetFieldname] as its data prop, so the fieldset's\n\t\t\t// children must be grouped under the fieldset key. The server returns flat SQL rows,\n\t\t\t// so we nest fieldset children here before AForm renders them.\n\t\t\tconst doctype = stonecrop.value.registry.registry[currentDoctype.value]\n\t\t\tif (doctype) {\n\t\t\t\tfor (const field of doctype.getSchemaArray()) {\n\t\t\t\t\tif (field.kind === 'fieldset') {\n\t\t\t\t\t\tconst nested: Record<string, any> = {}\n\t\t\t\t\t\tfor (const child of field.schema) {\n\t\t\t\t\t\t\tif (child.fieldname) nested[child.fieldname] = flat[child.fieldname]\n\t\t\t\t\t\t}\n\t\t\t\t\t\tflat[field.fieldname] = nested\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn flat\n\t\t} catch {\n\t\t\treturn {}\n\t\t}\n\t},\n\tset(newData: Record<string, any>) {\n\t\t// List views are read-only from AForm's perspective — HST writes only apply to record form\n\t\tif (currentView.value !== 'record') return\n\t\tif (!stonecrop.value || !currentDoctype.value || !currentRecordId.value) {\n\t\t\treturn\n\t\t}\n\n\t\ttry {\n\t\t\t// AForm emits nested data for fieldsets: { fieldsetKey: { childA: val } }.\n\t\t\t// HST and the server both expect flat rows, so flatten fieldset values back before writing.\n\t\t\tconst doctype = stonecrop.value.registry.registry[currentDoctype.value]\n\t\t\tconst fieldsetNames = new Set<string>()\n\t\t\tif (doctype) {\n\t\t\t\tfor (const field of doctype.getSchemaArray()) {\n\t\t\t\t\tif (field.kind === 'fieldset') {\n\t\t\t\t\t\tfieldsetNames.add(field.fieldname)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Two-pass flatten: non-fieldset keys first, then fieldset children.\n\t\t\t// Fieldset children must be applied last — AForm may emit stale flat copies\n\t\t\t// of fieldset children alongside the updated nested value, and the nested\n\t\t\t// value must win regardless of key insertion order.\n\t\t\tconst flatData: Record<string, any> = {}\n\t\t\tconst fieldsetValues: Record<string, any>[] = []\n\t\t\tfor (const [key, value] of Object.entries(newData)) {\n\t\t\t\tif (fieldsetNames.has(key) && value && typeof value === 'object' && !Array.isArray(value)) {\n\t\t\t\t\tfieldsetValues.push(value)\n\t\t\t\t} else {\n\t\t\t\t\tflatData[key] = value\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const nestedValue of fieldsetValues) {\n\t\t\t\tObject.assign(flatData, nestedValue)\n\t\t\t}\n\n\t\t\t// Only update fields that actually changed. Never write undefined — AForm may emit\n\t\t\t// schema fields absent from the record as undefined; writing them would silently\n\t\t\t// clear values that exist. Explicit null is allowed (intentional clear).\n\t\t\tconst changedFields: string[] = []\n\t\t\tif (isNewRecord.value) {\n\t\t\t\t// Reassigned, not mutated, so the getter re-runs. Relying on in-place mutation of the\n\t\t\t\t// cached object is what made a draft's edits vanish on any invalidation.\n\t\t\t\tconst next = { ...draftRecord.value }\n\t\t\t\tfor (const [fieldname, value] of Object.entries(flatData)) {\n\t\t\t\t\tif (value === undefined) continue\n\t\t\t\t\tif (next[fieldname] !== value) {\n\t\t\t\t\t\tnext[fieldname] = value\n\t\t\t\t\t\tchangedFields.push(fieldname)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdraftRecord.value = next\n\t\t\t} else {\n\t\t\t\tconst hstStore = stonecrop.value.getStore()\n\t\t\t\tfor (const [fieldname, value] of Object.entries(flatData)) {\n\t\t\t\t\tif (value === undefined) continue\n\t\t\t\t\tconst fieldPath = `${currentDoctype.value}.${currentRecordId.value}.${fieldname}`\n\t\t\t\t\tconst currentValue = hstStore.has(fieldPath) ? hstStore.get(fieldPath) : undefined\n\t\t\t\t\tif (currentValue !== value) {\n\t\t\t\t\t\thstStore.set(fieldPath, value)\n\t\t\t\t\t\tchangedFields.push(fieldname)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Advisory field-validation runs on the fields that actually changed (honors each\n\t\t\t// trigger's `on` set). Driven here, after the HST writes, so HST stays value-only.\n\t\t\tif (changedFields.length > 0) {\n\t\t\t\tdriveFieldValidation(changedFields)\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.warn('HST update failed:', error)\n\t\t}\n\t},\n})\n\n// Advisory field-validation: run the doctype's triggers for the fields that changed on this edit.\n// Snapshots the post-edit record as a plain, flat object so validators read siblings read-only\n// (the core store freezes it). Fire-and-forget — the reactive error store repaints AForm on resolve.\nfunction driveFieldValidation(changedFields: string[]) {\n\tif (!validationStore || !stonecrop.value || !currentDoctype.value || !currentRecordId.value) return\n\n\tconst doctype = stonecrop.value.registry.getDoctype(currentDoctype.value)\n\tconst triggers = doctype?.getTriggers()\n\tif (!triggers || Object.keys(triggers).length === 0) return\n\n\t// A draft's siblings come from the buffer; reading HST would hand every validator an empty record.\n\tconst record = isNewRecord.value\n\t\t? { ...draftRecord.value }\n\t\t: {\n\t\t\t\t...(stonecrop.value.getRecordById(currentDoctype.value, currentRecordId.value)?.get('') as Record<\n\t\t\t\t\tstring,\n\t\t\t\t\tunknown\n\t\t\t\t>),\n\t\t\t}\n\n\tfor (const field of changedFields) {\n\t\tvoid validationStore.validateField(triggers, field, record)\n\t}\n}\n\n// Computed properties for current route context.\n// When a routeAdapter is provided it takes full precedence over the registry's internal router.\nconst route = computed(() => (routeAdapter ? null : unref(stonecrop.value?.registry.router?.currentRoute)))\nconst router = computed(() => (routeAdapter ? null : stonecrop.value?.registry.router))\nconst currentDoctype = computed(() => {\n\tif (routeAdapter) return routeAdapter.getCurrentDoctype()\n\tif (!route.value) return ''\n\n\t// First check if we have actualDoctype in meta (from registered routes)\n\tif (route.value.meta?.actualDoctype) {\n\t\treturn route.value.meta.actualDoctype as string\n\t}\n\n\t// For named routes, use params.doctype\n\tif (route.value.params.doctype) {\n\t\treturn route.value.params.doctype.toString()\n\t}\n\n\t// For catch-all routes that haven't been registered yet, extract from path\n\tconst pathMatch = route.value.params.pathMatch as string[] | undefined\n\tif (pathMatch && pathMatch.length > 0) {\n\t\treturn pathMatch[0]\n\t}\n\n\treturn ''\n})\n\n// The route doctype for display and navigation (e.g., 'todo')\nconst routeDoctype = computed(() => {\n\tif (routeAdapter) return routeAdapter.getCurrentDoctype()\n\tif (!route.value) return ''\n\n\t// Check route meta first\n\tif (route.value.meta?.doctype) {\n\t\treturn route.value.meta.doctype as string\n\t}\n\n\t// For named routes, use params.doctype\n\tif (route.value.params.doctype) {\n\t\treturn route.value.params.doctype.toString()\n\t}\n\n\t// For catch-all routes, extract from path\n\tconst pathMatch = route.value.params.pathMatch as string[] | undefined\n\tif (pathMatch && pathMatch.length > 0) {\n\t\treturn pathMatch[0]\n\t}\n\n\treturn ''\n})\n\nconst currentRecordId = computed(() => {\n\tif (routeAdapter) return routeAdapter.getCurrentRecordId()\n\tif (!route.value) return ''\n\n\t// For named routes, use params.recordId\n\tif (route.value.params.recordId) {\n\t\treturn route.value.params.recordId.toString()\n\t}\n\n\t// For catch-all routes that haven't been registered yet, extract from path\n\tconst pathMatch = route.value.params.pathMatch as string[] | undefined\n\tif (pathMatch && pathMatch.length > 1) {\n\t\treturn pathMatch[1]\n\t}\n\n\treturn ''\n})\nconst isNewRecord = computed(() => isDraftRecordId(currentRecordId.value))\n\n// Determine current view based on route\nconst currentView = computed(() => {\n\tif (routeAdapter) return routeAdapter.getCurrentView()\n\tif (!route.value) {\n\t\treturn 'doctypes'\n\t}\n\n\t// Home route\n\tif (route.value.name === 'home' || route.value.path === '/') {\n\t\treturn 'doctypes'\n\t}\n\n\t// Named routes from registered doctypes\n\tif (route.value.name && route.value.name !== 'catch-all') {\n\t\tconst routeName = route.value.name as string\n\t\tif (routeName.includes('form') || route.value.params.recordId) {\n\t\t\treturn 'record'\n\t\t} else if (routeName.includes('list') || route.value.params.doctype) {\n\t\t\treturn 'records'\n\t\t}\n\t}\n\n\t// Catch-all route - determine from path structure\n\tconst pathMatch = route.value.params.pathMatch as string[] | undefined\n\tif (pathMatch && pathMatch.length > 0) {\n\t\tconst view = pathMatch.length === 1 ? 'records' : 'record'\n\t\treturn view\n\t}\n\n\treturn 'doctypes'\n})\n\n// Computed properties (now that all helper functions are defined)\n// Helper function to get available transitions for current record.\n// Reads the actual FSM state from the record's `status` field (or falls back to the\n// workflow initial state) so the available action buttons always reflect reality.\nconst getAvailableTransitions = () => {\n\tif (!stonecrop.value || !currentDoctype.value || !currentRecordId.value) {\n\t\treturn []\n\t}\n\n\ttry {\n\t\tconst doctype = stonecrop.value.registry.getDoctype(currentDoctype.value)\n\t\tif (!doctype?.workflow) return []\n\n\t\t// Delegate state resolution to Stonecrop — reads record 'status', falls back to workflow.initial\n\t\tconst currentState = stonecrop.value.getRecordState(currentDoctype.value, currentRecordId.value)\n\n\t\t// Delegate transition lookup to Doctype — no more manual workflow introspection\n\t\tconst transitions = doctype.getAvailableTransitions(currentState)\n\n\t\tconst recordData = currentViewData.value || {}\n\n\t\t// Each transition emits an 'action' event. The host app decides what to do\n\t\t// (call the server, trigger an FSM actor, update HST, etc.).\n\t\treturn transitions.map(({ name }) => ({\n\t\t\t// Prefer the workflow action's human-readable label (WorkflowMeta format);\n\t\t\t// fall back to the raw transition name for XState workflows with no action meta.\n\t\t\tlabel: doctype.getActionMeta(name)?.label ?? name,\n\t\t\taction: () => {\n\t\t\t\temit('action', {\n\t\t\t\t\tname,\n\t\t\t\t\tdoctype: currentDoctype.value,\n\t\t\t\t\trecordId: currentRecordId.value,\n\t\t\t\t\tdata: recordData,\n\t\t\t\t})\n\t\t\t},\n\t\t}))\n\t} catch (error) {\n\t\tconsole.warn('Error getting available transitions:', error)\n\t\treturn []\n\t}\n}\n\n// Helper: stateless Commands available for the current record — side-effect actions that\n// change no workflow state. Surfaced in the same Actions dropdown as transitions; each emits\n// the same 'action' event, so the host's handler runs a Command's clientHandler identically.\nconst getAvailableCommands = () => {\n\tif (!stonecrop.value || !currentDoctype.value || !currentRecordId.value) {\n\t\treturn []\n\t}\n\n\ttry {\n\t\tconst doctype = stonecrop.value.registry.getDoctype(currentDoctype.value)\n\t\tif (!doctype?.workflow) return []\n\n\t\tconst currentState = stonecrop.value.getRecordState(currentDoctype.value, currentRecordId.value)\n\t\tconst commands = doctype.getAvailableCommands(currentState)\n\t\tconst recordData = currentViewData.value || {}\n\n\t\treturn commands.map(({ name }) => ({\n\t\t\tlabel: doctype.getActionMeta(name)?.label ?? name,\n\t\t\taction: () => {\n\t\t\t\temit('action', {\n\t\t\t\t\tname,\n\t\t\t\t\tdoctype: currentDoctype.value,\n\t\t\t\t\trecordId: currentRecordId.value,\n\t\t\t\t\tdata: recordData,\n\t\t\t\t})\n\t\t\t},\n\t\t}))\n\t} catch (error) {\n\t\tconsole.warn('Error getting available commands:', error)\n\t\treturn []\n\t}\n}\n\nconst actionElements = computed(() => {\n\tif (hostActions) {\n\t\treturn hostActions\n\t}\n\n\tconst elements: ActionElements[] = []\n\n\tswitch (currentView.value) {\n\t\tcase 'records':\n\t\t\telements.push({\n\t\t\t\ttype: 'button',\n\t\t\t\tlabel: 'New Record',\n\t\t\t\taction: () => void createNewRecord(),\n\t\t\t})\n\t\t\tbreak\n\t\tcase 'record': {\n\t\t\t// Populate the Actions dropdown with every FSM transition AND stateless Command\n\t\t\t// available in the record's current state. Clicking either emits 'action'.\n\t\t\tconst recordActions = [...getAvailableTransitions(), ...getAvailableCommands()]\n\t\t\tif (recordActions.length > 0) {\n\t\t\t\telements.push({\n\t\t\t\t\ttype: 'dropdown',\n\t\t\t\t\tlabel: 'Actions',\n\t\t\t\t\tactions: recordActions,\n\t\t\t\t})\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn elements\n})\n\nconst navigationBreadcrumbs = computed(() => {\n\tconst breadcrumbs: { title: string; to: string }[] = []\n\n\tif (currentView.value === 'records' && routeDoctype.value) {\n\t\tbreadcrumbs.push(\n\t\t\t{ title: 'Home', to: '/' },\n\t\t\t{ title: formatDoctypeName(routeDoctype.value), to: `/${routeDoctype.value}` }\n\t\t)\n\t} else if (currentView.value === 'record' && routeDoctype.value) {\n\t\tconst recordPath = currentRecordId.value\n\t\t\t? `/${routeDoctype.value}/${currentRecordId.value}`\n\t\t\t: (route.value?.fullPath ?? '')\n\t\tbreadcrumbs.push(\n\t\t\t{ title: 'Home', to: '/' },\n\t\t\t{ title: formatDoctypeName(routeDoctype.value), to: `/${routeDoctype.value}` },\n\t\t\t{ title: isNewRecord.value ? 'New Record' : 'Edit Record', to: recordPath }\n\t\t)\n\t}\n\n\treturn breadcrumbs\n})\n\n// Command palette functionality\ntype Command = {\n\ttitle: string\n\tdescription: string\n\taction: () => void\n}\n\nconst searchCommands = (query: string): Command[] => {\n\tconst commands: Command[] = [\n\t\t{\n\t\t\ttitle: 'Go Home',\n\t\t\tdescription: 'Navigate to the home page',\n\t\t\taction: () => void doNavigate({ view: 'doctypes' }),\n\t\t},\n\t\t{\n\t\t\ttitle: 'Toggle Command Palette',\n\t\t\tdescription: 'Open/close the command palette',\n\t\t\taction: () => (commandPaletteOpen.value = !commandPaletteOpen.value),\n\t\t},\n\t]\n\n\t// Add doctype-specific commands\n\tif (routeDoctype.value) {\n\t\tcommands.push({\n\t\t\ttitle: `View ${formatDoctypeName(routeDoctype.value)} Records`,\n\t\t\tdescription: `Navigate to ${routeDoctype.value} list`,\n\t\t\taction: () => void doNavigate({ view: 'records', doctype: routeDoctype.value }),\n\t\t})\n\n\t\tcommands.push({\n\t\t\ttitle: `Create New ${formatDoctypeName(routeDoctype.value)}`,\n\t\t\tdescription: `Create a new ${routeDoctype.value} record`,\n\t\t\taction: () => void createNewRecord(),\n\t\t})\n\t}\n\n\t// Add available doctypes as commands\n\tavailableDoctypes.forEach(doctype => {\n\t\tcommands.push({\n\t\t\ttitle: `View ${formatDoctypeName(doctype)}`,\n\t\t\tdescription: `Navigate to ${doctype} list`,\n\t\t\taction: () => void doNavigate({ view: 'records', doctype }),\n\t\t})\n\t})\n\n\t// Filter commands based on query\n\tif (!query) return commands\n\n\treturn commands.filter(\n\t\tcmd =>\n\t\t\tcmd.title.toLowerCase().includes(query.toLowerCase()) ||\n\t\t\tcmd.description.toLowerCase().includes(query.toLowerCase())\n\t)\n}\n\nconst executeCommand = (command: Command) => {\n\tcommand.action()\n\tcommandPaletteOpen.value = false\n}\n\n// List reads are wired on the records table; ATable owns the fetch via getRecords.\nconst listRecordsFetcher = (options?: import('@stonecrop/schema').GetRecordsOptions) => {\n\tif (!stonecrop.value || !currentDoctype.value) {\n\t\treturn Promise.resolve({ data: [], hasMore: false })\n\t}\n\tconst doctype = stonecrop.value.registry.getDoctype(currentDoctype.value)\n\tif (!doctype) {\n\t\treturn Promise.resolve({ data: [], hasMore: false })\n\t}\n\treturn stonecrop.value.getRecords(doctype, options)\n}\n\n// Helper functions - moved here to avoid \"before initialization\" errors\nconst formatDoctypeName = (doctype: string): string => {\n\treturn doctype\n\t\t.split('-')\n\t\t.map(word => word.charAt(0).toUpperCase() + word.slice(1))\n\t\t.join(' ')\n}\n\n// Internal navigation helper: emits 'navigate', then calls the adapter (if any)\n// or falls back to the registry's Vue Router instance.\nconst doNavigate = async (target: NavigationTarget) => {\n\temit('navigate', target)\n\tif (routeAdapter) {\n\t\tawait routeAdapter.navigate(target)\n\t} else {\n\t\tif (target.view === 'doctypes') {\n\t\t\tawait router.value?.push('/')\n\t\t} else if (target.view === 'records' && target.doctype) {\n\t\t\tawait router.value?.push(`/${target.doctype}`)\n\t\t} else if (target.view === 'record' && target.doctype && target.recordId) {\n\t\t\tawait router.value?.push(`/${target.doctype}/${target.recordId}`)\n\t\t}\n\t}\n}\n\nconst navigateToDoctype = async (doctype: string) => {\n\tawait doNavigate({ view: 'records', doctype })\n}\n\nconst openRecord = async (recordId: string) => {\n\tconst doctype = routeDoctype.value\n\temit('record:open', { doctype, recordId })\n\tawait doNavigate({ view: 'record', doctype, recordId })\n}\n\nconst createNewRecord = async () => {\n\tawait doNavigate({ view: 'record', doctype: routeDoctype.value, recordId: DRAFT_RECORD_ID })\n}\n\n// Schema generator functions - moved here to be available to computed properties\nconst getDoctypesSchema = (): ResolvedField[] => {\n\tif (!availableDoctypes?.length) return []\n\n\treturn [\n\t\t{\n\t\t\tkind: 'table' as const,\n\t\t\tfieldname: 'doctypes_table',\n\t\t\tcomponent: 'ATable',\n\t\t\tlabel: 'Doctypes',\n\t\t\tschema: [\n\t\t\t\t{\n\t\t\t\t\tfieldname: 'doctype',\n\t\t\t\t\tlabel: 'Doctype',\n\t\t\t\t\tcomponent: 'ATextInput',\n\t\t\t\t\talign: 'left' as const,\n\t\t\t\t\tedit: false,\n\t\t\t\t\twidth: '20ch',\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tfieldname: 'display_name',\n\t\t\t\t\tlabel: 'Name',\n\t\t\t\t\tcomponent: 'ATextInput',\n\t\t\t\t\talign: 'left' as const,\n\t\t\t\t\tedit: false,\n\t\t\t\t\twidth: '30ch',\n\t\t\t\t},\n\t\t\t\t// No record count column. It read `getRecordIds(doctype).length`, which is how many\n\t\t\t\t// records HST happens to hold — zero for a doctype never opened, and the page size\n\t\t\t\t// for one that was. The real total belongs to the backend and Desktop never asks\n\t\t\t\t// for it, so this shell cannot answer it. Same reason the `recordIdField` prop went.\n\t\t\t\t{\n\t\t\t\t\tfieldname: 'actions',\n\t\t\t\t\tlabel: 'Actions',\n\t\t\t\t\tcomponent: 'ATextInput',\n\t\t\t\t\talign: 'center' as const,\n\t\t\t\t\tedit: false,\n\t\t\t\t\twidth: '20ch',\n\t\t\t\t},\n\t\t\t],\n\t\t\tconfig: { view: 'list' as const, fullWidth: true },\n\t\t} satisfies ResolvedTable,\n\t]\n}\n\nconst getRecordsSchema = (): ResolvedField[] => {\n\tif (!currentDoctype.value) return []\n\tif (!stonecrop.value) return []\n\n\tconst registry = stonecrop.value.registry\n\tconst doctype = registry.registry[currentDoctype.value]\n\n\tif (!doctype) return []\n\n\tconst schema = registry.resolveSchema(doctype)\n\n\t// If no schema is available, let the template fallback handle the loading state\n\tif (schema.length === 0) return []\n\n\tconst doctypeSlug = currentDoctype.value\n\tconst clientConfigured = Boolean(stonecrop.value.getClient())\n\n\treturn [\n\t\t{\n\t\t\tkind: 'table' as const,\n\t\t\tfieldname: 'records_table',\n\t\t\tcomponent: 'ATable',\n\t\t\t// Which resolved fields a cell can render is answered once, in @stonecrop/aform, and\n\t\t\t// called by the child-table builder too. Flattening alone is not the rule: it kept the\n\t\t\t// expanding links, whose value is a nested record or an array of them.\n\t\t\tschema: [...resolvedFieldsToColumns(schema), { fieldname: 'actions', label: 'Actions', component: 'ATextInput' }],\n\t\t\tconfig: { view: 'list' as const, fullWidth: true },\n\t\t\t...(clientConfigured ? { getRecords: listRecordsFetcher, sourceKey: doctypeSlug } : {}),\n\t\t} satisfies ResolvedTable,\n\t]\n}\n\nconst getRecordFormSchema = (): ResolvedField[] => {\n\tif (!currentDoctype.value) return []\n\tif (!stonecrop.value) return []\n\n\ttry {\n\t\tconst registry = stonecrop.value?.registry\n\t\tconst doctype = registry?.registry[currentDoctype.value]\n\n\t\tif (!doctype?.schema) {\n\t\t\t// Let the template fallback handle the loading state\n\t\t\treturn []\n\t\t}\n\n\t\treturn registry.resolveSchema(doctype)\n\t} catch {\n\t\treturn []\n\t}\n}\n\n// Additional data helper functions\nconst getRecords = () => {\n\tif (!stonecrop.value || !currentDoctype.value) {\n\t\treturn []\n\t}\n\n\tconst recordsNode = stonecrop.value.records(currentDoctype.value)\n\tconst recordsData = recordsNode?.get('')\n\n\tif (recordsData && typeof recordsData === 'object' && !Array.isArray(recordsData)) {\n\t\treturn Object.values(recordsData as Record<string, any>)\n\t}\n\n\treturn []\n}\n\n// Schema for different views - defined here after all helper functions are available\nconst currentViewSchema = computed(() => {\n\tswitch (currentView.value) {\n\t\tcase 'doctypes':\n\t\t\treturn getDoctypesSchema()\n\t\tcase 'records':\n\t\t\treturn getRecordsSchema()\n\t\tcase 'record':\n\t\t\treturn getRecordFormSchema()\n\t\tdefault:\n\t\t\treturn []\n\t}\n})\n\nconst handleActionClick = (_label: string, action: (() => void | Promise<void>) | undefined) => {\n\tif (action) {\n\t\tvoid action()\n\t}\n}\n\n/**\n * Resolve a record's identity for links and navigation.\n *\n * Identity is declared once, on the doctype: `primaryKey`, or `id` when nothing is declared.\n * Delegating to `Doctype.getRecordId` is what guarantees this matches the key\n * `Stonecrop.getRecords` stored the record under — resolving it independently here would let a\n * row render a link to an HST path that does not exist.\n *\n * There is deliberately no per-shell override. Identity is a per-doctype fact and one shell\n * renders many doctypes, so a single prop cannot answer it; a shell that named a field would\n * also be overriding the very declaration the store keyed on, which is the bug above.\n */\nconst resolveRecordId = (record: Record<string, unknown>): string | undefined => {\n\tif (!stonecrop.value || !currentDoctype.value) return undefined\n\treturn stonecrop.value.registry.registry[currentDoctype.value]?.getRecordId(record)\n}\n\n// Event handlers\nconst getRecordIdFromRow = (rowElement: HTMLTableRowElement): string | null => {\n\tconst cell = rowElement.querySelector('td[data-rowindex]')\n\tif (!cell) return null\n\n\tconst rowIndexAttr = cell.getAttribute('data-rowindex')\n\tif (rowIndexAttr === null) return null\n\n\tconst rowIndex = parseInt(rowIndexAttr, 10)\n\tif (isNaN(rowIndex)) return null\n\n\tconst records = getRecords()\n\tconst record = records[rowIndex]\n\tif (!record) return null\n\n\treturn resolveRecordId(record) ?? null\n}\n\nconst handleClick = async (event: Event) => {\n\tconst target = event.target as HTMLElement\n\tconst action = target.getAttribute('data-action')\n\n\tif (action === 'create') {\n\t\tawait createNewRecord()\n\t}\n\n\tconst cell = target.closest('td, th')\n\tif (cell) {\n\t\tconst cellText = cell.textContent?.trim()\n\t\tconst row = cell.closest('tr')\n\n\t\tif (cellText === 'View Records' && row) {\n\t\t\t// Get the doctype from the row data\n\t\t\tconst cells = row.querySelectorAll('td')\n\t\t\tif (cells.length > 0) {\n\t\t\t\tconst doctypeCell = cells[1] // Assuming doctype is in second column (first column is index)\n\t\t\t\tconst doctype = doctypeCell.textContent?.trim()\n\t\t\t\tif (doctype) {\n\t\t\t\t\tawait navigateToDoctype(doctype)\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (cellText === 'Edit' && row) {\n\t\t\t// Matched exactly, not by substring. This handler is bound to the whole desktop, so a\n\t\t\t// substring match fired on any cell whose *data* happened to contain the word — a task\n\t\t\t// titled \"Delete old backups\" popped a delete confirmation when you clicked it.\n\t\t\tconst recordId = getRecordIdFromRow(row)\n\t\t\tif (recordId) {\n\t\t\t\tawait openRecord(recordId)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Reads go through Stonecrop, which owns whether to fetch, how to key the result, and where to\n// put it. Desktop asks for data and renders what arrives; it decides none of that itself.\n//\n// Both loaders are no-ops without a client, so a host that populates HST some other way keeps\n// working unchanged rather than taking a thrown error on every navigation.\nconst loadRecordData = async () => {\n\tif (!stonecrop.value || !currentDoctype.value || !stonecrop.value.getClient()) return\n\n\tloading.value = true\n\ttry {\n\t\tawait stonecrop.value.getRecord(currentDoctype.value, currentRecordId.value)\n\t} catch (error) {\n\t\tconsole.warn('Error fetching record:', error)\n\t} finally {\n\t\tloading.value = false\n\t}\n}\n\n// Watch for route changes to load appropriate data\nwatch(\n\t[currentView, currentDoctype, currentRecordId],\n\t() => {\n\t\t// The events are notifications, not fetch requests: they announce what Desktop is about to\n\t\t// read so a host can hang analytics or a prefetch off them. The read itself is Stonecrop's.\n\t\tif (currentView.value === 'records' && currentDoctype.value) {\n\t\t\temit('load-records', { doctype: currentDoctype.value })\n\t\t} else if (currentView.value === 'record' && currentDoctype.value && currentRecordId.value) {\n\t\t\t// A draft has nothing to fetch — the record does not exist on the server yet. Desktop\n\t\t\t// used to emit anyway and leave the host to work it out, which meant every host had to\n\t\t\t// recognise the private draft-id scheme above just to suppress a doomed request; all of\n\t\t\t// them did, identically. `getRecord` declines the same case for the same reason.\n\t\t\tif (isNewRecord.value) return\n\n\t\t\temit('load-record', { doctype: currentDoctype.value, recordId: currentRecordId.value })\n\t\t\tvoid loadRecordData()\n\t\t}\n\t},\n\t{ immediate: true }\n)\n\n// Clear advisory validation errors when the target record changes, so errors from a previously\n// edited record never bleed into a newly opened one.\nwatch([currentDoctype, currentRecordId], () => {\n\tvalidationStore?.clearAll()\n})\n\n// Seeding on entry gives a new record the doctype's declared defaults, which it never used to get.\n// Discarding on exit matters as much: the draft segment is one shared literal, so a stale buffer\n// would open the next New Record pre-filled with the abandoned one's values.\nwatch(\n\t[currentDoctype, currentRecordId],\n\t() => {\n\t\tif (!isNewRecord.value) {\n\t\t\tdraftRecord.value = {}\n\t\t\treturn\n\t\t}\n\t\tconst registry = stonecrop.value?.registry\n\t\tdraftRecord.value = registry ? registry.initializeRecord(getRecordFormSchema()) : {}\n\t},\n\t{ immediate: true }\n)\n\n// Stonecrop reactive computed properties update automatically when the instance\n// becomes available — no manual watcher needed.\n\n// Provide navigation helpers and an emitAction convenience function to child components.\nconst desktopMethods = {\n\tnavigateToDoctype,\n\topenRecord,\n\tcreateNewRecord,\n\t/**\n\t * Convenience wrapper so child components (e.g. slot content) can emit\n\t * an action event without needing a direct reference to the emit function.\n\t */\n\temitAction: (name: string, data?: Record<string, any>) => {\n\t\temit('action', {\n\t\t\tname,\n\t\t\tdoctype: currentDoctype.value,\n\t\t\trecordId: currentRecordId.value,\n\t\t\tdata: data ?? currentViewData.value ?? {},\n\t\t})\n\t},\n}\n\nprovide('desktopMethods', desktopMethods)\n\n// Provide a navigator for AFormLink so the arrow button navigates to the linked record.\nprovide('aformLinkNavigator', {\n\tnavigate: (doctype: string, id: string | number) => {\n\t\tvoid doNavigate({ view: 'record', doctype, recordId: String(id) })\n\t},\n} satisfies AFormLinkNavigator)\n\n// Provide a resolver for AFormLink to look up display text by doctype + id.\n// Checks HST first (sync); falls back to an async client fetch if not cached.\n// Uses the target doctype's declared displayField — no heuristic field guessing.\nprovide('aformLinkResolver', async (doctypeSlug: string, id: string): Promise<string | undefined> => {\n\tif (!stonecrop.value) return undefined\n\ttry {\n\t\tconst meta = await stonecrop.value.getMeta({ path: `/${doctypeSlug}`, segments: [doctypeSlug] })\n\t\tconst displayField = meta?.displayField\n\t\tif (!displayField) return undefined\n\n\t\tconst readDisplay = (rec: Record<string, unknown> | undefined): string | undefined => {\n\t\t\tif (!rec) return undefined\n\t\t\tconst val = rec[displayField]\n\t\t\treturn typeof val === 'string' || typeof val === 'number' ? String(val) : undefined\n\t\t}\n\n\t\tconst cached = stonecrop.value.getRecordById(doctypeSlug, id)?.get('') as Record<string, unknown> | undefined\n\t\tconst cachedDisplay = readDisplay(cached)\n\t\tif (cachedDisplay != null) return cachedDisplay\n\n\t\tawait stonecrop.value.getRecord(doctypeSlug, id)\n\t\tconst fetched = stonecrop.value.getRecordById(doctypeSlug, id)?.get('') as Record<string, unknown> | undefined\n\t\treturn readDisplay(fetched)\n\t} catch {\n\t\treturn undefined\n\t}\n})\n\nconst handleKeydown = (event: KeyboardEvent) => {\n\tif ((event.ctrlKey || event.metaKey) && event.key === 'k') {\n\t\tevent.preventDefault()\n\t\tcommandPaletteOpen.value = true\n\t}\n\tif (event.key === 'Escape') {\n\t\tif (commandPaletteOpen.value) {\n\t\t\tcommandPaletteOpen.value = false\n\t\t\treturn\n\t\t}\n\t\tif (actionSetController.isPreviewOpen.value) {\n\t\t\tactionSetController.closePreview()\n\t\t\treturn\n\t\t}\n\t\tif (actionSetController.isDrawerOpen.value) {\n\t\t\tactionSetController.close()\n\t\t}\n\t}\n}\n\nconst visibleActionSetSlots = computed(() =>\n\t(actionSetSlots ?? [])\n\t\t.filter(slot => slot.show !== false)\n\t\t.map(slot =>\n\t\t\tObject.assign({}, slot, {\n\t\t\t\ticon: slot.icon ? markRaw(slot.icon) : undefined,\n\t\t\t\tcomponent: slot.component ? markRaw(slot.component) : undefined,\n\t\t\t})\n\t\t)\n)\n\nconst actionSetController = createActionSet({ doctype: currentDoctype, recordId: currentRecordId })\nprovide(actionSetKey, actionSetController)\n\nconst actionSetDrawerOpen = computed(() => actionSetController.isDrawerOpen.value)\nconst actionSetPreviewOpen = computed(() => actionSetController.isPreviewOpen.value)\nconst actionSetPreviewSubject = computed(() => actionSetController.previewSubject.value)\n\nwatch([currentDoctype, currentRecordId], () => {\n\tactionSetController.close()\n})\n\nfunction closeActionSetPreview() {\n\tactionSetController.closePreview()\n}\n\nonMounted(() => {\n\tdocument.addEventListener('keydown', handleKeydown)\n})\n\nonUnmounted(() => {\n\tdocument.removeEventListener('keydown', handleKeydown)\n})\n</script>\n\n<style scoped>\n.desktop {\n\tposition: relative;\n\theight: 100%;\n\tdisplay: flex;\n\tflex-direction: column;\n\tmin-height: 0;\n\toverflow: hidden;\n}\n\n.desktop__workspace {\n\tdisplay: flex;\n\tflex: 1;\n\tmin-height: 0;\n\tmin-width: 0;\n\tmargin-right: 0;\n\ttransition: margin-right 0.25s ease-out;\n}\n\n.desktop--action-set-open .desktop__workspace {\n\tmargin-right: var(--sc-action-set-drawer-width);\n}\n\n/* SheetNav is fixed to the viewport, so the workspace margin does not move it clear of the drawer. */\n.desktop__sheetnav {\n\ttransition: right 0.25s ease-out;\n}\n\n.desktop--action-set-open .desktop__sheetnav {\n\tright: var(--sc-action-set-drawer-width);\n}\n\n.desktop__main {\n\tflex: 1;\n\tmin-width: 0;\n\tmin-height: 0;\n\toverflow: auto;\n}\n\n.desktop--preview-open .desktop__main {\n\tflex: 0 0 calc(50% - 10px);\n\tmax-width: calc(50% - 10px);\n}\n\n.desktop__preview {\n\tflex: 0 0 50%;\n\tmax-width: 50%;\n\tmin-width: 0;\n\tmin-height: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tborder-left: 1px solid var(--sc-gray-20);\n\tbackground: var(--sc-form-background);\n\toverflow: hidden;\n}\n\n.desktop__preview-header {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\talign-items: center;\n\theight: 40px;\n\tpadding: 0 8px;\n\tborder-bottom: 1px solid var(--sc-gray-20);\n\tflex-shrink: 0;\n}\n\n.desktop__preview-close {\n\tborder: none;\n\tbackground: transparent;\n\tfont-size: 1.25rem;\n\tline-height: 1;\n\tcursor: pointer;\n\tcolor: var(--sc-gray-60);\n}\n\n.desktop__preview-body {\n\tflex: 1;\n\tmin-height: 0;\n\toverflow: auto;\n}\n\n.loading {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tmin-height: 50vh;\n\tcolor: var(--sc-gray-60);\n}\n</style>\n","<template>\n\t<div\n\t\tclass=\"desktop\"\n\t\t:class=\"{\n\t\t\t'desktop--action-set-open': actionSetDrawerOpen,\n\t\t\t'desktop--preview-open': actionSetPreviewOpen,\n\t\t}\"\n\t\t@click=\"handleClick\">\n\t\t<div class=\"desktop__workspace\">\n\t\t\t<div class=\"desktop__main\">\n\t\t\t\t<slot v-if=\"$slots.default\" />\n\t\t\t\t<AForm\n\t\t\t\t\tv-else-if=\"currentViewSchema.length > 0\"\n\t\t\t\t\tv-model:data=\"currentViewData\"\n\t\t\t\t\t:schema=\"currentViewSchema\"\n\t\t\t\t\t:errors=\"fieldErrors\" />\n\t\t\t\t<div v-else-if=\"!stonecrop\" class=\"loading\"><p>Initializing Stonecrop...</p></div>\n\t\t\t\t<div v-else class=\"loading\">\n\t\t\t\t\t<p>Loading {{ currentView }} data...</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<aside v-if=\"actionSetPreviewSubject\" class=\"desktop__preview\">\n\t\t\t\t<header class=\"desktop__preview-header\">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"desktop__preview-close\"\n\t\t\t\t\t\taria-label=\"Close preview\"\n\t\t\t\t\t\t@click=\"closeActionSetPreview\">\n\t\t\t\t\t\t×\n\t\t\t\t\t</button>\n\t\t\t\t</header>\n\t\t\t\t<div class=\"desktop__preview-body\">\n\t\t\t\t\t<component :is=\"actionSetPreviewSubject.view\" v-bind=\"actionSetPreviewSubject.props ?? {}\" />\n\t\t\t\t</div>\n\t\t\t</aside>\n\t\t</div>\n\n\t\t<ActionSet\n\t\t\t:slots=\"visibleActionSetSlots\"\n\t\t\t:elements=\"actionElements\"\n\t\t\t:controller=\"actionSetController\"\n\t\t\t@action-click=\"handleActionClick\"\n\t\t\t@search=\"commandPaletteOpen = true\" />\n\n\t\t<SheetNav class=\"desktop__sheetnav\" :breadcrumbs=\"navigationBreadcrumbs\">\n\t\t\t<template #toolbar>\n\t\t\t\t<slot name=\"sheetnav-toolbar\" />\n\t\t\t</template>\n\t\t</SheetNav>\n\n\t\t<CommandPalette\n\t\t\t:is-open=\"commandPaletteOpen\"\n\t\t\t:search=\"searchCommands\"\n\t\t\tplaceholder=\"Type a command or search...\"\n\t\t\t@select=\"executeCommand\"\n\t\t\t@close=\"commandPaletteOpen = false\">\n\t\t\t<template #title=\"{ result }\">\n\t\t\t\t{{ result.title }}\n\t\t\t</template>\n\t\t\t<template #content=\"{ result }\">\n\t\t\t\t{{ result.description }}\n\t\t\t</template>\n\t\t</CommandPalette>\n\t</div>\n</template>\n\n<script setup lang=\"ts\">\n// The draft segment comes from @stonecrop/stonecrop rather than being spelled out here: that\n// package guards fetching, field initialization and workflow readiness on the same question, and\n// when the two were written separately they disagreed and every guard over there went dead.\nimport { DRAFT_RECORD_ID, isDraftRecordId, useStonecrop, useValidationStore } from '@stonecrop/stonecrop'\nimport {\n\tAForm,\n\ttype AFormLinkNavigator,\n\tresolvedFieldsToColumns,\n\ttype ResolvedField,\n\ttype ResolvedTable,\n} from '@stonecrop/aform'\nimport { computed, markRaw, onMounted, onUnmounted, provide, ref, unref, watch } from 'vue'\n\nimport ActionSet from './ActionSet.vue'\nimport SheetNav from './SheetNav.vue'\nimport CommandPalette from './CommandPalette.vue'\nimport { createActionSet, actionSetKey } from '../composables/useActionSet'\nimport type {\n\tActionElements,\n\tRouteAdapter,\n\tNavigationTarget,\n\tActionEventPayload,\n\tRecordOpenEventPayload,\n\tLoadRecordsEventPayload,\n\tLoadRecordEventPayload,\n\tActionSetSlot,\n} from '../types'\n\nconst {\n\tavailableDoctypes = [],\n\trouteAdapter,\n\tactionSetSlots,\n\thostActions,\n} = defineProps<{\n\tavailableDoctypes?: string[]\n\t/**\n\t * Pluggable router adapter. When provided, Desktop uses these functions for all\n\t * routing instead of reaching into the registry's internal Vue Router instance.\n\t * Nuxt hosts (or any host with custom route conventions) should supply this.\n\t */\n\trouteAdapter?: RouteAdapter\n\t/** Host drawer slots, each shown as a tile in the ActionSet column. */\n\tactionSetSlots?: ActionSetSlot[]\n\t/** When provided, the Actions drawer lists exactly these, in place of the actions Desktop derives from the doctype. */\n\thostActions?: ActionElements[]\n}>()\n\nconst emit = defineEmits<{\n\t/**\n\t * Fired when the user triggers an FSM transition (action button click).\n\t * The host app is responsible for calling the server, persisting state, etc.\n\t */\n\taction: [payload: ActionEventPayload]\n\t/**\n\t * Fired when Desktop wants to navigate to a different view.\n\t * Also calls routeAdapter.navigate() if an adapter is provided.\n\t */\n\tnavigate: [target: NavigationTarget]\n\t/**\n\t * Fired when the user opens a specific record.\n\t */\n\t'record:open': [payload: RecordOpenEventPayload]\n\t/**\n\t * Fired when Desktop is about to read records for a list view. A notification, not a request:\n\t * Desktop performs the read itself through `Stonecrop.getRecords`. A host that fetches here\n\t * races that read into the same HST key.\n\t */\n\t'load-records': [payload: LoadRecordsEventPayload]\n\t/**\n\t * Fired when Desktop is about to read a single record for a form view. A notification, not a\n\t * request — see `load-records`. Not emitted for a draft, which has nothing to fetch.\n\t */\n\t'load-record': [payload: LoadRecordEventPayload]\n}>()\n\nconst { stonecrop } = useStonecrop()\n\n// Field-validation store (advisory, client-side). Pinia is a declared peerDependency, kept external\n// in this package's build (rollupOptions), so `useValidationStore()` resolves the host app's single\n// active Pinia directly — the old `getCurrentInstance().$pinia` workaround for the bundled-Pinia bug\n// is no longer needed. Still guarded: validation is optional and Desktop predates it, so a host that\n// mounts Desktop without Pinia disables validation gracefully instead of crashing on mount.\nlet validationStore: ReturnType<typeof useValidationStore> | null = null\ntry {\n\tvalidationStore = useValidationStore()\n} catch {\n\tvalidationStore = null\n}\n\n// Inline field errors handed to AForm. Only surfaced in the record form view (currentView is\n// defined below); empty elsewhere so a previous record's errors never bleed into a list view.\nconst fieldErrors = computed<Record<string, string[]>>(() =>\n\tcurrentView.value === 'record' ? (validationStore?.errorsByField ?? {}) : {}\n)\n\n// State\nconst loading = ref(false)\nconst commandPaletteOpen = ref(false)\n\n// The record being composed on a `/{doctype}/new` route. Deliberately not in HST: a draft has no\n// identity to be keyed by, and both ways of faking one fail — see `DRAFT_RECORD_ID`.\nconst draftRecord = ref<Record<string, any>>({})\n\n// Form/list data management — each view produces a different data shape.\n// List views (doctypes, records) return table row data keyed by fieldname.\n// Record view returns the record's fields for two-way binding — from HST, or from `draftRecord`\n// when the route is a draft.\nconst currentViewData = computed<Record<string, any>>({\n\tget() {\n\t\t// Doctypes list — rows come from availableDoctypes prop (reactive via availableDoctypes)\n\t\tif (currentView.value === 'doctypes') {\n\t\t\treturn {\n\t\t\t\tdoctypes_table:\n\t\t\t\t\tavailableDoctypes?.map(doctype => ({\n\t\t\t\t\t\tid: doctype,\n\t\t\t\t\t\tdoctype,\n\t\t\t\t\t\tdisplay_name: formatDoctypeName(doctype),\n\t\t\t\t\t\tactions: 'View Records',\n\t\t\t\t\t})) ?? [],\n\t\t\t}\n\t\t}\n\n\t\t// Records list — rows come from HST store (reactive because HST is Vue reactive())\n\t\tif (currentView.value === 'records') {\n\t\t\treturn {\n\t\t\t\trecords_table: getRecords().map(record =>\n\t\t\t\t\tObject.assign({}, record, {\n\t\t\t\t\t\tid: resolveRecordId(record) ?? '',\n\t\t\t\t\t\t// A list row is navigation. Actions live on the record view, where the Actions\n\t\t\t\t\t\t// dropdown is built from what the doctype declares and what the record's\n\t\t\t\t\t\t// current state allows. This cell used to also offer Delete, which dispatched\n\t\t\t\t\t\t// an action named `DELETE` that Desktop invented — no doctype in a\n\t\t\t\t\t\t// WorkflowMeta app declares it, so it failed on every click. Removal is a\n\t\t\t\t\t\t// workflow outcome (`archive`, `cancel`) and belongs in the doctype.\n\t\t\t\t\t\tactions: 'Edit',\n\t\t\t\t\t})\n\t\t\t\t),\n\t\t\t}\n\t\t}\n\n\t\t// Record form — read single record from HST\n\t\tif (!stonecrop.value || !currentDoctype.value || !currentRecordId.value) {\n\t\t\treturn {}\n\t\t}\n\n\t\ttry {\n\t\t\tconst source = isNewRecord.value\n\t\t\t\t? draftRecord.value\n\t\t\t\t: (stonecrop.value.getRecordById(currentDoctype.value, currentRecordId.value)?.get('') as\n\t\t\t\t\t\t| Record<string, any>\n\t\t\t\t\t\t| undefined)\n\t\t\t// Return a plain shallow copy so AForm mutations don't propagate directly into\n\t\t\t// the HST reactive object, which would bypass field-trigger diffing and cause\n\t\t\t// setupDeepReactivity to fire triggers for all fields on every keystroke.\n\t\t\tconst flat: Record<string, any> = { ...source }\n\n\t\t\t// AFieldset receives data[fieldsetFieldname] as its data prop, so the fieldset's\n\t\t\t// children must be grouped under the fieldset key. The server returns flat SQL rows,\n\t\t\t// so we nest fieldset children here before AForm renders them.\n\t\t\tconst doctype = stonecrop.value.registry.registry[currentDoctype.value]\n\t\t\tif (doctype) {\n\t\t\t\tfor (const field of doctype.getSchemaArray()) {\n\t\t\t\t\tif (field.kind === 'fieldset') {\n\t\t\t\t\t\tconst nested: Record<string, any> = {}\n\t\t\t\t\t\tfor (const child of field.schema) {\n\t\t\t\t\t\t\tif (child.fieldname) nested[child.fieldname] = flat[child.fieldname]\n\t\t\t\t\t\t}\n\t\t\t\t\t\tflat[field.fieldname] = nested\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn flat\n\t\t} catch {\n\t\t\treturn {}\n\t\t}\n\t},\n\tset(newData: Record<string, any>) {\n\t\t// List views are read-only from AForm's perspective — HST writes only apply to record form\n\t\tif (currentView.value !== 'record') return\n\t\tif (!stonecrop.value || !currentDoctype.value || !currentRecordId.value) {\n\t\t\treturn\n\t\t}\n\n\t\ttry {\n\t\t\t// AForm emits nested data for fieldsets: { fieldsetKey: { childA: val } }.\n\t\t\t// HST and the server both expect flat rows, so flatten fieldset values back before writing.\n\t\t\tconst doctype = stonecrop.value.registry.registry[currentDoctype.value]\n\t\t\tconst fieldsetNames = new Set<string>()\n\t\t\tif (doctype) {\n\t\t\t\tfor (const field of doctype.getSchemaArray()) {\n\t\t\t\t\tif (field.kind === 'fieldset') {\n\t\t\t\t\t\tfieldsetNames.add(field.fieldname)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Two-pass flatten: non-fieldset keys first, then fieldset children.\n\t\t\t// Fieldset children must be applied last — AForm may emit stale flat copies\n\t\t\t// of fieldset children alongside the updated nested value, and the nested\n\t\t\t// value must win regardless of key insertion order.\n\t\t\tconst flatData: Record<string, any> = {}\n\t\t\tconst fieldsetValues: Record<string, any>[] = []\n\t\t\tfor (const [key, value] of Object.entries(newData)) {\n\t\t\t\tif (fieldsetNames.has(key) && value && typeof value === 'object' && !Array.isArray(value)) {\n\t\t\t\t\tfieldsetValues.push(value)\n\t\t\t\t} else {\n\t\t\t\t\tflatData[key] = value\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const nestedValue of fieldsetValues) {\n\t\t\t\tObject.assign(flatData, nestedValue)\n\t\t\t}\n\n\t\t\t// Only update fields that actually changed. Never write undefined — AForm may emit\n\t\t\t// schema fields absent from the record as undefined; writing them would silently\n\t\t\t// clear values that exist. Explicit null is allowed (intentional clear).\n\t\t\tconst changedFields: string[] = []\n\t\t\tif (isNewRecord.value) {\n\t\t\t\t// Reassigned, not mutated, so the getter re-runs. Relying on in-place mutation of the\n\t\t\t\t// cached object is what made a draft's edits vanish on any invalidation.\n\t\t\t\tconst next = { ...draftRecord.value }\n\t\t\t\tfor (const [fieldname, value] of Object.entries(flatData)) {\n\t\t\t\t\tif (value === undefined) continue\n\t\t\t\t\tif (next[fieldname] !== value) {\n\t\t\t\t\t\tnext[fieldname] = value\n\t\t\t\t\t\tchangedFields.push(fieldname)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdraftRecord.value = next\n\t\t\t} else {\n\t\t\t\tconst hstStore = stonecrop.value.getStore()\n\t\t\t\tfor (const [fieldname, value] of Object.entries(flatData)) {\n\t\t\t\t\tif (value === undefined) continue\n\t\t\t\t\tconst fieldPath = `${currentDoctype.value}.${currentRecordId.value}.${fieldname}`\n\t\t\t\t\tconst currentValue = hstStore.has(fieldPath) ? hstStore.get(fieldPath) : undefined\n\t\t\t\t\tif (currentValue !== value) {\n\t\t\t\t\t\thstStore.set(fieldPath, value)\n\t\t\t\t\t\tchangedFields.push(fieldname)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Advisory field-validation runs on the fields that actually changed (honors each\n\t\t\t// trigger's `on` set). Driven here, after the HST writes, so HST stays value-only.\n\t\t\tif (changedFields.length > 0) {\n\t\t\t\tdriveFieldValidation(changedFields)\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.warn('HST update failed:', error)\n\t\t}\n\t},\n})\n\n// Advisory field-validation: run the doctype's triggers for the fields that changed on this edit.\n// Snapshots the post-edit record as a plain, flat object so validators read siblings read-only\n// (the core store freezes it). Fire-and-forget — the reactive error store repaints AForm on resolve.\nfunction driveFieldValidation(changedFields: string[]) {\n\tif (!validationStore || !stonecrop.value || !currentDoctype.value || !currentRecordId.value) return\n\n\tconst doctype = stonecrop.value.registry.getDoctype(currentDoctype.value)\n\tconst triggers = doctype?.getTriggers()\n\tif (!triggers || Object.keys(triggers).length === 0) return\n\n\t// A draft's siblings come from the buffer; reading HST would hand every validator an empty record.\n\tconst record = isNewRecord.value\n\t\t? { ...draftRecord.value }\n\t\t: {\n\t\t\t\t...(stonecrop.value.getRecordById(currentDoctype.value, currentRecordId.value)?.get('') as Record<\n\t\t\t\t\tstring,\n\t\t\t\t\tunknown\n\t\t\t\t>),\n\t\t\t}\n\n\tfor (const field of changedFields) {\n\t\tvoid validationStore.validateField(triggers, field, record)\n\t}\n}\n\n// Computed properties for current route context.\n// When a routeAdapter is provided it takes full precedence over the registry's internal router.\nconst route = computed(() => (routeAdapter ? null : unref(stonecrop.value?.registry.router?.currentRoute)))\nconst router = computed(() => (routeAdapter ? null : stonecrop.value?.registry.router))\nconst currentDoctype = computed(() => {\n\tif (routeAdapter) return routeAdapter.getCurrentDoctype()\n\tif (!route.value) return ''\n\n\t// First check if we have actualDoctype in meta (from registered routes)\n\tif (route.value.meta?.actualDoctype) {\n\t\treturn route.value.meta.actualDoctype as string\n\t}\n\n\t// For named routes, use params.doctype\n\tif (route.value.params.doctype) {\n\t\treturn route.value.params.doctype.toString()\n\t}\n\n\t// For catch-all routes that haven't been registered yet, extract from path\n\tconst pathMatch = route.value.params.pathMatch as string[] | undefined\n\tif (pathMatch && pathMatch.length > 0) {\n\t\treturn pathMatch[0]\n\t}\n\n\treturn ''\n})\n\n// The route doctype for display and navigation (e.g., 'todo')\nconst routeDoctype = computed(() => {\n\tif (routeAdapter) return routeAdapter.getCurrentDoctype()\n\tif (!route.value) return ''\n\n\t// Check route meta first\n\tif (route.value.meta?.doctype) {\n\t\treturn route.value.meta.doctype as string\n\t}\n\n\t// For named routes, use params.doctype\n\tif (route.value.params.doctype) {\n\t\treturn route.value.params.doctype.toString()\n\t}\n\n\t// For catch-all routes, extract from path\n\tconst pathMatch = route.value.params.pathMatch as string[] | undefined\n\tif (pathMatch && pathMatch.length > 0) {\n\t\treturn pathMatch[0]\n\t}\n\n\treturn ''\n})\n\nconst currentRecordId = computed(() => {\n\tif (routeAdapter) return routeAdapter.getCurrentRecordId()\n\tif (!route.value) return ''\n\n\t// For named routes, use params.recordId\n\tif (route.value.params.recordId) {\n\t\treturn route.value.params.recordId.toString()\n\t}\n\n\t// For catch-all routes that haven't been registered yet, extract from path\n\tconst pathMatch = route.value.params.pathMatch as string[] | undefined\n\tif (pathMatch && pathMatch.length > 1) {\n\t\treturn pathMatch[1]\n\t}\n\n\treturn ''\n})\nconst isNewRecord = computed(() => isDraftRecordId(currentRecordId.value))\n\n// Determine current view based on route\nconst currentView = computed(() => {\n\tif (routeAdapter) return routeAdapter.getCurrentView()\n\tif (!route.value) {\n\t\treturn 'doctypes'\n\t}\n\n\t// Home route\n\tif (route.value.name === 'home' || route.value.path === '/') {\n\t\treturn 'doctypes'\n\t}\n\n\t// Named routes from registered doctypes\n\tif (route.value.name && route.value.name !== 'catch-all') {\n\t\tconst routeName = route.value.name as string\n\t\tif (routeName.includes('form') || route.value.params.recordId) {\n\t\t\treturn 'record'\n\t\t} else if (routeName.includes('list') || route.value.params.doctype) {\n\t\t\treturn 'records'\n\t\t}\n\t}\n\n\t// Catch-all route - determine from path structure\n\tconst pathMatch = route.value.params.pathMatch as string[] | undefined\n\tif (pathMatch && pathMatch.length > 0) {\n\t\tconst view = pathMatch.length === 1 ? 'records' : 'record'\n\t\treturn view\n\t}\n\n\treturn 'doctypes'\n})\n\n// Computed properties (now that all helper functions are defined)\n// Helper function to get available transitions for current record.\n// Reads the actual FSM state from the record's `status` field (or falls back to the\n// workflow initial state) so the available action buttons always reflect reality.\nconst getAvailableTransitions = () => {\n\tif (!stonecrop.value || !currentDoctype.value || !currentRecordId.value) {\n\t\treturn []\n\t}\n\n\ttry {\n\t\tconst doctype = stonecrop.value.registry.getDoctype(currentDoctype.value)\n\t\tif (!doctype?.workflow) return []\n\n\t\t// Delegate state resolution to Stonecrop — reads record 'status', falls back to workflow.initial\n\t\tconst currentState = stonecrop.value.getRecordState(currentDoctype.value, currentRecordId.value)\n\n\t\t// Delegate transition lookup to Doctype — no more manual workflow introspection\n\t\tconst transitions = doctype.getAvailableTransitions(currentState)\n\n\t\tconst recordData = currentViewData.value || {}\n\n\t\t// Each transition emits an 'action' event. The host app decides what to do\n\t\t// (call the server, trigger an FSM actor, update HST, etc.).\n\t\treturn transitions.map(({ name }) => ({\n\t\t\t// Prefer the workflow action's human-readable label (WorkflowMeta format);\n\t\t\t// fall back to the raw transition name for XState workflows with no action meta.\n\t\t\tlabel: doctype.getActionMeta(name)?.label ?? name,\n\t\t\taction: () => {\n\t\t\t\temit('action', {\n\t\t\t\t\tname,\n\t\t\t\t\tdoctype: currentDoctype.value,\n\t\t\t\t\trecordId: currentRecordId.value,\n\t\t\t\t\tdata: recordData,\n\t\t\t\t})\n\t\t\t},\n\t\t}))\n\t} catch (error) {\n\t\tconsole.warn('Error getting available transitions:', error)\n\t\treturn []\n\t}\n}\n\n// Helper: stateless Commands available for the current record — side-effect actions that\n// change no workflow state. Surfaced in the same Actions dropdown as transitions; each emits\n// the same 'action' event, so the host's handler runs a Command's clientHandler identically.\nconst getAvailableCommands = () => {\n\tif (!stonecrop.value || !currentDoctype.value || !currentRecordId.value) {\n\t\treturn []\n\t}\n\n\ttry {\n\t\tconst doctype = stonecrop.value.registry.getDoctype(currentDoctype.value)\n\t\tif (!doctype?.workflow) return []\n\n\t\tconst currentState = stonecrop.value.getRecordState(currentDoctype.value, currentRecordId.value)\n\t\tconst commands = doctype.getAvailableCommands(currentState)\n\t\tconst recordData = currentViewData.value || {}\n\n\t\treturn commands.map(({ name }) => ({\n\t\t\tlabel: doctype.getActionMeta(name)?.label ?? name,\n\t\t\taction: () => {\n\t\t\t\temit('action', {\n\t\t\t\t\tname,\n\t\t\t\t\tdoctype: currentDoctype.value,\n\t\t\t\t\trecordId: currentRecordId.value,\n\t\t\t\t\tdata: recordData,\n\t\t\t\t})\n\t\t\t},\n\t\t}))\n\t} catch (error) {\n\t\tconsole.warn('Error getting available commands:', error)\n\t\treturn []\n\t}\n}\n\nconst actionElements = computed(() => {\n\tif (hostActions) {\n\t\treturn hostActions\n\t}\n\n\tconst elements: ActionElements[] = []\n\n\tswitch (currentView.value) {\n\t\tcase 'records':\n\t\t\telements.push({\n\t\t\t\ttype: 'button',\n\t\t\t\tlabel: 'New Record',\n\t\t\t\taction: () => void createNewRecord(),\n\t\t\t})\n\t\t\tbreak\n\t\tcase 'record': {\n\t\t\t// Populate the Actions dropdown with every FSM transition AND stateless Command\n\t\t\t// available in the record's current state. Clicking either emits 'action'.\n\t\t\tconst recordActions = [...getAvailableTransitions(), ...getAvailableCommands()]\n\t\t\tif (recordActions.length > 0) {\n\t\t\t\telements.push({\n\t\t\t\t\ttype: 'dropdown',\n\t\t\t\t\tlabel: 'Actions',\n\t\t\t\t\tactions: recordActions,\n\t\t\t\t})\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn elements\n})\n\nconst navigationBreadcrumbs = computed(() => {\n\tconst breadcrumbs: { title: string; to: string }[] = []\n\n\tif (currentView.value === 'records' && routeDoctype.value) {\n\t\tbreadcrumbs.push(\n\t\t\t{ title: 'Home', to: '/' },\n\t\t\t{ title: formatDoctypeName(routeDoctype.value), to: `/${routeDoctype.value}` }\n\t\t)\n\t} else if (currentView.value === 'record' && routeDoctype.value) {\n\t\tconst recordPath = currentRecordId.value\n\t\t\t? `/${routeDoctype.value}/${currentRecordId.value}`\n\t\t\t: (route.value?.fullPath ?? '')\n\t\tbreadcrumbs.push(\n\t\t\t{ title: 'Home', to: '/' },\n\t\t\t{ title: formatDoctypeName(routeDoctype.value), to: `/${routeDoctype.value}` },\n\t\t\t{ title: isNewRecord.value ? 'New Record' : 'Edit Record', to: recordPath }\n\t\t)\n\t}\n\n\treturn breadcrumbs\n})\n\n// Command palette functionality\ntype Command = {\n\ttitle: string\n\tdescription: string\n\taction: () => void\n}\n\nconst searchCommands = (query: string): Command[] => {\n\tconst commands: Command[] = [\n\t\t{\n\t\t\ttitle: 'Go Home',\n\t\t\tdescription: 'Navigate to the home page',\n\t\t\taction: () => void doNavigate({ view: 'doctypes' }),\n\t\t},\n\t\t{\n\t\t\ttitle: 'Toggle Command Palette',\n\t\t\tdescription: 'Open/close the command palette',\n\t\t\taction: () => (commandPaletteOpen.value = !commandPaletteOpen.value),\n\t\t},\n\t]\n\n\t// Add doctype-specific commands\n\tif (routeDoctype.value) {\n\t\tcommands.push({\n\t\t\ttitle: `View ${formatDoctypeName(routeDoctype.value)} Records`,\n\t\t\tdescription: `Navigate to ${routeDoctype.value} list`,\n\t\t\taction: () => void doNavigate({ view: 'records', doctype: routeDoctype.value }),\n\t\t})\n\n\t\tcommands.push({\n\t\t\ttitle: `Create New ${formatDoctypeName(routeDoctype.value)}`,\n\t\t\tdescription: `Create a new ${routeDoctype.value} record`,\n\t\t\taction: () => void createNewRecord(),\n\t\t})\n\t}\n\n\t// Add available doctypes as commands\n\tavailableDoctypes.forEach(doctype => {\n\t\tcommands.push({\n\t\t\ttitle: `View ${formatDoctypeName(doctype)}`,\n\t\t\tdescription: `Navigate to ${doctype} list`,\n\t\t\taction: () => void doNavigate({ view: 'records', doctype }),\n\t\t})\n\t})\n\n\t// Filter commands based on query\n\tif (!query) return commands\n\n\treturn commands.filter(\n\t\tcmd =>\n\t\t\tcmd.title.toLowerCase().includes(query.toLowerCase()) ||\n\t\t\tcmd.description.toLowerCase().includes(query.toLowerCase())\n\t)\n}\n\nconst executeCommand = (command: Command) => {\n\tcommand.action()\n\tcommandPaletteOpen.value = false\n}\n\n// List reads are wired on the records table; ATable owns the fetch via getRecords.\nconst listRecordsFetcher = (options?: import('@stonecrop/schema').GetRecordsOptions) => {\n\tif (!stonecrop.value || !currentDoctype.value) {\n\t\treturn Promise.resolve({ data: [], hasMore: false })\n\t}\n\tconst doctype = stonecrop.value.registry.getDoctype(currentDoctype.value)\n\tif (!doctype) {\n\t\treturn Promise.resolve({ data: [], hasMore: false })\n\t}\n\treturn stonecrop.value.getRecords(doctype, options)\n}\n\n// Helper functions - moved here to avoid \"before initialization\" errors\nconst formatDoctypeName = (doctype: string): string => {\n\treturn doctype\n\t\t.split('-')\n\t\t.map(word => word.charAt(0).toUpperCase() + word.slice(1))\n\t\t.join(' ')\n}\n\n// Internal navigation helper: emits 'navigate', then calls the adapter (if any)\n// or falls back to the registry's Vue Router instance.\nconst doNavigate = async (target: NavigationTarget) => {\n\temit('navigate', target)\n\tif (routeAdapter) {\n\t\tawait routeAdapter.navigate(target)\n\t} else {\n\t\tif (target.view === 'doctypes') {\n\t\t\tawait router.value?.push('/')\n\t\t} else if (target.view === 'records' && target.doctype) {\n\t\t\tawait router.value?.push(`/${target.doctype}`)\n\t\t} else if (target.view === 'record' && target.doctype && target.recordId) {\n\t\t\tawait router.value?.push(`/${target.doctype}/${target.recordId}`)\n\t\t}\n\t}\n}\n\nconst navigateToDoctype = async (doctype: string) => {\n\tawait doNavigate({ view: 'records', doctype })\n}\n\nconst openRecord = async (recordId: string) => {\n\tconst doctype = routeDoctype.value\n\temit('record:open', { doctype, recordId })\n\tawait doNavigate({ view: 'record', doctype, recordId })\n}\n\nconst createNewRecord = async () => {\n\tawait doNavigate({ view: 'record', doctype: routeDoctype.value, recordId: DRAFT_RECORD_ID })\n}\n\n// Schema generator functions - moved here to be available to computed properties\nconst getDoctypesSchema = (): ResolvedField[] => {\n\tif (!availableDoctypes?.length) return []\n\n\treturn [\n\t\t{\n\t\t\tkind: 'table' as const,\n\t\t\tfieldname: 'doctypes_table',\n\t\t\tcomponent: 'ATable',\n\t\t\tlabel: 'Doctypes',\n\t\t\tschema: [\n\t\t\t\t{\n\t\t\t\t\tfieldname: 'doctype',\n\t\t\t\t\tlabel: 'Doctype',\n\t\t\t\t\tcomponent: 'ATextInput',\n\t\t\t\t\talign: 'left' as const,\n\t\t\t\t\tedit: false,\n\t\t\t\t\twidth: '20ch',\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tfieldname: 'display_name',\n\t\t\t\t\tlabel: 'Name',\n\t\t\t\t\tcomponent: 'ATextInput',\n\t\t\t\t\talign: 'left' as const,\n\t\t\t\t\tedit: false,\n\t\t\t\t\twidth: '30ch',\n\t\t\t\t},\n\t\t\t\t// No record count column. It read `getRecordIds(doctype).length`, which is how many\n\t\t\t\t// records HST happens to hold — zero for a doctype never opened, and the page size\n\t\t\t\t// for one that was. The real total belongs to the backend and Desktop never asks\n\t\t\t\t// for it, so this shell cannot answer it. Same reason the `recordIdField` prop went.\n\t\t\t\t{\n\t\t\t\t\tfieldname: 'actions',\n\t\t\t\t\tlabel: 'Actions',\n\t\t\t\t\tcomponent: 'ATextInput',\n\t\t\t\t\talign: 'center' as const,\n\t\t\t\t\tedit: false,\n\t\t\t\t\twidth: '20ch',\n\t\t\t\t},\n\t\t\t],\n\t\t\tconfig: { view: 'list' as const, fullWidth: true },\n\t\t} satisfies ResolvedTable,\n\t]\n}\n\nconst getRecordsSchema = (): ResolvedField[] => {\n\tif (!currentDoctype.value) return []\n\tif (!stonecrop.value) return []\n\n\tconst registry = stonecrop.value.registry\n\tconst doctype = registry.registry[currentDoctype.value]\n\n\tif (!doctype) return []\n\n\tconst schema = registry.resolveSchema(doctype)\n\n\t// If no schema is available, let the template fallback handle the loading state\n\tif (schema.length === 0) return []\n\n\tconst doctypeSlug = currentDoctype.value\n\tconst clientConfigured = Boolean(stonecrop.value.getClient())\n\n\treturn [\n\t\t{\n\t\t\tkind: 'table' as const,\n\t\t\tfieldname: 'records_table',\n\t\t\tcomponent: 'ATable',\n\t\t\t// Which resolved fields a cell can render is answered once, in @stonecrop/aform, and\n\t\t\t// called by the child-table builder too. Flattening alone is not the rule: it kept the\n\t\t\t// expanding links, whose value is a nested record or an array of them.\n\t\t\tschema: [...resolvedFieldsToColumns(schema), { fieldname: 'actions', label: 'Actions', component: 'ATextInput' }],\n\t\t\tconfig: { view: 'list' as const, fullWidth: true },\n\t\t\t...(clientConfigured ? { getRecords: listRecordsFetcher, sourceKey: doctypeSlug } : {}),\n\t\t} satisfies ResolvedTable,\n\t]\n}\n\nconst getRecordFormSchema = (): ResolvedField[] => {\n\tif (!currentDoctype.value) return []\n\tif (!stonecrop.value) return []\n\n\ttry {\n\t\tconst registry = stonecrop.value?.registry\n\t\tconst doctype = registry?.registry[currentDoctype.value]\n\n\t\tif (!doctype?.schema) {\n\t\t\t// Let the template fallback handle the loading state\n\t\t\treturn []\n\t\t}\n\n\t\treturn registry.resolveSchema(doctype)\n\t} catch {\n\t\treturn []\n\t}\n}\n\n// Additional data helper functions\nconst getRecords = () => {\n\tif (!stonecrop.value || !currentDoctype.value) {\n\t\treturn []\n\t}\n\n\tconst recordsNode = stonecrop.value.records(currentDoctype.value)\n\tconst recordsData = recordsNode?.get('')\n\n\tif (recordsData && typeof recordsData === 'object' && !Array.isArray(recordsData)) {\n\t\treturn Object.values(recordsData as Record<string, any>)\n\t}\n\n\treturn []\n}\n\n// Schema for different views - defined here after all helper functions are available\nconst currentViewSchema = computed(() => {\n\tswitch (currentView.value) {\n\t\tcase 'doctypes':\n\t\t\treturn getDoctypesSchema()\n\t\tcase 'records':\n\t\t\treturn getRecordsSchema()\n\t\tcase 'record':\n\t\t\treturn getRecordFormSchema()\n\t\tdefault:\n\t\t\treturn []\n\t}\n})\n\nconst handleActionClick = (_label: string, action: (() => void | Promise<void>) | undefined) => {\n\tif (action) {\n\t\tvoid action()\n\t}\n}\n\n/**\n * Resolve a record's identity for links and navigation.\n *\n * Identity is declared once, on the doctype: `primaryKey`, or `id` when nothing is declared.\n * Delegating to `Doctype.getRecordId` is what guarantees this matches the key\n * `Stonecrop.getRecords` stored the record under — resolving it independently here would let a\n * row render a link to an HST path that does not exist.\n *\n * There is deliberately no per-shell override. Identity is a per-doctype fact and one shell\n * renders many doctypes, so a single prop cannot answer it; a shell that named a field would\n * also be overriding the very declaration the store keyed on, which is the bug above.\n */\nconst resolveRecordId = (record: Record<string, unknown>): string | undefined => {\n\tif (!stonecrop.value || !currentDoctype.value) return undefined\n\treturn stonecrop.value.registry.registry[currentDoctype.value]?.getRecordId(record)\n}\n\n// Event handlers\nconst getRecordIdFromRow = (rowElement: HTMLTableRowElement): string | null => {\n\tconst cell = rowElement.querySelector('td[data-rowindex]')\n\tif (!cell) return null\n\n\tconst rowIndexAttr = cell.getAttribute('data-rowindex')\n\tif (rowIndexAttr === null) return null\n\n\tconst rowIndex = parseInt(rowIndexAttr, 10)\n\tif (isNaN(rowIndex)) return null\n\n\tconst records = getRecords()\n\tconst record = records[rowIndex]\n\tif (!record) return null\n\n\treturn resolveRecordId(record) ?? null\n}\n\nconst handleClick = async (event: Event) => {\n\tconst target = event.target as HTMLElement\n\tconst action = target.getAttribute('data-action')\n\n\tif (action === 'create') {\n\t\tawait createNewRecord()\n\t}\n\n\tconst cell = target.closest('td, th')\n\tif (cell) {\n\t\tconst cellText = cell.textContent?.trim()\n\t\tconst row = cell.closest('tr')\n\n\t\tif (cellText === 'View Records' && row) {\n\t\t\t// Get the doctype from the row data\n\t\t\tconst cells = row.querySelectorAll('td')\n\t\t\tif (cells.length > 0) {\n\t\t\t\tconst doctypeCell = cells[1] // Assuming doctype is in second column (first column is index)\n\t\t\t\tconst doctype = doctypeCell.textContent?.trim()\n\t\t\t\tif (doctype) {\n\t\t\t\t\tawait navigateToDoctype(doctype)\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (cellText === 'Edit' && row) {\n\t\t\t// Matched exactly, not by substring. This handler is bound to the whole desktop, so a\n\t\t\t// substring match fired on any cell whose *data* happened to contain the word — a task\n\t\t\t// titled \"Delete old backups\" popped a delete confirmation when you clicked it.\n\t\t\tconst recordId = getRecordIdFromRow(row)\n\t\t\tif (recordId) {\n\t\t\t\tawait openRecord(recordId)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Reads go through Stonecrop, which owns whether to fetch, how to key the result, and where to\n// put it. Desktop asks for data and renders what arrives; it decides none of that itself.\n//\n// Both loaders are no-ops without a client, so a host that populates HST some other way keeps\n// working unchanged rather than taking a thrown error on every navigation.\nconst loadRecordData = async () => {\n\tif (!stonecrop.value || !currentDoctype.value || !stonecrop.value.getClient()) return\n\n\tloading.value = true\n\ttry {\n\t\tawait stonecrop.value.getRecord(currentDoctype.value, currentRecordId.value)\n\t} catch (error) {\n\t\tconsole.warn('Error fetching record:', error)\n\t} finally {\n\t\tloading.value = false\n\t}\n}\n\n// Watch for route changes to load appropriate data\nwatch(\n\t[currentView, currentDoctype, currentRecordId],\n\t() => {\n\t\t// The events are notifications, not fetch requests: they announce what Desktop is about to\n\t\t// read so a host can hang analytics or a prefetch off them. The read itself is Stonecrop's.\n\t\tif (currentView.value === 'records' && currentDoctype.value) {\n\t\t\temit('load-records', { doctype: currentDoctype.value })\n\t\t} else if (currentView.value === 'record' && currentDoctype.value && currentRecordId.value) {\n\t\t\t// A draft has nothing to fetch — the record does not exist on the server yet. Desktop\n\t\t\t// used to emit anyway and leave the host to work it out, which meant every host had to\n\t\t\t// recognise the private draft-id scheme above just to suppress a doomed request; all of\n\t\t\t// them did, identically. `getRecord` declines the same case for the same reason.\n\t\t\tif (isNewRecord.value) return\n\n\t\t\temit('load-record', { doctype: currentDoctype.value, recordId: currentRecordId.value })\n\t\t\tvoid loadRecordData()\n\t\t}\n\t},\n\t{ immediate: true }\n)\n\n// Clear advisory validation errors when the target record changes, so errors from a previously\n// edited record never bleed into a newly opened one.\nwatch([currentDoctype, currentRecordId], () => {\n\tvalidationStore?.clearAll()\n})\n\n// Seeding on entry gives a new record the doctype's declared defaults, which it never used to get.\n// Discarding on exit matters as much: the draft segment is one shared literal, so a stale buffer\n// would open the next New Record pre-filled with the abandoned one's values.\nwatch(\n\t[currentDoctype, currentRecordId],\n\t() => {\n\t\tif (!isNewRecord.value) {\n\t\t\tdraftRecord.value = {}\n\t\t\treturn\n\t\t}\n\t\tconst registry = stonecrop.value?.registry\n\t\tdraftRecord.value = registry ? registry.initializeRecord(getRecordFormSchema()) : {}\n\t},\n\t{ immediate: true }\n)\n\n// Stonecrop reactive computed properties update automatically when the instance\n// becomes available — no manual watcher needed.\n\n// Provide navigation helpers and an emitAction convenience function to child components.\nconst desktopMethods = {\n\tnavigateToDoctype,\n\topenRecord,\n\tcreateNewRecord,\n\t/**\n\t * Convenience wrapper so child components (e.g. slot content) can emit\n\t * an action event without needing a direct reference to the emit function.\n\t */\n\temitAction: (name: string, data?: Record<string, any>) => {\n\t\temit('action', {\n\t\t\tname,\n\t\t\tdoctype: currentDoctype.value,\n\t\t\trecordId: currentRecordId.value,\n\t\t\tdata: data ?? currentViewData.value ?? {},\n\t\t})\n\t},\n}\n\nprovide('desktopMethods', desktopMethods)\n\n// Provide a navigator for AFormLink so the arrow button navigates to the linked record.\nprovide('aformLinkNavigator', {\n\tnavigate: (doctype: string, id: string | number) => {\n\t\tvoid doNavigate({ view: 'record', doctype, recordId: String(id) })\n\t},\n} satisfies AFormLinkNavigator)\n\n// Provide a resolver for AFormLink to look up display text by doctype + id.\n// Checks HST first (sync); falls back to an async client fetch if not cached.\n// Uses the target doctype's declared displayField — no heuristic field guessing.\nprovide('aformLinkResolver', async (doctypeSlug: string, id: string): Promise<string | undefined> => {\n\tif (!stonecrop.value) return undefined\n\ttry {\n\t\tconst meta = await stonecrop.value.getMeta({ path: `/${doctypeSlug}`, segments: [doctypeSlug] })\n\t\tconst displayField = meta?.displayField\n\t\tif (!displayField) return undefined\n\n\t\tconst readDisplay = (rec: Record<string, unknown> | undefined): string | undefined => {\n\t\t\tif (!rec) return undefined\n\t\t\tconst val = rec[displayField]\n\t\t\treturn typeof val === 'string' || typeof val === 'number' ? String(val) : undefined\n\t\t}\n\n\t\tconst cached = stonecrop.value.getRecordById(doctypeSlug, id)?.get('') as Record<string, unknown> | undefined\n\t\tconst cachedDisplay = readDisplay(cached)\n\t\tif (cachedDisplay != null) return cachedDisplay\n\n\t\tawait stonecrop.value.getRecord(doctypeSlug, id)\n\t\tconst fetched = stonecrop.value.getRecordById(doctypeSlug, id)?.get('') as Record<string, unknown> | undefined\n\t\treturn readDisplay(fetched)\n\t} catch {\n\t\treturn undefined\n\t}\n})\n\nconst handleKeydown = (event: KeyboardEvent) => {\n\tif ((event.ctrlKey || event.metaKey) && event.key === 'k') {\n\t\tevent.preventDefault()\n\t\tcommandPaletteOpen.value = true\n\t}\n\tif (event.key === 'Escape') {\n\t\tif (commandPaletteOpen.value) {\n\t\t\tcommandPaletteOpen.value = false\n\t\t\treturn\n\t\t}\n\t\tif (actionSetController.isPreviewOpen.value) {\n\t\t\tactionSetController.closePreview()\n\t\t\treturn\n\t\t}\n\t\tif (actionSetController.isDrawerOpen.value) {\n\t\t\tactionSetController.close()\n\t\t}\n\t}\n}\n\nconst visibleActionSetSlots = computed(() =>\n\t(actionSetSlots ?? [])\n\t\t.filter(slot => slot.show !== false)\n\t\t.map(slot =>\n\t\t\tObject.assign({}, slot, {\n\t\t\t\ticon: slot.icon ? markRaw(slot.icon) : undefined,\n\t\t\t\tcomponent: slot.component ? markRaw(slot.component) : undefined,\n\t\t\t})\n\t\t)\n)\n\nconst actionSetController = createActionSet({ doctype: currentDoctype, recordId: currentRecordId })\nprovide(actionSetKey, actionSetController)\n\nconst actionSetDrawerOpen = computed(() => actionSetController.isDrawerOpen.value)\nconst actionSetPreviewOpen = computed(() => actionSetController.isPreviewOpen.value)\nconst actionSetPreviewSubject = computed(() => actionSetController.previewSubject.value)\n\nwatch([currentDoctype, currentRecordId], () => {\n\tactionSetController.close()\n})\n\nfunction closeActionSetPreview() {\n\tactionSetController.closePreview()\n}\n\nonMounted(() => {\n\tdocument.addEventListener('keydown', handleKeydown)\n})\n\nonUnmounted(() => {\n\tdocument.removeEventListener('keydown', handleKeydown)\n})\n</script>\n\n<style scoped>\n.desktop {\n\tposition: relative;\n\theight: 100%;\n\tdisplay: flex;\n\tflex-direction: column;\n\tmin-height: 0;\n\toverflow: hidden;\n}\n\n.desktop__workspace {\n\tdisplay: flex;\n\tflex: 1;\n\tmin-height: 0;\n\tmin-width: 0;\n\tmargin-right: 0;\n\ttransition: margin-right 0.25s ease-out;\n}\n\n.desktop--action-set-open .desktop__workspace {\n\tmargin-right: var(--sc-action-set-drawer-width);\n}\n\n/* SheetNav is fixed to the viewport, so the workspace margin does not move it clear of the drawer. */\n.desktop__sheetnav {\n\ttransition: right 0.25s ease-out;\n}\n\n.desktop--action-set-open .desktop__sheetnav {\n\tright: var(--sc-action-set-drawer-width);\n}\n\n.desktop__main {\n\tflex: 1;\n\tmin-width: 0;\n\tmin-height: 0;\n\toverflow: auto;\n}\n\n.desktop--preview-open .desktop__main {\n\tflex: 0 0 calc(50% - 10px);\n\tmax-width: calc(50% - 10px);\n}\n\n.desktop__preview {\n\tflex: 0 0 50%;\n\tmax-width: 50%;\n\tmin-width: 0;\n\tmin-height: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tborder-left: 1px solid var(--sc-gray-20);\n\tbackground: var(--sc-form-background);\n\toverflow: hidden;\n}\n\n.desktop__preview-header {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\talign-items: center;\n\theight: 40px;\n\tpadding: 0 8px;\n\tborder-bottom: 1px solid var(--sc-gray-20);\n\tflex-shrink: 0;\n}\n\n.desktop__preview-close {\n\tborder: none;\n\tbackground: transparent;\n\tfont-size: 1.25rem;\n\tline-height: 1;\n\tcursor: pointer;\n\tcolor: var(--sc-gray-60);\n}\n\n.desktop__preview-body {\n\tflex: 1;\n\tmin-height: 0;\n\toverflow: auto;\n}\n\n.loading {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tmin-height: 50vh;\n\tcolor: var(--sc-gray-60);\n}\n</style>\n","import { App, type Plugin } from 'vue'\n\nimport CommandPalette from '../components/CommandPalette.vue'\nimport Desktop from '../components/Desktop.vue'\nimport SheetNav from '../components/SheetNav.vue'\n\n/**\n * This is the main plugin that will be used to register all the desktop components.\n * @public\n */\nconst plugin: Plugin = {\n\tinstall: (app: App) => {\n\t\tapp.component('CommandPalette', CommandPalette)\n\t\tapp.component('Desktop', Desktop)\n\t\tapp.component('SheetNav', SheetNav)\n\t},\n}\n\nexport default plugin\n"],"mappings":"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"}