primitive-app 1.0.2
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.
- package/LICENSE +5 -0
- package/README.md +1202 -0
- package/dist/PrimitiveNotFound.vue.d.ts +4 -0
- package/dist/PrimitiveNotFound.vue.d.ts.map +1 -0
- package/dist/components/auth/PrimitiveLogin.vue.d.ts +108 -0
- package/dist/components/auth/PrimitiveLogin.vue.d.ts.map +1 -0
- package/dist/components/auth/PrimitiveLogout.vue.d.ts +32 -0
- package/dist/components/auth/PrimitiveLogout.vue.d.ts.map +1 -0
- package/dist/components/auth/PrimitiveOauthCallback.vue.d.ts +39 -0
- package/dist/components/auth/PrimitiveOauthCallback.vue.d.ts.map +1 -0
- package/dist/components/debug-suite/EphemeralDocService.d.ts +46 -0
- package/dist/components/debug-suite/EphemeralDocService.d.ts.map +1 -0
- package/dist/components/debug-suite/PrimitiveDebuggingSuite.vue.d.ts +15 -0
- package/dist/components/debug-suite/PrimitiveDebuggingSuite.vue.d.ts.map +1 -0
- package/dist/components/debug-suite/PrimitiveTestRunner.vue.d.ts +19 -0
- package/dist/components/debug-suite/PrimitiveTestRunner.vue.d.ts.map +1 -0
- package/dist/components/debug-suite/document-debugger/DocumentDebuggerIndex.vue.d.ts +4 -0
- package/dist/components/debug-suite/document-debugger/DocumentDebuggerIndex.vue.d.ts.map +1 -0
- package/dist/components/debug-suite/document-debugger/DocumentDebuggerModel.vue.d.ts +4 -0
- package/dist/components/debug-suite/document-debugger/DocumentDebuggerModel.vue.d.ts.map +1 -0
- package/dist/components/debug-suite/document-debugger/DocumentSidebar.vue.d.ts +48 -0
- package/dist/components/debug-suite/document-debugger/DocumentSidebar.vue.d.ts.map +1 -0
- package/dist/components/debug-suite/document-debugger/dialogs/MassDeleteDocsDialog.vue.d.ts +28 -0
- package/dist/components/debug-suite/document-debugger/dialogs/MassDeleteDocsDialog.vue.d.ts.map +1 -0
- package/dist/components/debug-suite/document-debugger/dialogs/MassDeleteRecordsDialog.vue.d.ts +32 -0
- package/dist/components/debug-suite/document-debugger/dialogs/MassDeleteRecordsDialog.vue.d.ts.map +1 -0
- package/dist/components/debug-suite/document-debugger/types.d.ts +67 -0
- package/dist/components/debug-suite/document-debugger/types.d.ts.map +1 -0
- package/dist/components/debug-suite/document-debugger/utils.d.ts +38 -0
- package/dist/components/debug-suite/document-debugger/utils.d.ts.map +1 -0
- package/dist/components/debug-suite/pages/DebugSuiteLayout.vue.d.ts +15 -0
- package/dist/components/debug-suite/pages/DebugSuiteLayout.vue.d.ts.map +1 -0
- package/dist/components/debug-suite/pages/DebuggingSuiteDocuments.vue.d.ts +4 -0
- package/dist/components/debug-suite/pages/DebuggingSuiteDocuments.vue.d.ts.map +1 -0
- package/dist/components/debug-suite/pages/DebuggingSuiteDocumentsModel.vue.d.ts +4 -0
- package/dist/components/debug-suite/pages/DebuggingSuiteDocumentsModel.vue.d.ts.map +1 -0
- package/dist/components/debug-suite/pages/DebuggingSuiteHome.vue.d.ts +4 -0
- package/dist/components/debug-suite/pages/DebuggingSuiteHome.vue.d.ts.map +1 -0
- package/dist/components/debug-suite/pages/DebuggingSuiteTests.vue.d.ts +4 -0
- package/dist/components/debug-suite/pages/DebuggingSuiteTests.vue.d.ts.map +1 -0
- package/dist/components/debug-suite/testTypes.d.ts +18 -0
- package/dist/components/debug-suite/testTypes.d.ts.map +1 -0
- package/dist/components/documents/PrimitiveManageDocuments.vue.d.ts +4 -0
- package/dist/components/documents/PrimitiveManageDocuments.vue.d.ts.map +1 -0
- package/dist/components/documents/PrimitiveShareDocumentDialog.vue.d.ts +26 -0
- package/dist/components/documents/PrimitiveShareDocumentDialog.vue.d.ts.map +1 -0
- package/dist/components/documents/PrimitiveSingleDocumentSwitcher.vue.d.ts +4 -0
- package/dist/components/documents/PrimitiveSingleDocumentSwitcher.vue.d.ts.map +1 -0
- package/dist/components/navigation/PrimitiveAppBreadcrumb.vue.d.ts +4 -0
- package/dist/components/navigation/PrimitiveAppBreadcrumb.vue.d.ts.map +1 -0
- package/dist/components/navigation/PrimitiveAppSidebarHeader.vue.d.ts +4 -0
- package/dist/components/navigation/PrimitiveAppSidebarHeader.vue.d.ts.map +1 -0
- package/dist/components/navigation/PrimitiveBottomNav.vue.d.ts +4 -0
- package/dist/components/navigation/PrimitiveBottomNav.vue.d.ts.map +1 -0
- package/dist/components/navigation/PrimitiveNavigationBadge.vue.d.ts +14 -0
- package/dist/components/navigation/PrimitiveNavigationBadge.vue.d.ts.map +1 -0
- package/dist/components/navigation/PrimitiveSidebarNav.vue.d.ts +4 -0
- package/dist/components/navigation/PrimitiveSidebarNav.vue.d.ts.map +1 -0
- package/dist/components/navigation/PrimitiveUserMenu.vue.d.ts +35 -0
- package/dist/components/navigation/PrimitiveUserMenu.vue.d.ts.map +1 -0
- package/dist/components/shared/DeleteConfirmationDialog.vue.d.ts +55 -0
- package/dist/components/shared/DeleteConfirmationDialog.vue.d.ts.map +1 -0
- package/dist/components/shared/PrimitiveLogoSpinner.vue.d.ts +22 -0
- package/dist/components/shared/PrimitiveLogoSpinner.vue.d.ts.map +1 -0
- package/dist/components/shared/PrimitiveSkeletonGate.vue.d.ts +34 -0
- package/dist/components/shared/PrimitiveSkeletonGate.vue.d.ts.map +1 -0
- package/dist/components/ui/avatar/Avatar.vue.d.ts +18 -0
- package/dist/components/ui/avatar/Avatar.vue.d.ts.map +1 -0
- package/dist/components/ui/avatar/AvatarFallback.vue.d.ts +19 -0
- package/dist/components/ui/avatar/AvatarFallback.vue.d.ts.map +1 -0
- package/dist/components/ui/avatar/AvatarImage.vue.d.ts +15 -0
- package/dist/components/ui/avatar/AvatarImage.vue.d.ts.map +1 -0
- package/dist/components/ui/avatar/index.d.ts +4 -0
- package/dist/components/ui/avatar/index.d.ts.map +1 -0
- package/dist/components/ui/badge/Badge.vue.d.ts +21 -0
- package/dist/components/ui/badge/Badge.vue.d.ts.map +1 -0
- package/dist/components/ui/badge/index.d.ts +7 -0
- package/dist/components/ui/badge/index.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb/Breadcrumb.vue.d.ts +18 -0
- package/dist/components/ui/breadcrumb/Breadcrumb.vue.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb/BreadcrumbEllipsis.vue.d.ts +18 -0
- package/dist/components/ui/breadcrumb/BreadcrumbEllipsis.vue.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb/BreadcrumbItem.vue.d.ts +18 -0
- package/dist/components/ui/breadcrumb/BreadcrumbItem.vue.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb/BreadcrumbLink.vue.d.ts +21 -0
- package/dist/components/ui/breadcrumb/BreadcrumbLink.vue.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb/BreadcrumbList.vue.d.ts +18 -0
- package/dist/components/ui/breadcrumb/BreadcrumbList.vue.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb/BreadcrumbPage.vue.d.ts +18 -0
- package/dist/components/ui/breadcrumb/BreadcrumbPage.vue.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb/BreadcrumbSeparator.vue.d.ts +18 -0
- package/dist/components/ui/breadcrumb/BreadcrumbSeparator.vue.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb/index.d.ts +8 -0
- package/dist/components/ui/breadcrumb/index.d.ts.map +1 -0
- package/dist/components/ui/button/Button.vue.d.ts +24 -0
- package/dist/components/ui/button/Button.vue.d.ts.map +1 -0
- package/dist/components/ui/button/index.d.ts +8 -0
- package/dist/components/ui/button/index.d.ts.map +1 -0
- package/dist/components/ui/calendar/Calendar.vue.d.ts +43 -0
- package/dist/components/ui/calendar/Calendar.vue.d.ts.map +1 -0
- package/dist/components/ui/calendar/CalendarCell.vue.d.ts +19 -0
- package/dist/components/ui/calendar/CalendarCell.vue.d.ts.map +1 -0
- package/dist/components/ui/calendar/CalendarCellTrigger.vue.d.ts +21 -0
- package/dist/components/ui/calendar/CalendarCellTrigger.vue.d.ts.map +1 -0
- package/dist/components/ui/calendar/CalendarGrid.vue.d.ts +19 -0
- package/dist/components/ui/calendar/CalendarGrid.vue.d.ts.map +1 -0
- package/dist/components/ui/calendar/CalendarGridBody.vue.d.ts +15 -0
- package/dist/components/ui/calendar/CalendarGridBody.vue.d.ts.map +1 -0
- package/dist/components/ui/calendar/CalendarGridHead.vue.d.ts +19 -0
- package/dist/components/ui/calendar/CalendarGridHead.vue.d.ts.map +1 -0
- package/dist/components/ui/calendar/CalendarGridRow.vue.d.ts +19 -0
- package/dist/components/ui/calendar/CalendarGridRow.vue.d.ts.map +1 -0
- package/dist/components/ui/calendar/CalendarHeadCell.vue.d.ts +19 -0
- package/dist/components/ui/calendar/CalendarHeadCell.vue.d.ts.map +1 -0
- package/dist/components/ui/calendar/CalendarHeader.vue.d.ts +19 -0
- package/dist/components/ui/calendar/CalendarHeader.vue.d.ts.map +1 -0
- package/dist/components/ui/calendar/CalendarHeading.vue.d.ts +20 -0
- package/dist/components/ui/calendar/CalendarHeading.vue.d.ts.map +1 -0
- package/dist/components/ui/calendar/CalendarNextButton.vue.d.ts +19 -0
- package/dist/components/ui/calendar/CalendarNextButton.vue.d.ts.map +1 -0
- package/dist/components/ui/calendar/CalendarPrevButton.vue.d.ts +19 -0
- package/dist/components/ui/calendar/CalendarPrevButton.vue.d.ts.map +1 -0
- package/dist/components/ui/calendar/index.d.ts +14 -0
- package/dist/components/ui/calendar/index.d.ts.map +1 -0
- package/dist/components/ui/card/Card.vue.d.ts +18 -0
- package/dist/components/ui/card/Card.vue.d.ts.map +1 -0
- package/dist/components/ui/card/CardAction.vue.d.ts +18 -0
- package/dist/components/ui/card/CardAction.vue.d.ts.map +1 -0
- package/dist/components/ui/card/CardContent.vue.d.ts +18 -0
- package/dist/components/ui/card/CardContent.vue.d.ts.map +1 -0
- package/dist/components/ui/card/CardDescription.vue.d.ts +18 -0
- package/dist/components/ui/card/CardDescription.vue.d.ts.map +1 -0
- package/dist/components/ui/card/CardFooter.vue.d.ts +18 -0
- package/dist/components/ui/card/CardFooter.vue.d.ts.map +1 -0
- package/dist/components/ui/card/CardHeader.vue.d.ts +18 -0
- package/dist/components/ui/card/CardHeader.vue.d.ts.map +1 -0
- package/dist/components/ui/card/CardTitle.vue.d.ts +18 -0
- package/dist/components/ui/card/CardTitle.vue.d.ts.map +1 -0
- package/dist/components/ui/card/index.d.ts +8 -0
- package/dist/components/ui/card/index.d.ts.map +1 -0
- package/dist/components/ui/carousel/Carousel.vue.d.ts +38 -0
- package/dist/components/ui/carousel/Carousel.vue.d.ts.map +1 -0
- package/dist/components/ui/carousel/CarouselContent.vue.d.ts +15 -0
- package/dist/components/ui/carousel/CarouselContent.vue.d.ts.map +1 -0
- package/dist/components/ui/carousel/CarouselItem.vue.d.ts +15 -0
- package/dist/components/ui/carousel/CarouselItem.vue.d.ts.map +1 -0
- package/dist/components/ui/carousel/CarouselNext.vue.d.ts +23 -0
- package/dist/components/ui/carousel/CarouselNext.vue.d.ts.map +1 -0
- package/dist/components/ui/carousel/CarouselPrevious.vue.d.ts +23 -0
- package/dist/components/ui/carousel/CarouselPrevious.vue.d.ts.map +1 -0
- package/dist/components/ui/carousel/index.d.ts +8 -0
- package/dist/components/ui/carousel/index.d.ts.map +1 -0
- package/dist/components/ui/carousel/interface.d.ts +21 -0
- package/dist/components/ui/carousel/interface.d.ts.map +1 -0
- package/dist/components/ui/carousel/useCarousel.d.ts +21 -0
- package/dist/components/ui/carousel/useCarousel.d.ts.map +1 -0
- package/dist/components/ui/checkbox/Checkbox.vue.d.ts +26 -0
- package/dist/components/ui/checkbox/Checkbox.vue.d.ts.map +1 -0
- package/dist/components/ui/checkbox/index.d.ts +2 -0
- package/dist/components/ui/checkbox/index.d.ts.map +1 -0
- package/dist/components/ui/collapsible/Collapsible.vue.d.ts +21 -0
- package/dist/components/ui/collapsible/Collapsible.vue.d.ts.map +1 -0
- package/dist/components/ui/collapsible/CollapsibleContent.vue.d.ts +15 -0
- package/dist/components/ui/collapsible/CollapsibleContent.vue.d.ts.map +1 -0
- package/dist/components/ui/collapsible/CollapsibleTrigger.vue.d.ts +15 -0
- package/dist/components/ui/collapsible/CollapsibleTrigger.vue.d.ts.map +1 -0
- package/dist/components/ui/collapsible/index.d.ts +4 -0
- package/dist/components/ui/collapsible/index.d.ts.map +1 -0
- package/dist/components/ui/dialog/Dialog.vue.d.ts +22 -0
- package/dist/components/ui/dialog/Dialog.vue.d.ts.map +1 -0
- package/dist/components/ui/dialog/DialogClose.vue.d.ts +15 -0
- package/dist/components/ui/dialog/DialogClose.vue.d.ts.map +1 -0
- package/dist/components/ui/dialog/DialogContent.vue.d.ts +36 -0
- package/dist/components/ui/dialog/DialogContent.vue.d.ts.map +1 -0
- package/dist/components/ui/dialog/DialogDescription.vue.d.ts +19 -0
- package/dist/components/ui/dialog/DialogDescription.vue.d.ts.map +1 -0
- package/dist/components/ui/dialog/DialogFooter.vue.d.ts +18 -0
- package/dist/components/ui/dialog/DialogFooter.vue.d.ts.map +1 -0
- package/dist/components/ui/dialog/DialogHeader.vue.d.ts +18 -0
- package/dist/components/ui/dialog/DialogHeader.vue.d.ts.map +1 -0
- package/dist/components/ui/dialog/DialogOverlay.vue.d.ts +19 -0
- package/dist/components/ui/dialog/DialogOverlay.vue.d.ts.map +1 -0
- package/dist/components/ui/dialog/DialogScrollContent.vue.d.ts +33 -0
- package/dist/components/ui/dialog/DialogScrollContent.vue.d.ts.map +1 -0
- package/dist/components/ui/dialog/DialogTitle.vue.d.ts +19 -0
- package/dist/components/ui/dialog/DialogTitle.vue.d.ts.map +1 -0
- package/dist/components/ui/dialog/DialogTrigger.vue.d.ts +15 -0
- package/dist/components/ui/dialog/DialogTrigger.vue.d.ts.map +1 -0
- package/dist/components/ui/dialog/index.d.ts +11 -0
- package/dist/components/ui/dialog/index.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu/DropdownMenu.vue.d.ts +21 -0
- package/dist/components/ui/dropdown-menu/DropdownMenu.vue.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue.d.ts +27 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuContent.vue.d.ts +33 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuContent.vue.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuGroup.vue.d.ts +15 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuGroup.vue.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuItem.vue.d.ts +23 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuItem.vue.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuLabel.vue.d.ts +20 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuLabel.vue.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue.d.ts +19 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuRadioItem.vue.d.ts +25 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuRadioItem.vue.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuSeparator.vue.d.ts +9 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuSeparator.vue.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuShortcut.vue.d.ts +18 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuShortcut.vue.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuSub.vue.d.ts +21 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuSub.vue.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuSubContent.vue.d.ts +35 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuSubContent.vue.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue.d.ts +20 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuTrigger.vue.d.ts +15 -0
- package/dist/components/ui/dropdown-menu/DropdownMenuTrigger.vue.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu/index.d.ts +16 -0
- package/dist/components/ui/dropdown-menu/index.d.ts.map +1 -0
- package/dist/components/ui/empty/Empty.vue.d.ts +18 -0
- package/dist/components/ui/empty/Empty.vue.d.ts.map +1 -0
- package/dist/components/ui/empty/EmptyContent.vue.d.ts +18 -0
- package/dist/components/ui/empty/EmptyContent.vue.d.ts.map +1 -0
- package/dist/components/ui/empty/EmptyDescription.vue.d.ts +18 -0
- package/dist/components/ui/empty/EmptyDescription.vue.d.ts.map +1 -0
- package/dist/components/ui/empty/EmptyHeader.vue.d.ts +18 -0
- package/dist/components/ui/empty/EmptyHeader.vue.d.ts.map +1 -0
- package/dist/components/ui/empty/EmptyMedia.vue.d.ts +20 -0
- package/dist/components/ui/empty/EmptyMedia.vue.d.ts.map +1 -0
- package/dist/components/ui/empty/EmptyTitle.vue.d.ts +18 -0
- package/dist/components/ui/empty/EmptyTitle.vue.d.ts.map +1 -0
- package/dist/components/ui/empty/index.d.ts +12 -0
- package/dist/components/ui/empty/index.d.ts.map +1 -0
- package/dist/components/ui/input/Input.vue.d.ts +14 -0
- package/dist/components/ui/input/Input.vue.d.ts.map +1 -0
- package/dist/components/ui/input/index.d.ts +2 -0
- package/dist/components/ui/input/index.d.ts.map +1 -0
- package/dist/components/ui/label/Label.vue.d.ts +19 -0
- package/dist/components/ui/label/Label.vue.d.ts.map +1 -0
- package/dist/components/ui/label/index.d.ts +2 -0
- package/dist/components/ui/label/index.d.ts.map +1 -0
- package/dist/components/ui/native-select/NativeSelect.vue.d.ts +24 -0
- package/dist/components/ui/native-select/NativeSelect.vue.d.ts.map +1 -0
- package/dist/components/ui/native-select/NativeSelectOptGroup.vue.d.ts +402 -0
- package/dist/components/ui/native-select/NativeSelectOptGroup.vue.d.ts.map +1 -0
- package/dist/components/ui/native-select/NativeSelectOption.vue.d.ts +406 -0
- package/dist/components/ui/native-select/NativeSelectOption.vue.d.ts.map +1 -0
- package/dist/components/ui/native-select/index.d.ts +4 -0
- package/dist/components/ui/native-select/index.d.ts.map +1 -0
- package/dist/components/ui/popover/Popover.vue.d.ts +22 -0
- package/dist/components/ui/popover/Popover.vue.d.ts.map +1 -0
- package/dist/components/ui/popover/PopoverAnchor.vue.d.ts +15 -0
- package/dist/components/ui/popover/PopoverAnchor.vue.d.ts.map +1 -0
- package/dist/components/ui/popover/PopoverContent.vue.d.ts +36 -0
- package/dist/components/ui/popover/PopoverContent.vue.d.ts.map +1 -0
- package/dist/components/ui/popover/PopoverTrigger.vue.d.ts +15 -0
- package/dist/components/ui/popover/PopoverTrigger.vue.d.ts.map +1 -0
- package/dist/components/ui/popover/index.d.ts +5 -0
- package/dist/components/ui/popover/index.d.ts.map +1 -0
- package/dist/components/ui/select/Select.vue.d.ts +25 -0
- package/dist/components/ui/select/Select.vue.d.ts.map +1 -0
- package/dist/components/ui/select/SelectContent.vue.d.ts +29 -0
- package/dist/components/ui/select/SelectContent.vue.d.ts.map +1 -0
- package/dist/components/ui/select/SelectGroup.vue.d.ts +15 -0
- package/dist/components/ui/select/SelectGroup.vue.d.ts.map +1 -0
- package/dist/components/ui/select/SelectItem.vue.d.ts +21 -0
- package/dist/components/ui/select/SelectItem.vue.d.ts.map +1 -0
- package/dist/components/ui/select/SelectItemText.vue.d.ts +15 -0
- package/dist/components/ui/select/SelectItemText.vue.d.ts.map +1 -0
- package/dist/components/ui/select/SelectLabel.vue.d.ts +19 -0
- package/dist/components/ui/select/SelectLabel.vue.d.ts.map +1 -0
- package/dist/components/ui/select/SelectScrollDownButton.vue.d.ts +19 -0
- package/dist/components/ui/select/SelectScrollDownButton.vue.d.ts.map +1 -0
- package/dist/components/ui/select/SelectScrollUpButton.vue.d.ts +19 -0
- package/dist/components/ui/select/SelectScrollUpButton.vue.d.ts.map +1 -0
- package/dist/components/ui/select/SelectSeparator.vue.d.ts +9 -0
- package/dist/components/ui/select/SelectSeparator.vue.d.ts.map +1 -0
- package/dist/components/ui/select/SelectTrigger.vue.d.ts +22 -0
- package/dist/components/ui/select/SelectTrigger.vue.d.ts.map +1 -0
- package/dist/components/ui/select/SelectValue.vue.d.ts +15 -0
- package/dist/components/ui/select/SelectValue.vue.d.ts.map +1 -0
- package/dist/components/ui/select/index.d.ts +12 -0
- package/dist/components/ui/select/index.d.ts.map +1 -0
- package/dist/components/ui/separator/Separator.vue.d.ts +12 -0
- package/dist/components/ui/separator/Separator.vue.d.ts.map +1 -0
- package/dist/components/ui/separator/index.d.ts +2 -0
- package/dist/components/ui/separator/index.d.ts.map +1 -0
- package/dist/components/ui/sheet/Sheet.vue.d.ts +22 -0
- package/dist/components/ui/sheet/Sheet.vue.d.ts.map +1 -0
- package/dist/components/ui/sheet/SheetClose.vue.d.ts +15 -0
- package/dist/components/ui/sheet/SheetClose.vue.d.ts.map +1 -0
- package/dist/components/ui/sheet/SheetContent.vue.d.ts +36 -0
- package/dist/components/ui/sheet/SheetContent.vue.d.ts.map +1 -0
- package/dist/components/ui/sheet/SheetDescription.vue.d.ts +19 -0
- package/dist/components/ui/sheet/SheetDescription.vue.d.ts.map +1 -0
- package/dist/components/ui/sheet/SheetFooter.vue.d.ts +18 -0
- package/dist/components/ui/sheet/SheetFooter.vue.d.ts.map +1 -0
- package/dist/components/ui/sheet/SheetHeader.vue.d.ts +18 -0
- package/dist/components/ui/sheet/SheetHeader.vue.d.ts.map +1 -0
- package/dist/components/ui/sheet/SheetOverlay.vue.d.ts +19 -0
- package/dist/components/ui/sheet/SheetOverlay.vue.d.ts.map +1 -0
- package/dist/components/ui/sheet/SheetTitle.vue.d.ts +19 -0
- package/dist/components/ui/sheet/SheetTitle.vue.d.ts.map +1 -0
- package/dist/components/ui/sheet/SheetTrigger.vue.d.ts +15 -0
- package/dist/components/ui/sheet/SheetTrigger.vue.d.ts.map +1 -0
- package/dist/components/ui/sheet/index.d.ts +9 -0
- package/dist/components/ui/sheet/index.d.ts.map +1 -0
- package/dist/components/ui/sidebar/Sidebar.vue.d.ts +23 -0
- package/dist/components/ui/sidebar/Sidebar.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarContent.vue.d.ts +18 -0
- package/dist/components/ui/sidebar/SidebarContent.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarFooter.vue.d.ts +18 -0
- package/dist/components/ui/sidebar/SidebarFooter.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarGroup.vue.d.ts +18 -0
- package/dist/components/ui/sidebar/SidebarGroup.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarGroupAction.vue.d.ts +19 -0
- package/dist/components/ui/sidebar/SidebarGroupAction.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarGroupContent.vue.d.ts +18 -0
- package/dist/components/ui/sidebar/SidebarGroupContent.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarGroupLabel.vue.d.ts +19 -0
- package/dist/components/ui/sidebar/SidebarGroupLabel.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarHeader.vue.d.ts +18 -0
- package/dist/components/ui/sidebar/SidebarHeader.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarInput.vue.d.ts +18 -0
- package/dist/components/ui/sidebar/SidebarInput.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarInset.vue.d.ts +18 -0
- package/dist/components/ui/sidebar/SidebarInset.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarMenu.vue.d.ts +18 -0
- package/dist/components/ui/sidebar/SidebarMenu.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarMenuAction.vue.d.ts +22 -0
- package/dist/components/ui/sidebar/SidebarMenuAction.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarMenuBadge.vue.d.ts +18 -0
- package/dist/components/ui/sidebar/SidebarMenuBadge.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarMenuButton.vue.d.ts +25 -0
- package/dist/components/ui/sidebar/SidebarMenuButton.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarMenuButtonChild.vue.d.ts +27 -0
- package/dist/components/ui/sidebar/SidebarMenuButtonChild.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarMenuItem.vue.d.ts +18 -0
- package/dist/components/ui/sidebar/SidebarMenuItem.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarMenuSkeleton.vue.d.ts +9 -0
- package/dist/components/ui/sidebar/SidebarMenuSkeleton.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarMenuSub.vue.d.ts +18 -0
- package/dist/components/ui/sidebar/SidebarMenuSub.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarMenuSubButton.vue.d.ts +24 -0
- package/dist/components/ui/sidebar/SidebarMenuSubButton.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarMenuSubItem.vue.d.ts +18 -0
- package/dist/components/ui/sidebar/SidebarMenuSubItem.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarProvider.vue.d.ts +27 -0
- package/dist/components/ui/sidebar/SidebarProvider.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarRail.vue.d.ts +18 -0
- package/dist/components/ui/sidebar/SidebarRail.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarSeparator.vue.d.ts +18 -0
- package/dist/components/ui/sidebar/SidebarSeparator.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/SidebarTrigger.vue.d.ts +8 -0
- package/dist/components/ui/sidebar/SidebarTrigger.vue.d.ts.map +1 -0
- package/dist/components/ui/sidebar/index.d.ts +38 -0
- package/dist/components/ui/sidebar/index.d.ts.map +1 -0
- package/dist/components/ui/sidebar/utils.d.ts +57 -0
- package/dist/components/ui/sidebar/utils.d.ts.map +1 -0
- package/dist/components/ui/skeleton/Skeleton.vue.d.ts +8 -0
- package/dist/components/ui/skeleton/Skeleton.vue.d.ts.map +1 -0
- package/dist/components/ui/skeleton/index.d.ts +2 -0
- package/dist/components/ui/skeleton/index.d.ts.map +1 -0
- package/dist/components/ui/switch/Switch.vue.d.ts +25 -0
- package/dist/components/ui/switch/Switch.vue.d.ts.map +1 -0
- package/dist/components/ui/switch/index.d.ts +2 -0
- package/dist/components/ui/switch/index.d.ts.map +1 -0
- package/dist/components/ui/table/Table.vue.d.ts +18 -0
- package/dist/components/ui/table/Table.vue.d.ts.map +1 -0
- package/dist/components/ui/table/TableBody.vue.d.ts +18 -0
- package/dist/components/ui/table/TableBody.vue.d.ts.map +1 -0
- package/dist/components/ui/table/TableCaption.vue.d.ts +18 -0
- package/dist/components/ui/table/TableCaption.vue.d.ts.map +1 -0
- package/dist/components/ui/table/TableCell.vue.d.ts +18 -0
- package/dist/components/ui/table/TableCell.vue.d.ts.map +1 -0
- package/dist/components/ui/table/TableEmpty.vue.d.ts +21 -0
- package/dist/components/ui/table/TableEmpty.vue.d.ts.map +1 -0
- package/dist/components/ui/table/TableFooter.vue.d.ts +18 -0
- package/dist/components/ui/table/TableFooter.vue.d.ts.map +1 -0
- package/dist/components/ui/table/TableHead.vue.d.ts +18 -0
- package/dist/components/ui/table/TableHead.vue.d.ts.map +1 -0
- package/dist/components/ui/table/TableHeader.vue.d.ts +18 -0
- package/dist/components/ui/table/TableHeader.vue.d.ts.map +1 -0
- package/dist/components/ui/table/TableRow.vue.d.ts +18 -0
- package/dist/components/ui/table/TableRow.vue.d.ts.map +1 -0
- package/dist/components/ui/table/index.d.ts +10 -0
- package/dist/components/ui/table/index.d.ts.map +1 -0
- package/dist/components/ui/table/utils.d.ts +4 -0
- package/dist/components/ui/table/utils.d.ts.map +1 -0
- package/dist/components/ui/textarea/Textarea.vue.d.ts +14 -0
- package/dist/components/ui/textarea/Textarea.vue.d.ts.map +1 -0
- package/dist/components/ui/textarea/index.d.ts +2 -0
- package/dist/components/ui/textarea/index.d.ts.map +1 -0
- package/dist/components/ui/tooltip/Tooltip.vue.d.ts +21 -0
- package/dist/components/ui/tooltip/Tooltip.vue.d.ts.map +1 -0
- package/dist/components/ui/tooltip/TooltipContent.vue.d.ts +27 -0
- package/dist/components/ui/tooltip/TooltipContent.vue.d.ts.map +1 -0
- package/dist/components/ui/tooltip/TooltipProvider.vue.d.ts +17 -0
- package/dist/components/ui/tooltip/TooltipProvider.vue.d.ts.map +1 -0
- package/dist/components/ui/tooltip/TooltipTrigger.vue.d.ts +15 -0
- package/dist/components/ui/tooltip/TooltipTrigger.vue.d.ts.map +1 -0
- package/dist/components/ui/tooltip/index.d.ts +5 -0
- package/dist/components/ui/tooltip/index.d.ts.map +1 -0
- package/dist/composables/useJsBaoDataLoader.d.ts +79 -0
- package/dist/composables/useJsBaoDataLoader.d.ts.map +1 -0
- package/dist/composables/useTheme.d.ts +34 -0
- package/dist/composables/useTheme.d.ts.map +1 -0
- package/dist/createPrimitiveApp.d.ts +74 -0
- package/dist/createPrimitiveApp.d.ts.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29781 -0
- package/dist/index.umd.cjs +8 -0
- package/dist/layouts/PrimitiveAppLayout.vue.d.ts +34 -0
- package/dist/layouts/PrimitiveAppLayout.vue.d.ts.map +1 -0
- package/dist/layouts/PrimitiveLoginLayout.vue.d.ts +24 -0
- package/dist/layouts/PrimitiveLoginLayout.vue.d.ts.map +1 -0
- package/dist/layouts/PrimitiveStaticLayout.vue.d.ts +17 -0
- package/dist/layouts/PrimitiveStaticLayout.vue.d.ts.map +1 -0
- package/dist/lib/logger.d.ts +20 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/routeOrUrl.d.ts +25 -0
- package/dist/lib/routeOrUrl.d.ts.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/models/UserPref.d.ts +10 -0
- package/dist/models/UserPref.d.ts.map +1 -0
- package/dist/models/generated/index.d.ts +3 -0
- package/dist/models/generated/index.d.ts.map +1 -0
- package/dist/models/generated/relationships.d.ts +2 -0
- package/dist/models/generated/relationships.d.ts.map +1 -0
- package/dist/pages/PrimitiveNotFound.vue.d.ts +4 -0
- package/dist/pages/PrimitiveNotFound.vue.d.ts.map +1 -0
- package/dist/router/primitiveRouter.d.ts +105 -0
- package/dist/router/primitiveRouter.d.ts.map +1 -0
- package/dist/services/JsBaoClientService.d.ts +85 -0
- package/dist/services/JsBaoClientService.d.ts.map +1 -0
- package/dist/stores/appConfigStore.d.ts +98 -0
- package/dist/stores/appConfigStore.d.ts.map +1 -0
- package/dist/stores/breadcrumbsStore.d.ts +52 -0
- package/dist/stores/breadcrumbsStore.d.ts.map +1 -0
- package/dist/stores/documentDebuggerStore.d.ts +43 -0
- package/dist/stores/documentDebuggerStore.d.ts.map +1 -0
- package/dist/stores/jsBaoDocumentsStore.d.ts +320 -0
- package/dist/stores/jsBaoDocumentsStore.d.ts.map +1 -0
- package/dist/stores/multiDocumentStore.d.ts +68 -0
- package/dist/stores/multiDocumentStore.d.ts.map +1 -0
- package/dist/stores/navigationStore.d.ts +192 -0
- package/dist/stores/navigationStore.d.ts.map +1 -0
- package/dist/stores/singleDocumentStore.d.ts +109 -0
- package/dist/stores/singleDocumentStore.d.ts.map +1 -0
- package/dist/stores/userStore.d.ts +142 -0
- package/dist/stores/userStore.d.ts.map +1 -0
- package/dist/types/app.d.ts +23 -0
- package/dist/types/app.d.ts.map +1 -0
- package/dist/types/navigation.d.ts +143 -0
- package/dist/types/navigation.d.ts.map +1 -0
- package/dist/types/router.d.ts +14 -0
- package/dist/types/router.d.ts.map +1 -0
- package/package.json +107 -0
- package/src/style.css +164 -0
package/README.md
ADDED
|
@@ -0,0 +1,1202 @@
|
|
|
1
|
+
# primitive-app
|
|
2
|
+
|
|
3
|
+
Primitive App is a **Vue 3–first helper library** that wires a Vue + Pinia + vue-router application into **js-bao-wss-client** with a cohesive set of:
|
|
4
|
+
|
|
5
|
+
- **Bootstrapping helpers** (`createPrimitiveApp`)
|
|
6
|
+
- **Routing & auth helpers** (`createPrimitiveRouter`, `PrimitiveRouterMeta`)
|
|
7
|
+
- **Stores** (app config, user, navigation, breadcrumbs, document stores)
|
|
8
|
+
- **Layouts & components** (`PrimitiveAppLayout`, auth layouts, navigation & document shells)
|
|
9
|
+
- **Data-loading composables** (`useJsBaoDataLoader`)
|
|
10
|
+
- **Debugging & test harness utilities** (Document Debugger, Test Runner)
|
|
11
|
+
|
|
12
|
+
It is designed to be used in apps that already provide:
|
|
13
|
+
|
|
14
|
+
- Vue 3
|
|
15
|
+
- Pinia
|
|
16
|
+
- vue-router
|
|
17
|
+
- Tailwind (or compatible utility CSS)
|
|
18
|
+
- `js-bao-wss-client` (and `js-bao` models in your app)
|
|
19
|
+
|
|
20
|
+
For concrete examples, see the demo app available at https://primitive-app-demo.primitive.app/
|
|
21
|
+
|
|
22
|
+
## It's recommended that apps wanting to use this library start by cloning https://github.com/Primitive-Labs/primitive-app-template
|
|
23
|
+
|
|
24
|
+
## Documentation
|
|
25
|
+
|
|
26
|
+
For guides and API reference docs, see **Primitive Docs**: https://primitive-labs.github.io/primitive-docs/
|
|
27
|
+
|
|
28
|
+
## High-level architecture
|
|
29
|
+
|
|
30
|
+
At a high level, `primitive-app` provides:
|
|
31
|
+
|
|
32
|
+
- **Bootstrapping**
|
|
33
|
+
- `createPrimitiveApp` – mount your Vue app, wire in Pinia and vue-router, set up js-bao, and initialize shared stores.
|
|
34
|
+
- **Routing & auth**
|
|
35
|
+
- `createPrimitiveRouter`, `AuthLevel`, `PrimitiveRouterMeta` – auth-guarded routes with breadcrumb metadata.
|
|
36
|
+
- **Stores**
|
|
37
|
+
- Configuration: `useAppConfigStore`, `useNavigationStore`, `useBreadcrumbsStore`
|
|
38
|
+
- User: `useUserStore`
|
|
39
|
+
- Documents: `useJsBaoDocumentsStore`, `useSingleDocumentStore`, `useMultiDocumentStore`
|
|
40
|
+
- **Layouts & components**
|
|
41
|
+
- `PrimitiveAppLayout`, `PrimitiveLoginLayout`, `PrimitiveStaticLayout`, navigation components, document management components, shared utilities.
|
|
42
|
+
- **Data loading**
|
|
43
|
+
- `useJsBaoDataLoader` – standard pattern for loading data (typically js-bao models) with subscriptions and a document readiness gate.
|
|
44
|
+
- **Debugging & test harness**
|
|
45
|
+
- `PrimitiveDebuggingSuite`, `PrimitiveTestRunner`, `DocumentDebugger`, and related pages for app-level integration tests and data inspection.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Bootstrapping with `createPrimitiveApp`
|
|
50
|
+
|
|
51
|
+
The recommended way to start a new app is to let `primitive-app` own the **initialization order**:
|
|
52
|
+
|
|
53
|
+
1. Initialize js-bao and the user store.
|
|
54
|
+
2. Initialize app config.
|
|
55
|
+
3. Wire the document store (single-document, multi-document, or none).
|
|
56
|
+
4. Wire breadcrumbs to the router.
|
|
57
|
+
5. Wire navigation to the router.
|
|
58
|
+
6. Mount your root component.
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// src/main.ts
|
|
62
|
+
import { createPrimitiveApp } from "primitive-app";
|
|
63
|
+
import App from "./App.vue";
|
|
64
|
+
import router from "./router/routes";
|
|
65
|
+
import { getAppConfig, getSingleDocumentConfig } from "./config/appConfig";
|
|
66
|
+
import { getJsBaoConfig, getLogLevel } from "./config/envConfig";
|
|
67
|
+
import { getNavigationConfig } from "./config/navigationConfig";
|
|
68
|
+
|
|
69
|
+
void createPrimitiveApp({
|
|
70
|
+
mainComponent: App,
|
|
71
|
+
router,
|
|
72
|
+
getAppConfig,
|
|
73
|
+
getJsBaoConfig,
|
|
74
|
+
getNavigationConfig,
|
|
75
|
+
getSingleDocumentConfig,
|
|
76
|
+
loginUrl: "/login",
|
|
77
|
+
getLogLevel,
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### `PrimitiveAppBootstrapOptions`
|
|
82
|
+
|
|
83
|
+
`createPrimitiveApp` accepts:
|
|
84
|
+
|
|
85
|
+
- **mainComponent**: your root Vue component (often `App.vue`).
|
|
86
|
+
- **router**: a `vue-router` instance (typically created via `createPrimitiveRouter`, see below).
|
|
87
|
+
- **getAppConfig**: `() => InitializeAppConfigOptions`
|
|
88
|
+
- **getJsBaoConfig**: `() => JsBaoClientOptions` (from `js-bao-wss-client`)
|
|
89
|
+
- **getNavigationConfig?**: `() => NavigationConfig`
|
|
90
|
+
- **getSingleDocumentConfig?**: `() => InitializeSingleDocumentOptions`
|
|
91
|
+
- **loginUrl**: path to your login route (e.g. `"/login"`), used by auth flows.
|
|
92
|
+
- **getLogLevel?**: `() => LogLevel` – overrides internal logging defaults.
|
|
93
|
+
- **mountTarget?**: CSS selector or DOM element (defaults to `"#app"`).
|
|
94
|
+
|
|
95
|
+
> **Note:** The `documentStoreMode` in your `getAppConfig` determines which document store(s) are initialized:
|
|
96
|
+
>
|
|
97
|
+
> - `DocumentStoreMode.None`: No document store is initialized.
|
|
98
|
+
> - `DocumentStoreMode.SingleDocument` or `DocumentStoreMode.SingleDocumentWithSwitching`: Requires `getSingleDocumentConfig`.
|
|
99
|
+
> - `DocumentStoreMode.MultiDoc`: Initializes `jsBaoDocumentsStore` on auth; apps register collections dynamically via `multiDocumentStore.registerCollection()`.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Routing & auth: `createPrimitiveRouter` and `PrimitiveRouterMeta`
|
|
104
|
+
|
|
105
|
+
Primitive App supplies an opinionated `vue-router` factory that:
|
|
106
|
+
|
|
107
|
+
- **Enforces auth** based on route metadata.
|
|
108
|
+
- **Redirects unauthenticated users** to a configured login route or URL.
|
|
109
|
+
- **Redirects non-admin users** away from admin-only routes.
|
|
110
|
+
- **Provides breadcrumb metadata** for the breadcrumbs store and layouts.
|
|
111
|
+
|
|
112
|
+
### Defining routes with `primitiveRouterMeta`
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
// src/router/routes.ts
|
|
116
|
+
import type { RouteRecordRaw } from "vue-router";
|
|
117
|
+
import {
|
|
118
|
+
createPrimitiveRouter,
|
|
119
|
+
PrimitiveAppLayout,
|
|
120
|
+
PrimitiveLoginLayout,
|
|
121
|
+
PrimitiveStaticLayout,
|
|
122
|
+
} from "primitive-app";
|
|
123
|
+
import LoginPage from "@/pages/LoginPage.vue";
|
|
124
|
+
import HomePage from "@/pages/HomePage.vue";
|
|
125
|
+
import TermsPage from "@/pages/TermsPage.vue";
|
|
126
|
+
|
|
127
|
+
const routes: RouteRecordRaw[] = [
|
|
128
|
+
{
|
|
129
|
+
path: "/",
|
|
130
|
+
component: PrimitiveAppLayout,
|
|
131
|
+
children: [
|
|
132
|
+
{
|
|
133
|
+
path: "",
|
|
134
|
+
name: "home",
|
|
135
|
+
component: HomePage,
|
|
136
|
+
meta: {
|
|
137
|
+
primitiveRouterMeta: {
|
|
138
|
+
requireAuth: "member", // "none" | "member" | "admin"
|
|
139
|
+
breadcrumb: { title: "Home" },
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
path: "routing/:slug",
|
|
145
|
+
name: "routing-slug",
|
|
146
|
+
component: () => import("@/pages/RoutingExamplePage.vue"),
|
|
147
|
+
meta: {
|
|
148
|
+
primitiveRouterMeta: {
|
|
149
|
+
requireAuth: "member",
|
|
150
|
+
breadcrumb: {
|
|
151
|
+
title: "Routing",
|
|
152
|
+
generator: (params) => String(params.slug ?? "Routing"),
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
path: "/",
|
|
161
|
+
component: PrimitiveLoginLayout,
|
|
162
|
+
children: [{ path: "login", name: "login", component: LoginPage }],
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
path: "/",
|
|
166
|
+
component: PrimitiveStaticLayout,
|
|
167
|
+
children: [
|
|
168
|
+
{
|
|
169
|
+
path: "terms-of-service",
|
|
170
|
+
name: "terms-of-service",
|
|
171
|
+
component: TermsPage,
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
},
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
const router = createPrimitiveRouter({
|
|
178
|
+
routes,
|
|
179
|
+
// or loginUrl: "https://auth.example.com/login"
|
|
180
|
+
loginRouteName: "login",
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
export default router;
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Auth behavior
|
|
187
|
+
|
|
188
|
+
- If `primitiveRouterMeta.requireAuth` is:
|
|
189
|
+
- `"none"`: route is public.
|
|
190
|
+
- `"member"`: user must be authenticated.
|
|
191
|
+
- `"admin"`: user must be authenticated and `useUserStore().isAdmin === true`.
|
|
192
|
+
- For member/admin routes:
|
|
193
|
+
- Unauthenticated users are redirected to the login route or URL with a `continueURL` query parameter.
|
|
194
|
+
- Non-admin users on admin routes are redirected to the app's `homeRouteName` from `useAppConfigStore`.
|
|
195
|
+
|
|
196
|
+
Breadcrumb metadata is consumed by `useBreadcrumbsStore` and `PrimitiveAppLayout` to render breadcrumb trails and page titles.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Stores
|
|
201
|
+
|
|
202
|
+
Primitive App provides several Pinia stores organized into three categories:
|
|
203
|
+
|
|
204
|
+
1. **Configuration stores** – Static app configuration and UI state
|
|
205
|
+
2. **User store** – Authentication and user preferences
|
|
206
|
+
3. **Document stores** – js-bao document management
|
|
207
|
+
|
|
208
|
+
### Configuration stores
|
|
209
|
+
|
|
210
|
+
#### App config store: `useAppConfigStore`
|
|
211
|
+
|
|
212
|
+
The app config store centralizes static configuration:
|
|
213
|
+
|
|
214
|
+
| Option | Type | Description |
|
|
215
|
+
| --------------------- | ------------------- | ------------------------------------------------ |
|
|
216
|
+
| `appName` | `string` | Display name for the app and default page titles |
|
|
217
|
+
| `appIcon` | `Component` | Vue component rendered in login/static layouts |
|
|
218
|
+
| `homeRouteName` | `RouteRecordName` | Route used as the "home" destination |
|
|
219
|
+
| `loginRouteName` | `RouteRecordName` | Named login route |
|
|
220
|
+
| `documentStoreMode` | `DocumentStoreMode` | Controls document store behavior (see below) |
|
|
221
|
+
| `pageTitleFormatter?` | `function` | Custom page title formatter |
|
|
222
|
+
| `loadingComponent?` | `Component` | Global loading component used by layouts |
|
|
223
|
+
|
|
224
|
+
**DocumentStoreMode options:**
|
|
225
|
+
|
|
226
|
+
- `DocumentStoreMode.None` – No document store initialized
|
|
227
|
+
- `DocumentStoreMode.SingleDocument` – Single document per user, no switching UI
|
|
228
|
+
- `DocumentStoreMode.SingleDocumentWithSwitching` – Single active document with switching/sharing UI
|
|
229
|
+
- `DocumentStoreMode.MultiDoc` – Multiple document collections managed dynamically
|
|
230
|
+
|
|
231
|
+
Example config:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
// src/config/appConfig.ts
|
|
235
|
+
import appIconUrl from "@/assets/app-icon.png";
|
|
236
|
+
import { DocumentStoreMode, type AppIconComponent } from "primitive-app";
|
|
237
|
+
import { defineComponent, h } from "vue";
|
|
238
|
+
import type { RouteLocationNormalizedLoaded } from "vue-router";
|
|
239
|
+
|
|
240
|
+
const AppIcon: AppIconComponent = defineComponent({
|
|
241
|
+
name: "AppIcon",
|
|
242
|
+
setup() {
|
|
243
|
+
return () => h("img", { src: appIconUrl, alt: "App Icon" });
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
function defaultPageTitleFormatter(input: {
|
|
248
|
+
breadcrumb: string | null;
|
|
249
|
+
appName: string;
|
|
250
|
+
route: RouteLocationNormalizedLoaded;
|
|
251
|
+
}): string {
|
|
252
|
+
if (input.breadcrumb && input.breadcrumb.trim().length > 0) {
|
|
253
|
+
return `${input.breadcrumb} : ${input.appName}`;
|
|
254
|
+
}
|
|
255
|
+
return input.appName;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function getAppConfig() {
|
|
259
|
+
return {
|
|
260
|
+
appName: "My Primitive App",
|
|
261
|
+
homeRouteName: "home",
|
|
262
|
+
loginRouteName: "login",
|
|
263
|
+
appIcon: AppIcon,
|
|
264
|
+
documentStoreMode: DocumentStoreMode.SingleDocumentWithSwitching,
|
|
265
|
+
pageTitleFormatter: defaultPageTitleFormatter,
|
|
266
|
+
} as const;
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
> `createPrimitiveApp` calls `useAppConfigStore().initialize(getAppConfig())` during bootstrap and sets up a reactive `document.title` using the provided formatter.
|
|
271
|
+
|
|
272
|
+
#### Navigation store: `useNavigationStore`
|
|
273
|
+
|
|
274
|
+
The navigation store drives sidebar, bottom navigation, and user menu:
|
|
275
|
+
|
|
276
|
+
**Configuration (`NavigationConfig`):**
|
|
277
|
+
|
|
278
|
+
| Option | Type | Description |
|
|
279
|
+
| ------------------------------- | ------------------------------- | ----------------------------------------- |
|
|
280
|
+
| `navOptions?.overflowMode` | `NavigationOverflowMode` | How to handle overflow items |
|
|
281
|
+
| `navOptions?.maxVisibleTabs` | `number` | Maximum tabs in bottom nav (default: 5) |
|
|
282
|
+
| `navOptions?.mobileNavEnabled` | `boolean` | Enable mobile bottom nav (default: true) |
|
|
283
|
+
| `navOptions?.mobileBackEnabled` | `boolean` | Enable mobile back button (default: true) |
|
|
284
|
+
| `navItems` | `Record<string, NavItemConfig>` | Navigation item definitions |
|
|
285
|
+
|
|
286
|
+
**NavItemConfig options:**
|
|
287
|
+
|
|
288
|
+
| Option | Type | Description |
|
|
289
|
+
| ------------------ | -------------------------------------- | ------------------------------------------------- |
|
|
290
|
+
| `key` | `string` | Unique identifier for the item |
|
|
291
|
+
| `navTitle` | `string` | Display title |
|
|
292
|
+
| `navGroup` | `"main" \| "secondary" \| "user-menu"` | Navigation group |
|
|
293
|
+
| `routeName?` | `string` | Vue router route name |
|
|
294
|
+
| `routeParams?` | `object` | Route parameters |
|
|
295
|
+
| `externalHref?` | `string` | External URL |
|
|
296
|
+
| `target?` | `string` | Link target (e.g., "\_blank") |
|
|
297
|
+
| `icon?` | `Component` | Icon component |
|
|
298
|
+
| `parentKey?` | `string` | Parent item key (for nesting) |
|
|
299
|
+
| `navHeader?` | `string` | Header text for grouped items |
|
|
300
|
+
| `matchRouteNames?` | `string[]` | Additional routes that activate this item |
|
|
301
|
+
| `mobilePriority?` | `number` | Priority for bottom nav (lower = higher priority) |
|
|
302
|
+
| `hidden?` | `boolean` | Hide this item |
|
|
303
|
+
|
|
304
|
+
Example:
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
// src/config/navigationConfig.ts
|
|
308
|
+
import { NavigationOverflowMode, type NavigationConfig } from "primitive-app";
|
|
309
|
+
import { BookOpen, Settings, LogOut } from "lucide-vue-next";
|
|
310
|
+
|
|
311
|
+
export function getNavigationConfig(): NavigationConfig {
|
|
312
|
+
return {
|
|
313
|
+
navOptions: {
|
|
314
|
+
overflowMode: NavigationOverflowMode.Always,
|
|
315
|
+
maxVisibleTabs: 3,
|
|
316
|
+
mobileNavEnabled: true,
|
|
317
|
+
mobileBackEnabled: true,
|
|
318
|
+
},
|
|
319
|
+
navItems: {
|
|
320
|
+
gettingStarted: {
|
|
321
|
+
key: "gettingStarted",
|
|
322
|
+
navTitle: "Getting Started",
|
|
323
|
+
navGroup: "main",
|
|
324
|
+
routeName: "getting-started",
|
|
325
|
+
icon: BookOpen,
|
|
326
|
+
mobilePriority: 1,
|
|
327
|
+
},
|
|
328
|
+
settings: {
|
|
329
|
+
key: "settings",
|
|
330
|
+
navTitle: "Settings",
|
|
331
|
+
navGroup: "user-menu",
|
|
332
|
+
routeName: "settings",
|
|
333
|
+
icon: Settings,
|
|
334
|
+
},
|
|
335
|
+
logout: {
|
|
336
|
+
key: "logout",
|
|
337
|
+
navTitle: "Log out",
|
|
338
|
+
navGroup: "user-menu",
|
|
339
|
+
routeName: "logout",
|
|
340
|
+
icon: LogOut,
|
|
341
|
+
mobilePriority: 2,
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
} as const;
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**Dynamic navigation actions:**
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
const navigation = useNavigationStore();
|
|
352
|
+
|
|
353
|
+
// Add items dynamically
|
|
354
|
+
navigation.addNavItems({ ... });
|
|
355
|
+
|
|
356
|
+
// Remove items
|
|
357
|
+
navigation.removeNavItems(['itemKey']);
|
|
358
|
+
|
|
359
|
+
// Show/hide items
|
|
360
|
+
navigation.hideNavItems(['itemKey']);
|
|
361
|
+
navigation.showNavItems(['itemKey']);
|
|
362
|
+
|
|
363
|
+
// Badges
|
|
364
|
+
navigation.setCountBadge('itemKey', 5);
|
|
365
|
+
navigation.setDotBadge('itemKey');
|
|
366
|
+
navigation.clearBadge('itemKey');
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
`PrimitiveAppLayout`, `PrimitiveSidebarNav`, `PrimitiveBottomNav`, and `PrimitiveUserMenu` all consume this store.
|
|
370
|
+
|
|
371
|
+
#### Breadcrumbs store: `useBreadcrumbsStore`
|
|
372
|
+
|
|
373
|
+
The breadcrumbs store maintains an array of breadcrumb segments derived from route metadata:
|
|
374
|
+
|
|
375
|
+
- **State:** `segments: BreadcrumbSegment[]` – array of `{ label, href }` objects
|
|
376
|
+
- **Actions:**
|
|
377
|
+
- `initialize({ router })` – Wire to Vue Router (called by `createPrimitiveApp`)
|
|
378
|
+
- `refreshCurrentRoute()` – Re-generate breadcrumbs for the current route
|
|
379
|
+
|
|
380
|
+
Use `refreshCurrentRoute()` when other reactive state that influences breadcrumb generators changes (e.g., user display name).
|
|
381
|
+
|
|
382
|
+
### User store: `useUserStore`
|
|
383
|
+
|
|
384
|
+
`useUserStore` wires your app into js-bao auth and user profile management.
|
|
385
|
+
|
|
386
|
+
**State:**
|
|
387
|
+
|
|
388
|
+
| Property | Type | Description |
|
|
389
|
+
| ----------------- | --------------------- | ----------------------------- |
|
|
390
|
+
| `currentUser` | `UserProfile \| null` | Current user profile |
|
|
391
|
+
| `isAuthenticated` | `boolean` | Whether user is authenticated |
|
|
392
|
+
| `isAdmin` | `boolean` (computed) | Whether user has admin role |
|
|
393
|
+
| `isOnline` | `boolean` | Network connectivity status |
|
|
394
|
+
| `isInitialized` | `boolean` | Whether store has initialized |
|
|
395
|
+
|
|
396
|
+
**Actions:**
|
|
397
|
+
|
|
398
|
+
| Method | Description |
|
|
399
|
+
| --------------------------------------------------- | ----------------------------------- |
|
|
400
|
+
| `initialize({ loginUrl })` | Initialize auth and event listeners |
|
|
401
|
+
| `login(continueURL?)` | Start OAuth flow |
|
|
402
|
+
| `handleOAuthCallback(defaultContinueUrl, loginUrl)` | Process OAuth callback |
|
|
403
|
+
| `logout(redirectTo?)` | Log out and optionally redirect |
|
|
404
|
+
|
|
405
|
+
**User preferences:**
|
|
406
|
+
|
|
407
|
+
Preferences are stored via the internal `UserPref` js-bao model in a per-user root document:
|
|
408
|
+
|
|
409
|
+
```ts
|
|
410
|
+
const user = useUserStore();
|
|
411
|
+
|
|
412
|
+
// Get a preference with default value
|
|
413
|
+
const theme = user.getPref<string>("theme", "light");
|
|
414
|
+
|
|
415
|
+
// Set a preference
|
|
416
|
+
await user.setPref("theme", "dark");
|
|
417
|
+
|
|
418
|
+
// Delete a preference
|
|
419
|
+
await user.deletePref("theme");
|
|
420
|
+
|
|
421
|
+
// Get all preferences
|
|
422
|
+
const allPrefs = user.getAllPrefs();
|
|
423
|
+
|
|
424
|
+
// Clear all preferences
|
|
425
|
+
await user.clearAllPrefs();
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Document stores
|
|
429
|
+
|
|
430
|
+
Primitive App provides three document stores that work together to manage js-bao documents. Understanding their relationship is key to choosing the right pattern for your app.
|
|
431
|
+
|
|
432
|
+
#### How document stores relate
|
|
433
|
+
|
|
434
|
+
```
|
|
435
|
+
+---------------------------------------------+
|
|
436
|
+
| jsBaoDocumentsStore |
|
|
437
|
+
| (All accessible documents & invitations) |
|
|
438
|
+
+---------------------------------------------+
|
|
439
|
+
/ \
|
|
440
|
+
/ \
|
|
441
|
+
+------------------------+ +------------------------+
|
|
442
|
+
| singleDocumentStore | | multiDocumentStore |
|
|
443
|
+
| (One active doc) | | (Tag-based |
|
|
444
|
+
| | | collections) |
|
|
445
|
+
+------------------------+ +------------------------+
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
- **`jsBaoDocumentsStore`**: The foundational reactive store that tracks all documents the user has access to, plus pending invitations. It handles document metadata events from js-bao and maintains the authoritative list.
|
|
449
|
+
- **`singleDocumentStore`**: Built on top of `jsBaoDocumentsStore`. Manages a single "active" document at a time. Ideal for simple apps.
|
|
450
|
+
- **`multiDocumentStore`**: Built on top of `jsBaoDocumentsStore`. Manages multiple document collections based on tags. Ideal for complex apps with varied sharing needs.
|
|
451
|
+
|
|
452
|
+
#### Choosing a document store pattern
|
|
453
|
+
|
|
454
|
+
**1. SingleDocument mode** – Simple apps without sharing needs
|
|
455
|
+
|
|
456
|
+
Best for: Personal apps, simple tools, single-user data.
|
|
457
|
+
|
|
458
|
+
```ts
|
|
459
|
+
// appConfig.ts
|
|
460
|
+
documentStoreMode: DocumentStoreMode.SingleDocument;
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
- Each user automatically gets a single document created for them.
|
|
464
|
+
- No document switching UI is shown.
|
|
465
|
+
- Data is conceptually like a single database per user.
|
|
466
|
+
|
|
467
|
+
**2. SingleDocumentWithSwitching mode** – Google Docs-like sharing pattern
|
|
468
|
+
|
|
469
|
+
Best for: Apps with clear data "containers" that users might share.
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
// appConfig.ts
|
|
473
|
+
documentStoreMode: DocumentStoreMode.SingleDocumentWithSwitching;
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
- Users automatically get a default document created.
|
|
477
|
+
- Users can create additional documents, each of which can be shared with others.
|
|
478
|
+
- Only one document is active at a time.
|
|
479
|
+
- Built-in UI for document switching, sharing invitations, and management.
|
|
480
|
+
- Examples: One document per "company", splitting "personal" from "shared" data.
|
|
481
|
+
|
|
482
|
+
Configuration:
|
|
483
|
+
|
|
484
|
+
```ts
|
|
485
|
+
export function getSingleDocumentConfig() {
|
|
486
|
+
return {
|
|
487
|
+
userVisibleDocumentName: "Project",
|
|
488
|
+
userVisibleDocumentNamePlural: "Projects",
|
|
489
|
+
defaultDocumentTitle: "My First Project",
|
|
490
|
+
manageDocumentsRouteName: "projects-manage",
|
|
491
|
+
} as const;
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
**3. MultiDoc mode** – Complex sharing and data modeling
|
|
496
|
+
|
|
497
|
+
Best for: Apps with complex sharing needs, multiple data sets, or large data volumes.
|
|
498
|
+
|
|
499
|
+
```ts
|
|
500
|
+
// appConfig.ts
|
|
501
|
+
documentStoreMode: DocumentStoreMode.MultiDoc;
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
- Multiple documents can be open simultaneously.
|
|
505
|
+
- Documents are organized into "collections" based on tags.
|
|
506
|
+
- Collections are registered dynamically at runtime.
|
|
507
|
+
- Supports auto-opening documents and auto-accepting invitations per collection.
|
|
508
|
+
- Recommended when: sharing different data sets with different users, data exceeds ~10MB per document, need to search across multiple documents.
|
|
509
|
+
|
|
510
|
+
Usage:
|
|
511
|
+
|
|
512
|
+
```ts
|
|
513
|
+
import { useMultiDocumentStore } from "primitive-app";
|
|
514
|
+
|
|
515
|
+
const multiDoc = useMultiDocumentStore();
|
|
516
|
+
|
|
517
|
+
// Register a collection (typically on component mount)
|
|
518
|
+
await multiDoc.registerCollection({
|
|
519
|
+
name: "workspaces",
|
|
520
|
+
tag: "workspace",
|
|
521
|
+
autoOpen: true, // Auto-open matching documents
|
|
522
|
+
autoAcceptInvites: true, // Auto-accept invitations for this collection
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// Access documents in a collection
|
|
526
|
+
const workspaces = computed(() => multiDoc.collections["workspaces"] ?? []);
|
|
527
|
+
|
|
528
|
+
// Check collection readiness
|
|
529
|
+
const isReady = multiDoc.getCollectionReadyRef("workspaces");
|
|
530
|
+
|
|
531
|
+
// Create a document in a collection
|
|
532
|
+
const newDoc = await multiDoc.createDocument("workspaces", "New Workspace");
|
|
533
|
+
|
|
534
|
+
// Unregister when done
|
|
535
|
+
await multiDoc.unregisterCollection("workspaces");
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
#### jsBaoDocumentsStore API
|
|
539
|
+
|
|
540
|
+
The base store for all document operations:
|
|
541
|
+
|
|
542
|
+
**State:**
|
|
543
|
+
|
|
544
|
+
| Property | Type | Description |
|
|
545
|
+
| ---------------------- | ---------------------- | --------------------------- |
|
|
546
|
+
| `documents` | `TrackedDocument[]` | All accessible documents |
|
|
547
|
+
| `pendingInvitations` | `DocumentInvitation[]` | Pending sharing invitations |
|
|
548
|
+
| `openDocumentIds` | `Set<string>` | Currently open document IDs |
|
|
549
|
+
| `documentListLoaded` | `boolean` | Document list has loaded |
|
|
550
|
+
| `invitationListLoaded` | `boolean` | Invitation list has loaded |
|
|
551
|
+
| `isReady` | `boolean` (computed) | Both lists have loaded |
|
|
552
|
+
|
|
553
|
+
**Actions:**
|
|
554
|
+
|
|
555
|
+
| Method | Description |
|
|
556
|
+
| --------------------------------------------- | ------------------------------ |
|
|
557
|
+
| `initialize()` | Load documents and invitations |
|
|
558
|
+
| `reset()` | Clear state and unsubscribe |
|
|
559
|
+
| `refreshDocuments()` | Reload document list |
|
|
560
|
+
| `openDocument(id)` | Open a document |
|
|
561
|
+
| `closeDocument(id)` | Close a document |
|
|
562
|
+
| `createDocument(title, tags?)` | Create a new document |
|
|
563
|
+
| `renameDocument(id, title)` | Rename a document |
|
|
564
|
+
| `deleteDocument(id)` | Delete a document |
|
|
565
|
+
| `shareDocument(id, email, permission)` | Share with another user |
|
|
566
|
+
| `refreshPendingInvitations()` | Reload invitations |
|
|
567
|
+
| `acceptInvitation(documentId)` | Accept an invitation |
|
|
568
|
+
| `declineInvitation(documentId, invitationId)` | Decline an invitation |
|
|
569
|
+
|
|
570
|
+
#### singleDocumentStore API
|
|
571
|
+
|
|
572
|
+
Manages a single active document:
|
|
573
|
+
|
|
574
|
+
**State:**
|
|
575
|
+
|
|
576
|
+
| Property | Type | Description |
|
|
577
|
+
| ------------------------- | ------------------------- | -------------------------- |
|
|
578
|
+
| `currentDocumentId` | `string \| null` | Active document ID |
|
|
579
|
+
| `currentDocumentMetadata` | `TrackedDocument \| null` | Active document metadata |
|
|
580
|
+
| `isReady` | `boolean` | Document is open and ready |
|
|
581
|
+
| `isCurrentDocReadOnly` | `boolean` | User has read-only access |
|
|
582
|
+
|
|
583
|
+
**Actions:**
|
|
584
|
+
|
|
585
|
+
| Method | Description |
|
|
586
|
+
| --------------------- | ------------------------------ |
|
|
587
|
+
| `initialize(options)` | Initialize with config |
|
|
588
|
+
| `reset()` | Clear state |
|
|
589
|
+
| `switchDocument(id)` | Switch to a different document |
|
|
590
|
+
|
|
591
|
+
#### multiDocumentStore API
|
|
592
|
+
|
|
593
|
+
Manages multiple document collections:
|
|
594
|
+
|
|
595
|
+
**State:**
|
|
596
|
+
|
|
597
|
+
| Property | Type | Description |
|
|
598
|
+
| ------------- | ----------------------------------- | ------------------------------- |
|
|
599
|
+
| `collections` | `Record<string, TrackedDocument[]>` | Documents grouped by collection |
|
|
600
|
+
|
|
601
|
+
**Actions:**
|
|
602
|
+
|
|
603
|
+
| Method | Description |
|
|
604
|
+
| ----------------------------------- | ------------------------------- |
|
|
605
|
+
| `registerCollection(config)` | Register a new collection |
|
|
606
|
+
| `unregisterCollection(name)` | Unregister a collection |
|
|
607
|
+
| `getCollection(name)` | Get documents in a collection |
|
|
608
|
+
| `isCollectionRegistered(name)` | Check if collection exists |
|
|
609
|
+
| `isCollectionReady(name)` | Check if collection is ready |
|
|
610
|
+
| `getCollectionReadyRef(name)` | Get reactive readiness ref |
|
|
611
|
+
| `createDocument(collection, title)` | Create document in collection |
|
|
612
|
+
| `isDocumentReady(id)` | Check if document is open |
|
|
613
|
+
| `getDocumentReadyRef(idRef)` | Get reactive document readiness |
|
|
614
|
+
| `reset()` | Clear all collections |
|
|
615
|
+
|
|
616
|
+
---
|
|
617
|
+
|
|
618
|
+
## `PrimitiveAppLayout` usage patterns
|
|
619
|
+
|
|
620
|
+
`PrimitiveAppLayout` is the main authenticated **app shell**. It:
|
|
621
|
+
|
|
622
|
+
- Renders a sidebar (desktop), breadcrumb header, and optional bottom nav (mobile).
|
|
623
|
+
- Integrates with:
|
|
624
|
+
- `useNavigationStore` (sidebar items, bottom nav, back behavior).
|
|
625
|
+
- `useBreadcrumbsStore` (breadcrumbs and page title).
|
|
626
|
+
- `useUserStore` (user menu, online status).
|
|
627
|
+
- `useAppConfigStore` and `DocumentStoreMode` (document switching vs static header).
|
|
628
|
+
- `useSingleDocumentStore` (optional single-document switcher).
|
|
629
|
+
- Shows a service worker "update required" banner and an optional worktree label banner (for dev workflows).
|
|
630
|
+
|
|
631
|
+
You can adopt it at different levels of complexity:
|
|
632
|
+
|
|
633
|
+
### 1) Use it as-is (configure via stores and route metadata)
|
|
634
|
+
|
|
635
|
+
This is the simplest path – use `PrimitiveAppLayout` as the root layout for authenticated routes, and configure:
|
|
636
|
+
|
|
637
|
+
- App config (`getAppConfig`)
|
|
638
|
+
- Document store (`getSingleDocumentConfig` + `DocumentStoreMode`)
|
|
639
|
+
- Navigation (`getNavigationConfig`)
|
|
640
|
+
- Routing meta (`primitiveRouterMeta`)
|
|
641
|
+
|
|
642
|
+
Example (excerpt):
|
|
643
|
+
|
|
644
|
+
```ts
|
|
645
|
+
// src/router/routes.ts
|
|
646
|
+
const routes: RouteRecordRaw[] = [
|
|
647
|
+
{
|
|
648
|
+
path: "/",
|
|
649
|
+
component: PrimitiveAppLayout,
|
|
650
|
+
children: [
|
|
651
|
+
{
|
|
652
|
+
path: "",
|
|
653
|
+
name: "home",
|
|
654
|
+
component: () => import("@/pages/HomePage.vue"),
|
|
655
|
+
meta: {
|
|
656
|
+
primitiveRouterMeta: {
|
|
657
|
+
requireAuth: "member",
|
|
658
|
+
breadcrumb: { title: "Home" },
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
},
|
|
662
|
+
],
|
|
663
|
+
},
|
|
664
|
+
// login + static sections...
|
|
665
|
+
];
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
You do **not** pass any props to `PrimitiveAppLayout` – it reads state from the shared stores.
|
|
669
|
+
|
|
670
|
+
### 2) Wrap it in a layout that performs extra data loading or dynamic tweaks
|
|
671
|
+
|
|
672
|
+
Wrap `PrimitiveAppLayout` in your own layout to:
|
|
673
|
+
|
|
674
|
+
- Run data loaders (`useJsBaoDataLoader`, app-specific composables).
|
|
675
|
+
- Dynamically modify navigation (`navigationStore.addNavItems`, badges).
|
|
676
|
+
- Add theming or additional chrome around the shell.
|
|
677
|
+
|
|
678
|
+
```vue
|
|
679
|
+
<!-- src/layouts/ThemedPrimitiveAppLayout.vue -->
|
|
680
|
+
<script setup lang="ts">
|
|
681
|
+
import { PrimitiveAppLayout, useNavigationStore } from "primitive-app";
|
|
682
|
+
import { onMounted } from "vue";
|
|
683
|
+
|
|
684
|
+
const navigation = useNavigationStore();
|
|
685
|
+
|
|
686
|
+
onMounted(() => {
|
|
687
|
+
navigation.addNavItems({
|
|
688
|
+
recentItems: {
|
|
689
|
+
key: "recentItems",
|
|
690
|
+
navTitle: "Recent Items",
|
|
691
|
+
navGroup: "main",
|
|
692
|
+
routeName: "recent-items",
|
|
693
|
+
mobilePriority: 2,
|
|
694
|
+
},
|
|
695
|
+
});
|
|
696
|
+
});
|
|
697
|
+
</script>
|
|
698
|
+
|
|
699
|
+
<template>
|
|
700
|
+
<div class="min-h-screen bg-background text-foreground">
|
|
701
|
+
<PrimitiveAppLayout>
|
|
702
|
+
<router-view />
|
|
703
|
+
</PrimitiveAppLayout>
|
|
704
|
+
</div>
|
|
705
|
+
</template>
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
Routes now use `ThemedPrimitiveAppLayout` instead of `PrimitiveAppLayout` directly.
|
|
709
|
+
|
|
710
|
+
### 3) Override sidebar / bottom nav content via slots
|
|
711
|
+
|
|
712
|
+
`PrimitiveAppLayout` exposes:
|
|
713
|
+
|
|
714
|
+
- `#sidebar` – replaces the entire sidebar area.
|
|
715
|
+
- `#bottomNav` – replaces the mobile bottom navigation bar.
|
|
716
|
+
- Default slot – main content area (which typically renders `<router-view />`).
|
|
717
|
+
|
|
718
|
+
This lets you keep the **overall shell behavior** (SW banner, worktree label, mobile back header, padding) while providing your own navigation components.
|
|
719
|
+
|
|
720
|
+
```vue
|
|
721
|
+
<!-- src/layouts/CustomShellLayout.vue -->
|
|
722
|
+
<script setup lang="ts">
|
|
723
|
+
import {
|
|
724
|
+
PrimitiveAppLayout,
|
|
725
|
+
PrimitiveUserMenu,
|
|
726
|
+
useNavigationStore,
|
|
727
|
+
useUserStore,
|
|
728
|
+
} from "primitive-app";
|
|
729
|
+
import { computed } from "vue";
|
|
730
|
+
|
|
731
|
+
const navigation = useNavigationStore();
|
|
732
|
+
const user = useUserStore();
|
|
733
|
+
|
|
734
|
+
const mainNavItems = computed(() => navigation.navMain);
|
|
735
|
+
const userMenuItems = computed(() => navigation.userMenuItems);
|
|
736
|
+
</script>
|
|
737
|
+
|
|
738
|
+
<template>
|
|
739
|
+
<PrimitiveAppLayout>
|
|
740
|
+
<template #sidebar>
|
|
741
|
+
<aside class="flex h-full flex-col border-r bg-background">
|
|
742
|
+
<header class="p-4 border-b">
|
|
743
|
+
<h1 class="text-sm font-semibold tracking-tight">
|
|
744
|
+
My Custom Sidebar
|
|
745
|
+
</h1>
|
|
746
|
+
</header>
|
|
747
|
+
|
|
748
|
+
<nav class="flex-1 overflow-y-auto p-2 space-y-1">
|
|
749
|
+
<RouterLink
|
|
750
|
+
v-for="item in mainNavItems"
|
|
751
|
+
:key="item.title"
|
|
752
|
+
:to="item.url"
|
|
753
|
+
class="block px-3 py-2 rounded-md text-sm hover:bg-accent"
|
|
754
|
+
>
|
|
755
|
+
{{ item.title }}
|
|
756
|
+
</RouterLink>
|
|
757
|
+
</nav>
|
|
758
|
+
|
|
759
|
+
<footer class="border-t p-2">
|
|
760
|
+
<PrimitiveUserMenu
|
|
761
|
+
:current-user="user.currentUser"
|
|
762
|
+
:is-online="user.isOnline"
|
|
763
|
+
:user-menu-items="userMenuItems"
|
|
764
|
+
/>
|
|
765
|
+
</footer>
|
|
766
|
+
</aside>
|
|
767
|
+
</template>
|
|
768
|
+
|
|
769
|
+
<template #bottomNav>
|
|
770
|
+
<nav class="fixed inset-x-0 bottom-0 border-t bg-background md:hidden">
|
|
771
|
+
<!-- your own mobile tab bar implementation -->
|
|
772
|
+
</nav>
|
|
773
|
+
</template>
|
|
774
|
+
|
|
775
|
+
<router-view />
|
|
776
|
+
</PrimitiveAppLayout>
|
|
777
|
+
</template>
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
You can also reconstruct the _default_ layout but insert custom elements (e.g. banners, diagnostics) inside the slots.
|
|
781
|
+
|
|
782
|
+
### 4) Build a completely custom layout
|
|
783
|
+
|
|
784
|
+
Finally, you can skip `PrimitiveAppLayout` entirely and build your own shell using the underlying stores and components:
|
|
785
|
+
|
|
786
|
+
- `useAppConfigStore`, `useUserStore`, `useNavigationStore`, `useBreadcrumbsStore`, `useSingleDocumentStore`
|
|
787
|
+
- Components: `PrimitiveSidebarNav`, `PrimitiveAppBreadcrumb`, `PrimitiveUserMenu`, `PrimitiveBottomNav`, `PrimitiveSingleDocumentSwitcher`, etc.
|
|
788
|
+
|
|
789
|
+
```vue
|
|
790
|
+
<!-- src/layouts/MyFullyCustomLayout.vue -->
|
|
791
|
+
<script setup lang="ts">
|
|
792
|
+
import {
|
|
793
|
+
PrimitiveAppBreadcrumb,
|
|
794
|
+
PrimitiveSidebarNav,
|
|
795
|
+
PrimitiveUserMenu,
|
|
796
|
+
useAppConfigStore,
|
|
797
|
+
useNavigationStore,
|
|
798
|
+
useUserStore,
|
|
799
|
+
} from "primitive-app";
|
|
800
|
+
|
|
801
|
+
const appConfig = useAppConfigStore();
|
|
802
|
+
const navigation = useNavigationStore();
|
|
803
|
+
const user = useUserStore();
|
|
804
|
+
</script>
|
|
805
|
+
|
|
806
|
+
<template>
|
|
807
|
+
<div class="min-h-screen grid grid-cols-[240px_1fr]">
|
|
808
|
+
<aside class="border-r bg-background flex flex-col">
|
|
809
|
+
<header class="p-4 border-b">
|
|
810
|
+
<span class="font-semibold text-sm">
|
|
811
|
+
{{ appConfig.appName() }}
|
|
812
|
+
</span>
|
|
813
|
+
</header>
|
|
814
|
+
|
|
815
|
+
<main class="flex-1 overflow-y-auto">
|
|
816
|
+
<PrimitiveSidebarNav />
|
|
817
|
+
</main>
|
|
818
|
+
|
|
819
|
+
<footer class="border-t p-2">
|
|
820
|
+
<PrimitiveUserMenu
|
|
821
|
+
:current-user="user.currentUser"
|
|
822
|
+
:is-online="user.isOnline"
|
|
823
|
+
:user-menu-items="navigation.userMenuItems"
|
|
824
|
+
/>
|
|
825
|
+
</footer>
|
|
826
|
+
</aside>
|
|
827
|
+
|
|
828
|
+
<section class="flex flex-col">
|
|
829
|
+
<header class="h-12 flex items-center border-b px-4">
|
|
830
|
+
<PrimitiveAppBreadcrumb />
|
|
831
|
+
</header>
|
|
832
|
+
|
|
833
|
+
<main class="flex-1 overflow-y-auto p-4">
|
|
834
|
+
<router-view />
|
|
835
|
+
</main>
|
|
836
|
+
</section>
|
|
837
|
+
</div>
|
|
838
|
+
</template>
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
This is useful if you have strong visual/UX requirements or are gradually adopting `primitive-app` in an existing application.
|
|
842
|
+
|
|
843
|
+
---
|
|
844
|
+
|
|
845
|
+
## Data loading with `useJsBaoDataLoader`
|
|
846
|
+
|
|
847
|
+
`useJsBaoDataLoader` provides a standardized pattern for:
|
|
848
|
+
|
|
849
|
+
- Loading data asynchronously (typically via js-bao models).
|
|
850
|
+
- Watching a "document ready" gate (e.g., from `useSingleDocumentStore`).
|
|
851
|
+
- Reacting to changes in query params.
|
|
852
|
+
- Subscribing to js-bao models and automatically reloading when data changes.
|
|
853
|
+
- Integrating with skeleton components like `PrimitiveSkeletonGate`.
|
|
854
|
+
|
|
855
|
+
### API overview
|
|
856
|
+
|
|
857
|
+
```ts
|
|
858
|
+
import { useJsBaoDataLoader } from "primitive-app";
|
|
859
|
+
|
|
860
|
+
const { data, initialDataLoaded, reload } = useJsBaoDataLoader<Data, Query>({
|
|
861
|
+
subscribeTo: [
|
|
862
|
+
/* models with Model.subscribe(cb) */
|
|
863
|
+
],
|
|
864
|
+
queryParams, // ref/computed or plain value; null disables query-driven reloads
|
|
865
|
+
documentReady, // ref/computed/boolean gate
|
|
866
|
+
loadData: async ({ queryParams }) => {
|
|
867
|
+
/* ... */
|
|
868
|
+
},
|
|
869
|
+
pauseUpdates, // optional ref/computed/boolean
|
|
870
|
+
debounceMs: 50, // optional debounce for reloads
|
|
871
|
+
onError: (error) => {}, // optional error handler
|
|
872
|
+
});
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
- **Result:**
|
|
876
|
+
- `data: Ref<Data | null>` – last successfully loaded value.
|
|
877
|
+
- `initialDataLoaded: Ref<boolean>` – becomes `true` after first successful load while `documentReady` is `true`.
|
|
878
|
+
- `reload(): void` – manually schedule a reload (respecting debounce and gates).
|
|
879
|
+
|
|
880
|
+
### Example with js-bao models and the single-document store
|
|
881
|
+
|
|
882
|
+
```ts
|
|
883
|
+
// src/pages/ProductsPage.vue (script setup)
|
|
884
|
+
import {
|
|
885
|
+
PrimitiveSkeletonGate,
|
|
886
|
+
useJsBaoDataLoader,
|
|
887
|
+
useSingleDocumentStore,
|
|
888
|
+
} from "primitive-app";
|
|
889
|
+
import { Product } from "@/models/Product";
|
|
890
|
+
import { storeToRefs } from "pinia";
|
|
891
|
+
import { computed, ref } from "vue";
|
|
892
|
+
|
|
893
|
+
type PageData = { products: Product[] };
|
|
894
|
+
|
|
895
|
+
const singleDoc = useSingleDocumentStore();
|
|
896
|
+
const { isReady: documentReady } = storeToRefs(singleDoc);
|
|
897
|
+
|
|
898
|
+
const queryParams = ref({}); // "load all" for this example
|
|
899
|
+
|
|
900
|
+
const { data, initialDataLoaded, reload } = useJsBaoDataLoader<PageData>({
|
|
901
|
+
subscribeTo: [Product],
|
|
902
|
+
queryParams,
|
|
903
|
+
documentReady,
|
|
904
|
+
loadData: async () => {
|
|
905
|
+
const result = await Product.query(queryParams.value);
|
|
906
|
+
return { products: (result.data || []) as Product[] };
|
|
907
|
+
},
|
|
908
|
+
onError: (err) => {
|
|
909
|
+
console.error("Error loading products", err);
|
|
910
|
+
},
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
const products = computed(() => data.value?.products ?? []);
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
```vue
|
|
917
|
+
<!-- template -->
|
|
918
|
+
<PrimitiveSkeletonGate :is-ready="initialDataLoaded">
|
|
919
|
+
<template #skeleton>
|
|
920
|
+
<!-- skeleton UI -->
|
|
921
|
+
</template>
|
|
922
|
+
|
|
923
|
+
<ul>
|
|
924
|
+
<li v-for="product in products" :key="product.id">
|
|
925
|
+
{{ product.name }}
|
|
926
|
+
</li>
|
|
927
|
+
</ul>
|
|
928
|
+
</PrimitiveSkeletonGate>
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
### Example without js-bao (simple async data)
|
|
932
|
+
|
|
933
|
+
```ts
|
|
934
|
+
import { useJsBaoDataLoader, PrimitiveSkeletonGate } from "primitive-app";
|
|
935
|
+
import { ref } from "vue";
|
|
936
|
+
|
|
937
|
+
type DemoData = { message: string };
|
|
938
|
+
|
|
939
|
+
const simulateSlowLoad = async (): Promise<DemoData> => {
|
|
940
|
+
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
941
|
+
return { message: "Loaded after a short delay." };
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
const { data, initialDataLoaded } = useJsBaoDataLoader<DemoData>({
|
|
945
|
+
subscribeTo: [],
|
|
946
|
+
queryParams: ref({}),
|
|
947
|
+
documentReady: ref(true),
|
|
948
|
+
loadData: simulateSlowLoad,
|
|
949
|
+
});
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
---
|
|
953
|
+
|
|
954
|
+
## Auth, navigation, documents, and debug components
|
|
955
|
+
|
|
956
|
+
Primitive App includes higher-level components that sit on top of the stores:
|
|
957
|
+
|
|
958
|
+
- **Auth components**
|
|
959
|
+
- `PrimitiveLogin`, `PrimitiveLogout`, `PrimitiveOauthCallback`
|
|
960
|
+
- **Navigation components**
|
|
961
|
+
- `PrimitiveAppBreadcrumb`, `PrimitiveSidebarNav`, `PrimitiveBottomNav`,
|
|
962
|
+
`PrimitiveUserMenu`, `PrimitiveNavigationBadge`
|
|
963
|
+
- **Document components**
|
|
964
|
+
- `PrimitiveManageDocuments`, `PrimitiveShareDocumentDialog`,
|
|
965
|
+
`PrimitiveSingleDocumentSwitcher`
|
|
966
|
+
- **Shared components**
|
|
967
|
+
- `PrimitiveLogoSpinner`, `PrimitiveSkeletonGate`, `DeleteConfirmationDialog`
|
|
968
|
+
- **Debug suite**
|
|
969
|
+
- `PrimitiveDebuggingSuite`, `PrimitiveTestRunner`, `DocumentDebugger`,
|
|
970
|
+
`DebugSuiteLayout`, plus debug pages you can mount on a `/debug` route.
|
|
971
|
+
|
|
972
|
+
---
|
|
973
|
+
|
|
974
|
+
## Debugging suite
|
|
975
|
+
|
|
976
|
+
The debugging suite provides tools for testing and inspecting your app during development:
|
|
977
|
+
|
|
978
|
+
### Document Debugger
|
|
979
|
+
|
|
980
|
+
The Document Debugger is a powerful tool for inspecting and managing js-bao documents and model data:
|
|
981
|
+
|
|
982
|
+
- **Document management**: View all documents, create new documents, rename, delete, and switch between documents
|
|
983
|
+
- **Model inspection**: Browse all registered js-bao models and their records
|
|
984
|
+
- **Record CRUD**: Create, read, update, and delete records directly
|
|
985
|
+
- **Filtering & sorting**: Filter and sort records by any field
|
|
986
|
+
- **Mass operations**: Bulk delete records or documents
|
|
987
|
+
- **Schema inspection**: View model fields, types, indexes, relationships, and unique constraints
|
|
988
|
+
|
|
989
|
+
The Document Debugger uses its own document context independent of the app's main document stores, so you can inspect data without affecting the app state.
|
|
990
|
+
|
|
991
|
+
### Test Runner
|
|
992
|
+
|
|
993
|
+
The test harness runs app-level integration tests. Tests are grouped into `PrimitiveTestGroup` objects and registered with the `DebugSuiteLayout`.
|
|
994
|
+
|
|
995
|
+
#### Defining test groups
|
|
996
|
+
|
|
997
|
+
Create a file for your test groups (e.g., `src/tests/myTests.ts`):
|
|
998
|
+
|
|
999
|
+
```ts
|
|
1000
|
+
// src/tests/myTests.ts
|
|
1001
|
+
import type { PrimitiveTestGroup } from "primitive-app";
|
|
1002
|
+
import { useUserStore, useSingleDocumentStore } from "primitive-app";
|
|
1003
|
+
|
|
1004
|
+
// Helper to format pass/fail scores (the runner parses this format)
|
|
1005
|
+
function formatScore(passed: number, total: number): string {
|
|
1006
|
+
const percentage = total === 0 ? 100 : (passed / total) * 100;
|
|
1007
|
+
return `${passed}/${total} (${percentage.toFixed(1)}%)`;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
export const userPreferencesTestGroup: PrimitiveTestGroup = {
|
|
1011
|
+
name: "User Preferences",
|
|
1012
|
+
mode: "offline", // "offline" (default) or "online" – controls network requirement
|
|
1013
|
+
tests: [
|
|
1014
|
+
{
|
|
1015
|
+
id: "user-pref-read-all",
|
|
1016
|
+
name: "can read all user preferences without error",
|
|
1017
|
+
async run(log?: (m: string) => void): Promise<string> {
|
|
1018
|
+
const user = useUserStore();
|
|
1019
|
+
|
|
1020
|
+
log?.("Reading all preferences from userStore...");
|
|
1021
|
+
const prefs = user.getAllPrefs();
|
|
1022
|
+
log?.(`Retrieved ${Object.keys(prefs).length} preference(s)`);
|
|
1023
|
+
|
|
1024
|
+
// Returning a score string indicates success
|
|
1025
|
+
return formatScore(1, 1);
|
|
1026
|
+
},
|
|
1027
|
+
},
|
|
1028
|
+
{
|
|
1029
|
+
id: "user-pref-default",
|
|
1030
|
+
name: "getPref returns default value for missing key",
|
|
1031
|
+
async run(log?: (m: string) => void): Promise<string> {
|
|
1032
|
+
const user = useUserStore();
|
|
1033
|
+
const defaultValue = "fallback";
|
|
1034
|
+
|
|
1035
|
+
log?.(`Requesting missing key with default "${defaultValue}"...`);
|
|
1036
|
+
const value = user.getPref<string>("nonexistent_key", defaultValue);
|
|
1037
|
+
|
|
1038
|
+
// Throwing an Error fails the test
|
|
1039
|
+
if (value !== defaultValue) {
|
|
1040
|
+
throw new Error(`Expected "${defaultValue}", got "${value}"`);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
log?.("Default value returned correctly");
|
|
1044
|
+
return formatScore(1, 1);
|
|
1045
|
+
},
|
|
1046
|
+
},
|
|
1047
|
+
],
|
|
1048
|
+
};
|
|
1049
|
+
|
|
1050
|
+
export const documentTestGroup: PrimitiveTestGroup = {
|
|
1051
|
+
name: "Document Store",
|
|
1052
|
+
mode: "offline",
|
|
1053
|
+
tests: [
|
|
1054
|
+
{
|
|
1055
|
+
id: "doc-store-ready",
|
|
1056
|
+
name: "single document store becomes ready",
|
|
1057
|
+
async run(log?: (m: string) => void): Promise<string> {
|
|
1058
|
+
const docStore = useSingleDocumentStore();
|
|
1059
|
+
|
|
1060
|
+
log?.(`Document store ready: ${docStore.isReady}`);
|
|
1061
|
+
if (!docStore.isReady) {
|
|
1062
|
+
throw new Error("Expected document store to be ready");
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
return formatScore(1, 1);
|
|
1066
|
+
},
|
|
1067
|
+
},
|
|
1068
|
+
],
|
|
1069
|
+
};
|
|
1070
|
+
|
|
1071
|
+
// Export all test groups as an array
|
|
1072
|
+
export const appTestGroups: PrimitiveTestGroup[] = [
|
|
1073
|
+
userPreferencesTestGroup,
|
|
1074
|
+
documentTestGroup,
|
|
1075
|
+
];
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
#### Test function conventions
|
|
1079
|
+
|
|
1080
|
+
- **`id`**: Unique identifier for the test (used for selection state).
|
|
1081
|
+
- **`name`**: Human-readable test name displayed in the UI.
|
|
1082
|
+
- **`run(log?)`**: Async function that executes the test.
|
|
1083
|
+
- Use `log?.("message")` to output progress to the test runner.
|
|
1084
|
+
- Return a score string like `"1/1 (100.0%)"` to indicate success (the runner parses this format).
|
|
1085
|
+
- Throw an `Error` to fail the test.
|
|
1086
|
+
- **`mode`** (on the group): `"offline"` (default) runs tests without requiring network; `"online"` requires network connectivity.
|
|
1087
|
+
|
|
1088
|
+
#### Registering tests with routes
|
|
1089
|
+
|
|
1090
|
+
Mount the debug suite on a `/debug` route and pass your test groups to `DebugSuiteLayout`:
|
|
1091
|
+
|
|
1092
|
+
```ts
|
|
1093
|
+
// src/router/routes.ts
|
|
1094
|
+
import {
|
|
1095
|
+
createPrimitiveRouter,
|
|
1096
|
+
DebugSuiteLayout,
|
|
1097
|
+
DebuggingSuiteHome,
|
|
1098
|
+
DebuggingSuiteTests,
|
|
1099
|
+
DebuggingSuiteDocuments,
|
|
1100
|
+
} from "primitive-app";
|
|
1101
|
+
import { appTestGroups } from "@/tests/myTests";
|
|
1102
|
+
import type { RouteRecordRaw } from "vue-router";
|
|
1103
|
+
|
|
1104
|
+
const routes: RouteRecordRaw[] = [
|
|
1105
|
+
// ... your app routes ...
|
|
1106
|
+
|
|
1107
|
+
// Debug suite (admin-only recommended)
|
|
1108
|
+
{
|
|
1109
|
+
path: "/debug",
|
|
1110
|
+
component: DebugSuiteLayout,
|
|
1111
|
+
props: {
|
|
1112
|
+
testGroups: appTestGroups,
|
|
1113
|
+
appName: "My App",
|
|
1114
|
+
},
|
|
1115
|
+
meta: {
|
|
1116
|
+
primitiveRouterMeta: {
|
|
1117
|
+
requireAuth: "admin", // or "member" for broader access
|
|
1118
|
+
},
|
|
1119
|
+
},
|
|
1120
|
+
children: [
|
|
1121
|
+
{
|
|
1122
|
+
path: "",
|
|
1123
|
+
name: "debug-home",
|
|
1124
|
+
component: DebuggingSuiteHome,
|
|
1125
|
+
},
|
|
1126
|
+
{
|
|
1127
|
+
path: "test",
|
|
1128
|
+
name: "debug-test",
|
|
1129
|
+
component: DebuggingSuiteTests,
|
|
1130
|
+
},
|
|
1131
|
+
{
|
|
1132
|
+
path: "documents",
|
|
1133
|
+
name: "debug-documents",
|
|
1134
|
+
component: DebuggingSuiteDocuments,
|
|
1135
|
+
},
|
|
1136
|
+
],
|
|
1137
|
+
},
|
|
1138
|
+
];
|
|
1139
|
+
|
|
1140
|
+
const router = createPrimitiveRouter({
|
|
1141
|
+
routes,
|
|
1142
|
+
loginRouteName: "login",
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1145
|
+
export default router;
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
The debug suite provides:
|
|
1149
|
+
|
|
1150
|
+
- **Home page** (`DebuggingSuiteHome`): Overview and quick actions.
|
|
1151
|
+
- **Test page** (`DebuggingSuiteTests`): Select and run tests, view results and logs.
|
|
1152
|
+
- **Documents page** (`DebuggingSuiteDocuments`): Inspect and manage js-bao documents via the Document Debugger.
|
|
1153
|
+
|
|
1154
|
+
---
|
|
1155
|
+
|
|
1156
|
+
## js-bao integration & logging
|
|
1157
|
+
|
|
1158
|
+
### JsBao client service
|
|
1159
|
+
|
|
1160
|
+
Primitive App centralizes js-bao client setup via:
|
|
1161
|
+
|
|
1162
|
+
- `initializeJsBao(config: JsBaoClientOptions)` – called once at startup.
|
|
1163
|
+
- `jsBaoClientService.getClientAsync()` – used internally by stores to access the client.
|
|
1164
|
+
|
|
1165
|
+
`config.models` from your app are merged with the library's internal models (such as `UserPref`) before initializing the client.
|
|
1166
|
+
|
|
1167
|
+
### Logging
|
|
1168
|
+
|
|
1169
|
+
`primitive-app` ships with a scoped logging utility:
|
|
1170
|
+
|
|
1171
|
+
- `primitiveAppBaseLogger`
|
|
1172
|
+
- `createLogger({ level, scope })`
|
|
1173
|
+
- `LogLevel` (`"debug" | "info" | "warn" | "error" | "none"`)
|
|
1174
|
+
|
|
1175
|
+
Example:
|
|
1176
|
+
|
|
1177
|
+
```ts
|
|
1178
|
+
import { createLogger } from "primitive-app";
|
|
1179
|
+
|
|
1180
|
+
const logger = createLogger({
|
|
1181
|
+
level: import.meta.env.DEV ? "debug" : "info",
|
|
1182
|
+
scope: ["MyApp", "ProductsPage"],
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
logger.debug("Loaded products", { count: 42 });
|
|
1186
|
+
```
|
|
1187
|
+
|
|
1188
|
+
---
|
|
1189
|
+
|
|
1190
|
+
## Reference and further examples
|
|
1191
|
+
|
|
1192
|
+
For a more complete, real-world example, refer to:
|
|
1193
|
+
|
|
1194
|
+
- **Demo app (`primitive-app-demo`)**
|
|
1195
|
+
- `src/main.ts` – bootstrapping with `createPrimitiveApp`
|
|
1196
|
+
- `src/router/routes.ts` – routes + `createPrimitiveRouter`
|
|
1197
|
+
- `src/config/appConfig.ts`, `src/config/envConfig.ts`, `src/config/navigationConfig.ts`
|
|
1198
|
+
- `src/pages/GettingStarted*` – focused pages on configuration, theming, routing, navigation, data loading, utilities
|
|
1199
|
+
- **Template app (`primitive-app-template`)**
|
|
1200
|
+
- Minimal starter following the same patterns, useful for new apps.
|
|
1201
|
+
|
|
1202
|
+
For a deeper, implementation-level map of the library (especially for automation or AI agents), see [`AGENTS.md`](./AGENTS.md).
|