@pilotiq/pilotiq 0.23.1 → 0.24.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/CHANGELOG.md +91 -0
- package/boost/guidelines.md +566 -0
- package/boost/skills/pilotiq-fields/SKILL.md +47 -0
- package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
- package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
- package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
- package/boost/skills/pilotiq-relations/SKILL.md +47 -0
- package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
- package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
- package/boost/skills/pilotiq-resource/SKILL.md +61 -0
- package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
- package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
- package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
- package/dist/actions/exportFactory.d.ts +10 -0
- package/dist/actions/exportFactory.d.ts.map +1 -1
- package/dist/actions/exportFactory.js +10 -0
- package/dist/actions/exportFactory.js.map +1 -1
- package/dist/react/CollabRoomContext.d.ts +5 -5
- package/dist/react/index.d.ts +0 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +0 -1
- package/dist/react/index.js.map +1 -1
- package/dist/routes/helpers.d.ts.map +1 -1
- package/dist/routes/helpers.js +6 -2
- package/dist/routes/helpers.js.map +1 -1
- package/dist/routes/relations.d.ts.map +1 -1
- package/dist/routes/relations.js +12 -0
- package/dist/routes/relations.js.map +1 -1
- package/package.json +6 -1
- package/.turbo/turbo-build.log +0 -8
- package/CLAUDE.md +0 -265
- package/dist/react/useCollabSeed.d.ts +0 -23
- package/dist/react/useCollabSeed.d.ts.map +0 -1
- package/dist/react/useCollabSeed.js +0 -82
- package/dist/react/useCollabSeed.js.map +0 -1
- package/src/Cluster.test.ts +0 -283
- package/src/Cluster.ts +0 -83
- package/src/Column.test.ts +0 -199
- package/src/Column.ts +0 -710
- package/src/Global.test.ts +0 -367
- package/src/Global.ts +0 -169
- package/src/Page.test.ts +0 -114
- package/src/Page.ts +0 -208
- package/src/Pilotiq.perf.test.ts +0 -252
- package/src/Pilotiq.test.ts +0 -129
- package/src/Pilotiq.ts +0 -1158
- package/src/PilotiqRegistry.ts +0 -36
- package/src/PilotiqServiceProvider.ts +0 -121
- package/src/RelationManager.test.ts +0 -400
- package/src/RelationManager.ts +0 -527
- package/src/RenderHook.test.ts +0 -252
- package/src/RenderHook.ts +0 -242
- package/src/Resource.test.ts +0 -284
- package/src/Resource.ts +0 -526
- package/src/RightPanel.test.ts +0 -202
- package/src/RightPanel.ts +0 -132
- package/src/Tab.test.ts +0 -91
- package/src/Tab.ts +0 -156
- package/src/UserMenuItem.ts +0 -145
- package/src/actions/Action.test.ts +0 -2526
- package/src/actions/Action.ts +0 -1515
- package/src/actions/ActionGroup.test.ts +0 -112
- package/src/actions/ActionGroup.ts +0 -173
- package/src/actions/attachFactory.ts +0 -172
- package/src/actions/bulkFactories.ts +0 -168
- package/src/actions/crudFactories.ts +0 -220
- package/src/actions/exportFactory.ts +0 -215
- package/src/actions/factoryHelpers.ts +0 -177
- package/src/actions/importFactory.ts +0 -243
- package/src/actions/index.ts +0 -17
- package/src/actions/m2mFactories.ts +0 -193
- package/src/actions/relationFactories.ts +0 -372
- package/src/applyPageHooks.test.ts +0 -463
- package/src/applyPageHooks.ts +0 -330
- package/src/authorization.test.ts +0 -483
- package/src/breadcrumbs.test.ts +0 -238
- package/src/cells/coerce.test.ts +0 -85
- package/src/cells/coerce.ts +0 -84
- package/src/clusterPaths.ts +0 -35
- package/src/columns/BadgeColumn.test.ts +0 -54
- package/src/columns/BadgeColumn.ts +0 -32
- package/src/columns/BooleanColumn.test.ts +0 -41
- package/src/columns/BooleanColumn.ts +0 -18
- package/src/columns/ColorColumn.test.ts +0 -37
- package/src/columns/ColorColumn.ts +0 -38
- package/src/columns/IconColumn.test.ts +0 -54
- package/src/columns/IconColumn.ts +0 -37
- package/src/columns/ImageColumn.test.ts +0 -41
- package/src/columns/ImageColumn.ts +0 -28
- package/src/columns/SelectColumn.ts +0 -98
- package/src/columns/TextColumn.test.ts +0 -190
- package/src/columns/TextColumn.ts +0 -20
- package/src/columns/TextInputColumn.ts +0 -68
- package/src/columns/ToggleColumn.ts +0 -46
- package/src/columns/editableColumns.test.ts +0 -238
- package/src/columns/index.ts +0 -9
- package/src/defaultGlobalPages.ts +0 -95
- package/src/defaultPages.test.ts +0 -634
- package/src/defaultPages.ts +0 -617
- package/src/defaultViewPage.test.ts +0 -147
- package/src/elements/Form.test.ts +0 -223
- package/src/elements/Form.ts +0 -416
- package/src/elements/ListTabs.ts +0 -28
- package/src/elements/Table.test.ts +0 -422
- package/src/elements/Table.ts +0 -850
- package/src/elements/TableGroup.test.ts +0 -260
- package/src/elements/TableGroup.ts +0 -334
- package/src/elements/dispatchAction.test.ts +0 -463
- package/src/elements/dispatchAction.ts +0 -355
- package/src/elements/dispatchForm.test.ts +0 -477
- package/src/elements/dispatchForm.ts +0 -1993
- package/src/elements/dispatchTable.test.ts +0 -1514
- package/src/elements/dispatchTable.ts +0 -745
- package/src/elements/index.ts +0 -21
- package/src/entries/BadgeEntry.ts +0 -39
- package/src/entries/CodeEntry.test.ts +0 -40
- package/src/entries/CodeEntry.ts +0 -52
- package/src/entries/ColorEntry.ts +0 -63
- package/src/entries/ComponentEntry.test.ts +0 -173
- package/src/entries/ComponentEntry.ts +0 -95
- package/src/entries/Entry.ts +0 -304
- package/src/entries/IconEntry.ts +0 -49
- package/src/entries/ImageEntry.ts +0 -61
- package/src/entries/KeyValueEntry.ts +0 -47
- package/src/entries/RepeatableEntry.test.ts +0 -239
- package/src/entries/RepeatableEntry.ts +0 -173
- package/src/entries/TextEntry.test.ts +0 -394
- package/src/entries/TextEntry.ts +0 -60
- package/src/entries/index.ts +0 -12
- package/src/entries/leaves.test.ts +0 -306
- package/src/entries/registry.ts +0 -54
- package/src/fields/BuilderField.test.ts +0 -1188
- package/src/fields/BuilderField.ts +0 -605
- package/src/fields/BuilderRelationship.test.ts +0 -811
- package/src/fields/CheckboxField.test.ts +0 -44
- package/src/fields/CheckboxField.ts +0 -27
- package/src/fields/CheckboxListField.test.ts +0 -99
- package/src/fields/CheckboxListField.ts +0 -66
- package/src/fields/ColorPickerField.test.ts +0 -33
- package/src/fields/ColorPickerField.ts +0 -25
- package/src/fields/DateField.ts +0 -54
- package/src/fields/DateTimeField.test.ts +0 -55
- package/src/fields/EmailField.ts +0 -16
- package/src/fields/Field.test.ts +0 -654
- package/src/fields/Field.ts +0 -817
- package/src/fields/FileUploadField.test.ts +0 -143
- package/src/fields/FileUploadField.ts +0 -159
- package/src/fields/HiddenField.test.ts +0 -27
- package/src/fields/HiddenField.ts +0 -28
- package/src/fields/KeyValueField.test.ts +0 -105
- package/src/fields/KeyValueField.ts +0 -55
- package/src/fields/MarkdownField.test.ts +0 -167
- package/src/fields/MarkdownField.ts +0 -162
- package/src/fields/NumberField.ts +0 -33
- package/src/fields/RadioField.test.ts +0 -94
- package/src/fields/RadioField.ts +0 -67
- package/src/fields/RepeaterField.test.ts +0 -1806
- package/src/fields/RepeaterField.ts +0 -939
- package/src/fields/RepeaterRelationship.test.ts +0 -1923
- package/src/fields/RepeaterSimple.test.ts +0 -248
- package/src/fields/RowButton.test.ts +0 -219
- package/src/fields/RowButton.ts +0 -135
- package/src/fields/SelectField.test.ts +0 -192
- package/src/fields/SelectField.ts +0 -235
- package/src/fields/SliderField.test.ts +0 -50
- package/src/fields/SliderField.ts +0 -53
- package/src/fields/SlugField.ts +0 -24
- package/src/fields/TagsInputField.test.ts +0 -154
- package/src/fields/TagsInputField.ts +0 -133
- package/src/fields/TextField.test.ts +0 -213
- package/src/fields/TextField.ts +0 -177
- package/src/fields/TextareaField.test.ts +0 -58
- package/src/fields/TextareaField.ts +0 -59
- package/src/fields/ToggleButtonsField.test.ts +0 -106
- package/src/fields/ToggleButtonsField.ts +0 -59
- package/src/fields/ToggleField.ts +0 -16
- package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
- package/src/fields/optionsResolver.ts +0 -95
- package/src/fields/resolveField.ts +0 -28
- package/src/filters/BooleanFilter.ts +0 -35
- package/src/filters/DateRangeFilter.test.ts +0 -194
- package/src/filters/DateRangeFilter.ts +0 -148
- package/src/filters/Filter.test.ts +0 -268
- package/src/filters/Filter.ts +0 -184
- package/src/filters/FormFilter.test.ts +0 -238
- package/src/filters/FormFilter.ts +0 -215
- package/src/filters/MultiSelectFilter.test.ts +0 -119
- package/src/filters/MultiSelectFilter.ts +0 -78
- package/src/filters/QueryBuilderFilter.test.ts +0 -662
- package/src/filters/QueryBuilderFilter.ts +0 -398
- package/src/filters/SelectFilter.ts +0 -46
- package/src/filters/TernaryFilter.test.ts +0 -160
- package/src/filters/TernaryFilter.ts +0 -72
- package/src/filters/TrashedFilter.test.ts +0 -149
- package/src/filters/TrashedFilter.ts +0 -55
- package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
- package/src/filters/queryBuilder/Constraint.ts +0 -115
- package/src/filters/queryBuilder/DateConstraint.ts +0 -69
- package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
- package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
- package/src/filters/queryBuilder/TextConstraint.ts +0 -64
- package/src/filters/queryBuilder/index.ts +0 -12
- package/src/icons/index.ts +0 -2
- package/src/icons/lucide.ts +0 -204
- package/src/icons/registry.test.ts +0 -56
- package/src/icons/registry.ts +0 -41
- package/src/icons/types.ts +0 -47
- package/src/index.ts +0 -525
- package/src/io/csv.test.ts +0 -142
- package/src/io/csv.ts +0 -170
- package/src/nestedRelationManagerData.test.ts +0 -547
- package/src/notifications/Notification.test.ts +0 -210
- package/src/notifications/Notification.ts +0 -354
- package/src/notifications/broadcast.test.ts +0 -110
- package/src/notifications/broadcast.ts +0 -95
- package/src/notifications/database.test.ts +0 -383
- package/src/notifications/database.ts +0 -398
- package/src/notifications/databaseNotifications.test.ts +0 -187
- package/src/notifications/dispatchNotificationAction.test.ts +0 -341
- package/src/notifications/dispatchNotificationAction.ts +0 -142
- package/src/notifications/flash.test.ts +0 -89
- package/src/notifications/flash.ts +0 -71
- package/src/notifications/index.ts +0 -45
- package/src/notifications/registerBroadcastAuth.test.ts +0 -134
- package/src/notifications/registerBroadcastAuth.ts +0 -100
- package/src/notifications/resolveSavedNotification.test.ts +0 -82
- package/src/notifications/resolveSavedNotification.ts +0 -59
- package/src/notifications/types.ts +0 -93
- package/src/orm/m2mAccessor.ts +0 -66
- package/src/orm/modelDefaults.test.ts +0 -633
- package/src/orm/modelDefaults.ts +0 -666
- package/src/pageData/breadcrumbs.ts +0 -288
- package/src/pageData/forms.ts +0 -578
- package/src/pageData/helpers.ts +0 -857
- package/src/pageData/misc.ts +0 -347
- package/src/pageData/navigation.ts +0 -842
- package/src/pageData/relationPages.ts +0 -1248
- package/src/pageData/relationTabs.ts +0 -286
- package/src/pageData/resourcePages.ts +0 -609
- package/src/pageData.test.ts +0 -1545
- package/src/pageData.ts +0 -341
- package/src/plugins/index.ts +0 -8
- package/src/plugins/themeEditor.test.ts +0 -36
- package/src/plugins/themeEditor.ts +0 -45
- package/src/react/AppShell.tsx +0 -251
- package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
- package/src/react/CollabRoomContext.ts +0 -98
- package/src/react/CollabTextRendererRegistry.ts +0 -102
- package/src/react/CommandPalette.tsx +0 -375
- package/src/react/CurrentUserContext.tsx +0 -50
- package/src/react/CustomPageWrapperGate.tsx +0 -69
- package/src/react/CustomPageWrapperRegistry.ts +0 -45
- package/src/react/FieldFocusReporterRegistry.ts +0 -37
- package/src/react/FieldLabelSlotRegistry.ts +0 -30
- package/src/react/FieldPresenceRegistry.ts +0 -46
- package/src/react/FormCollabBindingRegistry.ts +0 -242
- package/src/react/FormStateContext.tsx +0 -591
- package/src/react/HeadHooks.tsx +0 -126
- package/src/react/MarkdownEditorRegistry.test.ts +0 -38
- package/src/react/MarkdownEditorRegistry.ts +0 -107
- package/src/react/NotificationActionStrip.tsx +0 -263
- package/src/react/NotificationBell.tsx +0 -426
- package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
- package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
- package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
- package/src/react/PendingSuggestionsContext.tsx +0 -172
- package/src/react/RecordWrapperGate.tsx +0 -58
- package/src/react/RecordWrapperRegistry.ts +0 -39
- package/src/react/RenderHookSlot.tsx +0 -32
- package/src/react/RightSidebar.tsx +0 -257
- package/src/react/RightSidebarContext.tsx +0 -234
- package/src/react/RightSidebarTrigger.tsx +0 -53
- package/src/react/RowCoordsContext.tsx +0 -23
- package/src/react/SchemaRenderer.tsx +0 -549
- package/src/react/SearchTrigger.tsx +0 -46
- package/src/react/ThemeProvider.tsx +0 -93
- package/src/react/ThemeSettingsPage.tsx +0 -579
- package/src/react/ThemeToggle.tsx +0 -20
- package/src/react/Toaster.tsx +0 -158
- package/src/react/UserMenu.tsx +0 -196
- package/src/react/WidgetDataContext.tsx +0 -157
- package/src/react/cells/EditableCell.tsx +0 -389
- package/src/react/component-slots.test.ts +0 -103
- package/src/react/component-slots.ts +0 -116
- package/src/react/fieldJsHandler.test.ts +0 -166
- package/src/react/fieldJsHandler.ts +0 -79
- package/src/react/fields/BuilderInput.tsx +0 -1078
- package/src/react/fields/CheckboxInput.tsx +0 -39
- package/src/react/fields/CheckboxListInput.tsx +0 -102
- package/src/react/fields/ColorInput.tsx +0 -71
- package/src/react/fields/DateFieldInput.tsx +0 -70
- package/src/react/fields/DateTimeInput.tsx +0 -62
- package/src/react/fields/FieldShell.tsx +0 -348
- package/src/react/fields/FileUploadInput.tsx +0 -639
- package/src/react/fields/HiddenInput.tsx +0 -17
- package/src/react/fields/KeyValueInput.tsx +0 -230
- package/src/react/fields/MarkdownInput.tsx +0 -560
- package/src/react/fields/RadioInput.tsx +0 -81
- package/src/react/fields/RepeaterInput.test.ts +0 -116
- package/src/react/fields/RepeaterInput.tsx +0 -1420
- package/src/react/fields/SelectFieldInput.tsx +0 -280
- package/src/react/fields/SliderInput.tsx +0 -81
- package/src/react/fields/TagsInput.tsx +0 -283
- package/src/react/fields/TextLikeInput.tsx +0 -256
- package/src/react/fields/ToggleButtonsInput.tsx +0 -60
- package/src/react/fields/ToggleFieldInput.tsx +0 -56
- package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
- package/src/react/fields/relationshipRenameDispatch.ts +0 -97
- package/src/react/fields/repeaterReconcile.test.ts +0 -114
- package/src/react/fields/repeaterReconcile.ts +0 -104
- package/src/react/fields/rowChromeButton.tsx +0 -336
- package/src/react/fields/rowState.ts +0 -106
- package/src/react/fields/syncRowGates.test.ts +0 -202
- package/src/react/fields/syncRowGates.ts +0 -66
- package/src/react/fields/textInputControls.tsx +0 -238
- package/src/react/fields/useRowReorderDnd.ts +0 -78
- package/src/react/formStateHelpers.test.ts +0 -508
- package/src/react/formStateHelpers.ts +0 -381
- package/src/react/hooks/use-mobile.ts +0 -19
- package/src/react/icon-context.tsx +0 -60
- package/src/react/index.ts +0 -195
- package/src/react/layouts/SidebarLayout.tsx +0 -250
- package/src/react/layouts/TopbarLayout.tsx +0 -258
- package/src/react/navigate.tsx +0 -37
- package/src/react/onProviderSynced.test.ts +0 -90
- package/src/react/parseRecordEditUrl.test.ts +0 -122
- package/src/react/parseRecordEditUrl.ts +0 -94
- package/src/react/persistedState.ts +0 -40
- package/src/react/registry.ts +0 -48
- package/src/react/right-panel-registry.tsx +0 -47
- package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
- package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
- package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
- package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
- package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
- package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
- package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
- package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
- package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
- package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
- package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
- package/src/react/schemaRenderer/action/buttons.tsx +0 -99
- package/src/react/schemaRenderer/action/helpers.ts +0 -140
- package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
- package/src/react/schemaRenderer/columnFormat.ts +0 -65
- package/src/react/schemaRenderer/constants.ts +0 -50
- package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
- package/src/react/schemaRenderer/form/renderField.tsx +0 -511
- package/src/react/schemaRenderer/helpers.tsx +0 -81
- package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
- package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
- package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
- package/src/react/schemaRenderer/table/filters.tsx +0 -1233
- package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
- package/src/react/schemaRenderer/table/links.tsx +0 -112
- package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
- package/src/react/schemaRenderer/table/url.tsx +0 -143
- package/src/react/theme-preview/apply.ts +0 -99
- package/src/react/theme-preview/build-html.ts +0 -436
- package/src/react/ui/button.tsx +0 -51
- package/src/react/ui/calendar.tsx +0 -67
- package/src/react/ui/checkbox.tsx +0 -29
- package/src/react/ui/dialog.tsx +0 -108
- package/src/react/ui/dropdown-menu.tsx +0 -97
- package/src/react/ui/input.tsx +0 -20
- package/src/react/ui/label.tsx +0 -21
- package/src/react/ui/popover.tsx +0 -50
- package/src/react/ui/select.tsx +0 -169
- package/src/react/ui/separator.tsx +0 -25
- package/src/react/ui/sheet.tsx +0 -136
- package/src/react/ui/sidebar.tsx +0 -723
- package/src/react/ui/skeleton.tsx +0 -13
- package/src/react/ui/slider.tsx +0 -34
- package/src/react/ui/switch.tsx +0 -28
- package/src/react/ui/table.tsx +0 -105
- package/src/react/ui/tabs.tsx +0 -63
- package/src/react/ui/textarea.tsx +0 -18
- package/src/react/ui/tooltip.tsx +0 -64
- package/src/react/useCollabSeed.ts +0 -86
- package/src/react/useResizableWidth.ts +0 -139
- package/src/react/utils.ts +0 -6
- package/src/react/widgetRegistry.test.ts +0 -43
- package/src/react/widgetRegistry.ts +0 -50
- package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
- package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
- package/src/react/widgets/ViewRenderer.tsx +0 -71
- package/src/relationManagerData.test.ts +0 -1595
- package/src/richtext/index.ts +0 -8
- package/src/richtext/registry.ts +0 -89
- package/src/routes/globals.ts +0 -148
- package/src/routes/guard.test.ts +0 -325
- package/src/routes/helpers.ts +0 -700
- package/src/routes/pages.ts +0 -175
- package/src/routes/panel.ts +0 -204
- package/src/routes/relations.ts +0 -1227
- package/src/routes/resources.ts +0 -781
- package/src/routes/theme.ts +0 -91
- package/src/routes-nested-relations.test.ts +0 -676
- package/src/routes-relations.test.ts +0 -972
- package/src/routes.test.ts +0 -2027
- package/src/routes.ts +0 -303
- package/src/schema/Alert.test.ts +0 -109
- package/src/schema/Alert.ts +0 -131
- package/src/schema/Block.ts +0 -169
- package/src/schema/Breadcrumbs.ts +0 -40
- package/src/schema/Card.ts +0 -35
- package/src/schema/Divider.ts +0 -20
- package/src/schema/Element.ts +0 -219
- package/src/schema/EmptyState.test.ts +0 -37
- package/src/schema/EmptyState.ts +0 -63
- package/src/schema/Fieldset.ts +0 -43
- package/src/schema/Grid.ts +0 -43
- package/src/schema/Group.ts +0 -30
- package/src/schema/Heading.ts +0 -39
- package/src/schema/Html.ts +0 -67
- package/src/schema/Icon.ts +0 -54
- package/src/schema/Image.ts +0 -57
- package/src/schema/LinkTag.ts +0 -41
- package/src/schema/Markdown.ts +0 -85
- package/src/schema/MetaTag.ts +0 -41
- package/src/schema/RelationTabs.ts +0 -71
- package/src/schema/ScriptTag.ts +0 -55
- package/src/schema/Section.ts +0 -160
- package/src/schema/ServerDataElement.test.ts +0 -140
- package/src/schema/ServerDataElement.ts +0 -156
- package/src/schema/SlotComponent.test.ts +0 -77
- package/src/schema/SlotComponent.ts +0 -71
- package/src/schema/Split.ts +0 -50
- package/src/schema/Stat.test.ts +0 -118
- package/src/schema/Stat.ts +0 -154
- package/src/schema/StatsOverview.test.ts +0 -141
- package/src/schema/StatsOverview.ts +0 -119
- package/src/schema/StyleTag.ts +0 -35
- package/src/schema/TableWidget.test.ts +0 -297
- package/src/schema/TableWidget.ts +0 -289
- package/src/schema/Tabs.ts +0 -79
- package/src/schema/Text.ts +0 -58
- package/src/schema/UnorderedList.ts +0 -49
- package/src/schema/View.test.ts +0 -111
- package/src/schema/View.ts +0 -127
- package/src/schema/Wizard.ts +0 -220
- package/src/schema/containers.test.ts +0 -564
- package/src/schema/headTags.test.ts +0 -134
- package/src/schema/index.ts +0 -40
- package/src/schema/primes.test.ts +0 -269
- package/src/schema/resolveSchema.test.ts +0 -379
- package/src/schema/resolveSchema.ts +0 -917
- package/src/schema/sanitize.ts +0 -58
- package/src/search.test.ts +0 -446
- package/src/search.ts +0 -178
- package/src/sessionFilters.test.ts +0 -375
- package/src/sessionFilters.ts +0 -143
- package/src/slot-components/index.ts +0 -10
- package/src/slot-components/registry.ts +0 -56
- package/src/styles/file-upload.css +0 -13
- package/src/summarizers/Summarizer.test.ts +0 -84
- package/src/summarizers/Summarizer.ts +0 -123
- package/src/summarizers/index.ts +0 -11
- package/src/theme/base-colors.ts +0 -68
- package/src/theme/chart-colors.ts +0 -50
- package/src/theme/colors.ts +0 -447
- package/src/theme/generate-css.test.ts +0 -139
- package/src/theme/generate-css.ts +0 -44
- package/src/theme/generate-scale.test.ts +0 -106
- package/src/theme/generate-scale.ts +0 -97
- package/src/theme/icon-map.ts +0 -42
- package/src/theme/index.ts +0 -34
- package/src/theme/migrate.test.ts +0 -178
- package/src/theme/migrate.ts +0 -81
- package/src/theme/presets.ts +0 -135
- package/src/theme/radius.ts +0 -18
- package/src/theme/resolve.test.ts +0 -238
- package/src/theme/resolve.ts +0 -96
- package/src/theme/spacing.ts +0 -18
- package/src/theme/storage.test.ts +0 -126
- package/src/theme/storage.ts +0 -106
- package/src/theme/theme-colors.ts +0 -88
- package/src/theme/types.ts +0 -125
- package/src/uploads/UploadAdapter.ts +0 -35
- package/src/uploads/index.ts +0 -2
- package/src/uploads/localUpload.test.ts +0 -70
- package/src/uploads/localUpload.ts +0 -84
- package/src/validation/Validator.ts +0 -49
- package/src/validation/index.ts +0 -28
- package/src/validation/rules.ts +0 -78
- package/src/validation/runValidators.ts +0 -435
- package/src/validation/uniqueValidator.test.ts +0 -196
- package/src/validation/uniqueValidator.ts +0 -133
- package/src/validation/validators.test.ts +0 -268
- package/src/vite.test.ts +0 -184
- package/src/vite.ts +0 -787
- package/src/widgets/index.ts +0 -10
- package/src/widgets/registry.ts +0 -45
- package/src/widgets.test.ts +0 -592
- package/tsconfig.build.json +0 -11
- package/tsconfig.json +0 -4
- package/tsconfig.test.json +0 -10
- package/views/react/Dashboard.tsx +0 -27
- package/views/react/Resources/Form.tsx +0 -102
- package/views/react/Resources/Index.tsx +0 -49
|
@@ -1,426 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import React from 'react'
|
|
4
|
-
import {
|
|
5
|
-
DropdownMenu,
|
|
6
|
-
DropdownMenuContent,
|
|
7
|
-
DropdownMenuTrigger,
|
|
8
|
-
} from './ui/dropdown-menu.js'
|
|
9
|
-
import { useNavigate } from './navigate.js'
|
|
10
|
-
import { useIconFor } from './icon-context.js'
|
|
11
|
-
import { cn } from './utils.js'
|
|
12
|
-
import type { DatabaseNotificationsMeta } from '../pageData.js'
|
|
13
|
-
import type { DatabaseNotificationMeta } from '../notifications/database.js'
|
|
14
|
-
import type { NavigationBadgeColor } from '../Resource.js'
|
|
15
|
-
import type { NotificationMeta as NotificationMetaImport } from '../notifications/Notification.js'
|
|
16
|
-
import { NotificationActionStrip } from './NotificationActionStrip.js'
|
|
17
|
-
import { useToast } from './Toaster.js'
|
|
18
|
-
|
|
19
|
-
const BADGE_COLOR: Record<NavigationBadgeColor, string> = {
|
|
20
|
-
default: 'bg-muted text-muted-foreground',
|
|
21
|
-
primary: 'bg-primary text-primary-foreground',
|
|
22
|
-
success: 'bg-emerald-500 text-white',
|
|
23
|
-
warning: 'bg-amber-500 text-white',
|
|
24
|
-
destructive: 'bg-red-500 text-white',
|
|
25
|
-
info: 'bg-sky-500 text-white',
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const TYPE_DOT: Record<NonNullable<DatabaseNotificationMeta['type']>, string> = {
|
|
29
|
-
info: 'bg-sky-500',
|
|
30
|
-
success: 'bg-emerald-500',
|
|
31
|
-
warning: 'bg-amber-500',
|
|
32
|
-
error: 'bg-red-500',
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Bell-icon dropdown for `Pilotiq.databaseNotifications()`. Renders
|
|
37
|
-
* nothing when `meta` is absent — `panelInfo()` only ships the meta
|
|
38
|
-
* when the panel opted in AND a user resolved.
|
|
39
|
-
*
|
|
40
|
-
* Lifecycle:
|
|
41
|
-
* - First mount: fetch the current list via `meta.listUrl`.
|
|
42
|
-
* - Polling: when `meta.polling > 0`, refetch every N seconds.
|
|
43
|
-
* Pauses while `document.visibilityState !== 'visible'` so a
|
|
44
|
-
* backgrounded tab doesn't keep tickling the server.
|
|
45
|
-
* - Mark-read: optimistic — flips `readAt` immediately, refetches
|
|
46
|
-
* after the POST so the unread count rebases against the server.
|
|
47
|
-
* - Click-through: when a row carries a `url`, the click marks-read
|
|
48
|
-
* + SPA-navigates in the same step.
|
|
49
|
-
*/
|
|
50
|
-
export function NotificationBell({ meta }: { meta?: DatabaseNotificationsMeta }) {
|
|
51
|
-
if (!meta) return null
|
|
52
|
-
const navigate = useNavigate()
|
|
53
|
-
|
|
54
|
-
const [open, setOpen] = React.useState(false)
|
|
55
|
-
const [data, setData] = React.useState<{
|
|
56
|
-
notifications: DatabaseNotificationMeta[]
|
|
57
|
-
unreadCount: number
|
|
58
|
-
} | null>(null)
|
|
59
|
-
const [loading, setLoading] = React.useState(false)
|
|
60
|
-
|
|
61
|
-
// ── Fetch helpers ─────────────────────────────────────
|
|
62
|
-
// Latest-wins seq tracking — same pattern as the global-search
|
|
63
|
-
// palette / live-form state. A slow first request can't clobber a
|
|
64
|
-
// fresh second one.
|
|
65
|
-
const seqRef = React.useRef(0)
|
|
66
|
-
const latestRef = React.useRef(0)
|
|
67
|
-
const refetch = React.useCallback(async () => {
|
|
68
|
-
const seq = ++seqRef.current
|
|
69
|
-
setLoading(true)
|
|
70
|
-
try {
|
|
71
|
-
const r = await fetch(meta.listUrl, {
|
|
72
|
-
headers: { Accept: 'application/json' },
|
|
73
|
-
credentials: 'same-origin',
|
|
74
|
-
})
|
|
75
|
-
if (!r.ok) return
|
|
76
|
-
const json = await r.json() as {
|
|
77
|
-
notifications?: DatabaseNotificationMeta[]
|
|
78
|
-
unreadCount?: number
|
|
79
|
-
}
|
|
80
|
-
if (seq < latestRef.current) return
|
|
81
|
-
latestRef.current = seq
|
|
82
|
-
setData({
|
|
83
|
-
notifications: json.notifications ?? [],
|
|
84
|
-
unreadCount: json.unreadCount ?? 0,
|
|
85
|
-
})
|
|
86
|
-
} catch { /* network-flake; the next poll retries */ }
|
|
87
|
-
finally {
|
|
88
|
-
if (seq >= latestRef.current) setLoading(false)
|
|
89
|
-
}
|
|
90
|
-
}, [meta.listUrl])
|
|
91
|
-
|
|
92
|
-
// ── First mount + polling ────────────────────────────
|
|
93
|
-
React.useEffect(() => {
|
|
94
|
-
void refetch()
|
|
95
|
-
}, [refetch])
|
|
96
|
-
|
|
97
|
-
React.useEffect(() => {
|
|
98
|
-
if (meta.polling === null || meta.polling === 0) return
|
|
99
|
-
const ms = meta.polling * 1000
|
|
100
|
-
const tick = () => {
|
|
101
|
-
if (typeof document !== 'undefined' && document.visibilityState !== 'visible') return
|
|
102
|
-
void refetch()
|
|
103
|
-
}
|
|
104
|
-
const id = window.setInterval(tick, ms)
|
|
105
|
-
return () => window.clearInterval(id)
|
|
106
|
-
}, [meta.polling, refetch])
|
|
107
|
-
|
|
108
|
-
// ── Phase 2 broadcast subscription ───────────────────
|
|
109
|
-
// When `meta.broadcast` is present, connect to the panel's WebSocket
|
|
110
|
-
// and listen for `notification.created` on the user's private channel.
|
|
111
|
-
// Triggers the same `refetch()` already wired for polling — broadcast
|
|
112
|
-
// is "low-latency refetch hint", not a payload-pushing transport.
|
|
113
|
-
//
|
|
114
|
-
// RudderSocket loads via dynamic import so the bell doesn't bundle
|
|
115
|
-
// broadcast for apps that don't enable it. Failures (package missing,
|
|
116
|
-
// wsUrl unreachable, auth rejected) silently fall back to polling.
|
|
117
|
-
React.useEffect(() => {
|
|
118
|
-
if (!meta.broadcast) return
|
|
119
|
-
if (typeof window === 'undefined') return
|
|
120
|
-
let cancelled = false
|
|
121
|
-
let socket: { disconnect(): void } | null = null
|
|
122
|
-
|
|
123
|
-
const wsUrl = meta.broadcast.wsUrl || sameOriginWsUrl()
|
|
124
|
-
const channelName = meta.broadcast.channel
|
|
125
|
-
const eventName = meta.broadcast.event
|
|
126
|
-
|
|
127
|
-
void (async () => {
|
|
128
|
-
try {
|
|
129
|
-
const RudderSocket = await loadRudderSocket()
|
|
130
|
-
if (!RudderSocket || cancelled) return
|
|
131
|
-
const s = new RudderSocket(wsUrl)
|
|
132
|
-
socket = s
|
|
133
|
-
// Strip the `private-` prefix — `RudderSocket.private(name)`
|
|
134
|
-
// re-adds it. We pass an empty token because the auth callback
|
|
135
|
-
// we registered server-side reads the upgrade request's cookies,
|
|
136
|
-
// not a per-message bearer token.
|
|
137
|
-
const bareName = channelName.replace(/^private-/, '')
|
|
138
|
-
s.private(bareName, '').on(eventName, () => {
|
|
139
|
-
if (cancelled) return
|
|
140
|
-
void refetch()
|
|
141
|
-
})
|
|
142
|
-
} catch { /* package not vendored / connect failed — polling covers it */ }
|
|
143
|
-
})()
|
|
144
|
-
|
|
145
|
-
return () => {
|
|
146
|
-
cancelled = true
|
|
147
|
-
socket?.disconnect()
|
|
148
|
-
}
|
|
149
|
-
}, [meta.broadcast?.wsUrl, meta.broadcast?.channel, meta.broadcast?.event, refetch])
|
|
150
|
-
|
|
151
|
-
// ── Mutations ────────────────────────────────────────
|
|
152
|
-
const markRead = React.useCallback(async (id: string) => {
|
|
153
|
-
// Optimistic flip — the bell stays responsive even on slow networks.
|
|
154
|
-
setData(prev => prev && {
|
|
155
|
-
notifications: prev.notifications.map(n =>
|
|
156
|
-
n.id === id ? { ...n, readAt: n.readAt ?? new Date().toISOString() } : n,
|
|
157
|
-
),
|
|
158
|
-
unreadCount: Math.max(0, prev.unreadCount - 1),
|
|
159
|
-
})
|
|
160
|
-
try {
|
|
161
|
-
await fetch(meta.readUrl.replace(':id', encodeURIComponent(id)), {
|
|
162
|
-
method: 'POST',
|
|
163
|
-
headers: { Accept: 'application/json' },
|
|
164
|
-
credentials: 'same-origin',
|
|
165
|
-
})
|
|
166
|
-
} catch { /* swallow — next poll resyncs */ }
|
|
167
|
-
void refetch()
|
|
168
|
-
}, [meta.readUrl, refetch])
|
|
169
|
-
|
|
170
|
-
const markAllRead = React.useCallback(async () => {
|
|
171
|
-
setData(prev => prev && {
|
|
172
|
-
notifications: prev.notifications.map(n =>
|
|
173
|
-
n.readAt ? n : { ...n, readAt: new Date().toISOString() }),
|
|
174
|
-
unreadCount: 0,
|
|
175
|
-
})
|
|
176
|
-
try {
|
|
177
|
-
await fetch(meta.readAllUrl, {
|
|
178
|
-
method: 'POST',
|
|
179
|
-
headers: { Accept: 'application/json' },
|
|
180
|
-
credentials: 'same-origin',
|
|
181
|
-
})
|
|
182
|
-
} catch { /* swallow */ }
|
|
183
|
-
void refetch()
|
|
184
|
-
}, [meta.readAllUrl, refetch])
|
|
185
|
-
|
|
186
|
-
const unreadCount = data?.unreadCount ?? 0
|
|
187
|
-
const items = data?.notifications ?? []
|
|
188
|
-
const toast = useToast()
|
|
189
|
-
|
|
190
|
-
return (
|
|
191
|
-
<DropdownMenu open={open} onOpenChange={setOpen}>
|
|
192
|
-
<DropdownMenuTrigger
|
|
193
|
-
className="relative inline-flex items-center justify-center size-9 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors focus:outline-none"
|
|
194
|
-
aria-label={meta.trigger?.label ?? 'Notifications'}
|
|
195
|
-
>
|
|
196
|
-
<BellIcon icon={meta.trigger?.icon} />
|
|
197
|
-
{unreadCount > 0 && (
|
|
198
|
-
<span
|
|
199
|
-
className={cn(
|
|
200
|
-
'absolute -top-0.5 -end-0.5 inline-flex min-w-[1rem] h-4 items-center justify-center rounded-full px-1 text-[10px] font-semibold leading-none',
|
|
201
|
-
BADGE_COLOR[meta.badgeColor],
|
|
202
|
-
)}
|
|
203
|
-
aria-label={`${unreadCount} unread`}
|
|
204
|
-
>
|
|
205
|
-
{unreadCount > 99 ? '99+' : unreadCount}
|
|
206
|
-
</span>
|
|
207
|
-
)}
|
|
208
|
-
</DropdownMenuTrigger>
|
|
209
|
-
|
|
210
|
-
<DropdownMenuContent align="end" className="w-80 p-0">
|
|
211
|
-
<header className="flex items-center justify-between px-3 py-2 border-b">
|
|
212
|
-
<span className="text-sm font-medium">Notifications</span>
|
|
213
|
-
{unreadCount > 0 && (
|
|
214
|
-
<button
|
|
215
|
-
type="button"
|
|
216
|
-
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
|
|
217
|
-
onClick={() => { void markAllRead() }}
|
|
218
|
-
>
|
|
219
|
-
Mark all as read
|
|
220
|
-
</button>
|
|
221
|
-
)}
|
|
222
|
-
</header>
|
|
223
|
-
|
|
224
|
-
<div className="max-h-96 overflow-y-auto">
|
|
225
|
-
{items.length === 0
|
|
226
|
-
? <EmptyState loading={loading} />
|
|
227
|
-
: items.map(n => (
|
|
228
|
-
<NotificationRow
|
|
229
|
-
key={n.id}
|
|
230
|
-
notification={n}
|
|
231
|
-
actionUrlTemplate={meta.actionUrl}
|
|
232
|
-
onMarkRead={markRead}
|
|
233
|
-
onNavigate={(href) => { setOpen(false); navigate(href) }}
|
|
234
|
-
onNotify={(notifs) => notifs.forEach(t => toast.notify(t))}
|
|
235
|
-
onAfterClick={() => setOpen(false)}
|
|
236
|
-
/>
|
|
237
|
-
))
|
|
238
|
-
}
|
|
239
|
-
</div>
|
|
240
|
-
</DropdownMenuContent>
|
|
241
|
-
</DropdownMenu>
|
|
242
|
-
)
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function NotificationRow({
|
|
246
|
-
notification: n,
|
|
247
|
-
actionUrlTemplate,
|
|
248
|
-
onMarkRead,
|
|
249
|
-
onNavigate,
|
|
250
|
-
onNotify,
|
|
251
|
-
onAfterClick,
|
|
252
|
-
}: {
|
|
253
|
-
notification: DatabaseNotificationMeta
|
|
254
|
-
actionUrlTemplate?: string
|
|
255
|
-
onMarkRead: (id: string) => void
|
|
256
|
-
onNavigate: (url: string) => void
|
|
257
|
-
onNotify: (notifs: NotificationMetaImport[]) => void
|
|
258
|
-
onAfterClick: () => void
|
|
259
|
-
}) {
|
|
260
|
-
const RowIcon = useIconFor(n.icon)
|
|
261
|
-
const unread = !n.readAt
|
|
262
|
-
|
|
263
|
-
const onClick = (e: React.MouseEvent) => {
|
|
264
|
-
// Modified clicks (cmd/ctrl/middle) preserve native semantics.
|
|
265
|
-
if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.shiftKey) return
|
|
266
|
-
e.preventDefault()
|
|
267
|
-
if (unread) onMarkRead(n.id)
|
|
268
|
-
if (n.url) onNavigate(n.url)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Body wrapper is `<a>` when the row has a click-through url, plain
|
|
272
|
-
// `<button>` otherwise. Form-mode + handler-mode action buttons in
|
|
273
|
-
// the strip can't validly nest inside an `<a>`/`<button>`, so the
|
|
274
|
-
// strip lives as a sibling under a shared chrome `<div>`.
|
|
275
|
-
const Tag: 'a' | 'button' = n.url ? 'a' : 'button'
|
|
276
|
-
const tagProps: React.AnchorHTMLAttributes<HTMLAnchorElement> & React.ButtonHTMLAttributes<HTMLButtonElement> = {
|
|
277
|
-
onClick,
|
|
278
|
-
className: 'w-full text-start flex items-start gap-2 focus:outline-none',
|
|
279
|
-
}
|
|
280
|
-
if (Tag === 'a') {
|
|
281
|
-
tagProps.href = n.url!
|
|
282
|
-
} else {
|
|
283
|
-
tagProps.type = 'button'
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return (
|
|
287
|
-
<div
|
|
288
|
-
className={cn(
|
|
289
|
-
'px-3 py-2 hover:bg-accent transition-colors border-b last:border-b-0',
|
|
290
|
-
unread && 'bg-primary/5',
|
|
291
|
-
)}
|
|
292
|
-
>
|
|
293
|
-
<Tag {...(tagProps as React.HTMLAttributes<HTMLElement>)}>
|
|
294
|
-
<span
|
|
295
|
-
className={cn(
|
|
296
|
-
'mt-1.5 inline-block size-2 rounded-full shrink-0',
|
|
297
|
-
n.type ? TYPE_DOT[n.type] : 'bg-muted-foreground/40',
|
|
298
|
-
)}
|
|
299
|
-
aria-hidden="true"
|
|
300
|
-
/>
|
|
301
|
-
<span className="flex-1 min-w-0">
|
|
302
|
-
<span className="flex items-center gap-1.5">
|
|
303
|
-
{RowIcon && <RowIcon className="size-3.5 text-muted-foreground" aria-hidden="true" />}
|
|
304
|
-
<span className={cn('text-sm leading-tight', unread ? 'font-medium' : '')}>
|
|
305
|
-
{n.title || 'Notification'}
|
|
306
|
-
</span>
|
|
307
|
-
</span>
|
|
308
|
-
{n.body && (
|
|
309
|
-
<span className="block text-xs text-muted-foreground mt-0.5 line-clamp-2">
|
|
310
|
-
{n.body}
|
|
311
|
-
</span>
|
|
312
|
-
)}
|
|
313
|
-
<span className="block text-[11px] text-muted-foreground/70 mt-1">
|
|
314
|
-
{formatRelative(n.createdAt)}
|
|
315
|
-
</span>
|
|
316
|
-
</span>
|
|
317
|
-
</Tag>
|
|
318
|
-
{n.actions && n.actions.length > 0 && (
|
|
319
|
-
<div className="ms-4">
|
|
320
|
-
<NotificationActionStrip
|
|
321
|
-
actions={n.actions}
|
|
322
|
-
{...(actionUrlTemplate !== undefined ? { actionUrlTemplate } : {})}
|
|
323
|
-
notificationId={n.id}
|
|
324
|
-
onMarkAsRead={onMarkRead}
|
|
325
|
-
onNotify={onNotify}
|
|
326
|
-
onAfterClick={onAfterClick}
|
|
327
|
-
/>
|
|
328
|
-
</div>
|
|
329
|
-
)}
|
|
330
|
-
</div>
|
|
331
|
-
)
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function EmptyState({ loading }: { loading: boolean }) {
|
|
335
|
-
return (
|
|
336
|
-
<div className="px-3 py-8 text-center text-sm text-muted-foreground">
|
|
337
|
-
{loading ? 'Loading…' : 'No notifications'}
|
|
338
|
-
</div>
|
|
339
|
-
)
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function BellIcon({ icon }: { icon: string | undefined }) {
|
|
343
|
-
// Honor `meta.trigger.icon` when set (string registry name); fall
|
|
344
|
-
// through to the bundled bell glyph otherwise. The registry-resolved
|
|
345
|
-
// hook returns `null` when no entry is registered, so a misspelt name
|
|
346
|
-
// still shows the default bell — better than a silent blank.
|
|
347
|
-
const Icon = useIconFor(icon)
|
|
348
|
-
if (Icon) return <Icon className="size-5" aria-hidden="true" />
|
|
349
|
-
return (
|
|
350
|
-
<svg
|
|
351
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
352
|
-
width="20" height="20" viewBox="0 0 24 24" fill="none"
|
|
353
|
-
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
|
|
354
|
-
aria-hidden="true"
|
|
355
|
-
>
|
|
356
|
-
<path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" />
|
|
357
|
-
<path d="M10.3 21a1.94 1.94 0 0 0 3.4 0" />
|
|
358
|
-
</svg>
|
|
359
|
-
)
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/** Soft-load `RudderSocket` from whichever path the consuming app
|
|
363
|
-
* vendored it to. `@rudderjs/broadcast` ships the client as
|
|
364
|
-
* `client/RudderSocket.ts`, intended for vendor:publish copy into the
|
|
365
|
-
* app's `src/`. We try a stack of common locations in order, returning
|
|
366
|
-
* the first that resolves; falls through to `null` when nothing's
|
|
367
|
-
* reachable so the bell silently continues with polling.
|
|
368
|
-
*
|
|
369
|
-
* Apps that vendor the file under a custom path can register their
|
|
370
|
-
* own loader by setting `window.__pilotiqRudderSocket` to the class
|
|
371
|
-
* constructor before the bell mounts. */
|
|
372
|
-
type RudderSocketCtor = new (url: string) => {
|
|
373
|
-
private(name: string, token: string): {
|
|
374
|
-
on(event: string, handler: (data: unknown) => void): unknown
|
|
375
|
-
}
|
|
376
|
-
disconnect(): void
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
async function loadRudderSocket(): Promise<RudderSocketCtor | null> {
|
|
380
|
-
// Allow apps to register a custom-path RudderSocket without us
|
|
381
|
-
// having to guess at the import path.
|
|
382
|
-
const w = typeof window !== 'undefined'
|
|
383
|
-
? (window as unknown as { __pilotiqRudderSocket?: RudderSocketCtor })
|
|
384
|
-
: null
|
|
385
|
-
if (w?.__pilotiqRudderSocket) return w.__pilotiqRudderSocket
|
|
386
|
-
|
|
387
|
-
// Apps that vendor:publish the broadcast client typically end up
|
|
388
|
-
// with `src/RudderSocket.ts` (or `.js`); try common shapes via the
|
|
389
|
-
// app's import-map / module resolution. The dynamic import is
|
|
390
|
-
// wrapped in a try/catch so missing modules don't surface.
|
|
391
|
-
const candidates = [
|
|
392
|
-
'@rudderjs/broadcast/client/RudderSocket.js',
|
|
393
|
-
'@rudderjs/broadcast/client/RudderSocket',
|
|
394
|
-
]
|
|
395
|
-
for (const id of candidates) {
|
|
396
|
-
try {
|
|
397
|
-
const mod = await import(/* @vite-ignore */ id) as { RudderSocket?: RudderSocketCtor }
|
|
398
|
-
if (mod.RudderSocket) return mod.RudderSocket
|
|
399
|
-
} catch { /* try next */ }
|
|
400
|
-
}
|
|
401
|
-
return null
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/** Same-origin `ws://…/ws` (or `wss://` on https). Used when
|
|
405
|
-
* `meta.broadcast.wsUrl` is empty — the default for apps that boot
|
|
406
|
-
* `BroadcastingProvider` with no custom path. */
|
|
407
|
-
function sameOriginWsUrl(): string {
|
|
408
|
-
if (typeof window === 'undefined') return ''
|
|
409
|
-
const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
410
|
-
return `${proto}//${window.location.host}/ws`
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
function formatRelative(ts: number): string {
|
|
414
|
-
const now = Date.now()
|
|
415
|
-
const diff = Math.max(0, now - ts)
|
|
416
|
-
const sec = Math.floor(diff / 1000)
|
|
417
|
-
if (sec < 45) return 'Just now'
|
|
418
|
-
if (sec < 90) return '1 minute ago'
|
|
419
|
-
const min = Math.floor(sec / 60)
|
|
420
|
-
if (min < 60) return `${min} minutes ago`
|
|
421
|
-
const hr = Math.floor(min / 60)
|
|
422
|
-
if (hr < 24) return `${hr} hour${hr === 1 ? '' : 's'} ago`
|
|
423
|
-
const day = Math.floor(hr / 24)
|
|
424
|
-
if (day < 7) return `${day} day${day === 1 ? '' : 's'} ago`
|
|
425
|
-
return new Date(ts).toLocaleDateString()
|
|
426
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
registerPendingSuggestionApplier,
|
|
6
|
-
getPendingSuggestionApplier,
|
|
7
|
-
_clearAppliersForTests,
|
|
8
|
-
} from './PendingSuggestionApplierRegistry.js'
|
|
9
|
-
|
|
10
|
-
describe('PendingSuggestionApplierRegistry', () => {
|
|
11
|
-
beforeEach(() => { _clearAppliersForTests() })
|
|
12
|
-
|
|
13
|
-
it('returns the scoped applier when both formId and fieldName match', () => {
|
|
14
|
-
const apply = (): void => {}
|
|
15
|
-
registerPendingSuggestionApplier('form-1', 'bio', apply)
|
|
16
|
-
assert.equal(getPendingSuggestionApplier('form-1', 'bio'), apply)
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('routes scoped lookups to the matching scoped entry across multi-form pages', () => {
|
|
20
|
-
// Two editors in two forms register under the same field name but
|
|
21
|
-
// different formIds — the routing has to disambiguate.
|
|
22
|
-
const applyA = (): void => {}
|
|
23
|
-
const applyB = (): void => {}
|
|
24
|
-
registerPendingSuggestionApplier('form-A', 'summary', applyA)
|
|
25
|
-
registerPendingSuggestionApplier('form-B', 'summary', applyB)
|
|
26
|
-
assert.equal(getPendingSuggestionApplier('form-A', 'summary'), applyA)
|
|
27
|
-
assert.equal(getPendingSuggestionApplier('form-B', 'summary'), applyB)
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it('falls through to the wildcard slot when no scoped match exists', () => {
|
|
31
|
-
const wild = (): void => {}
|
|
32
|
-
registerPendingSuggestionApplier(undefined, 'bio', wild)
|
|
33
|
-
// formId provided but the registry only has a wildcard entry —
|
|
34
|
-
// the lookup should still resolve so historic single-form pages keep
|
|
35
|
-
// working even when consumers thread a formId.
|
|
36
|
-
assert.equal(getPendingSuggestionApplier('form-1', 'bio'), wild)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('global producer + scoped consumer: undefined-formId lookup finds the scoped entry', () => {
|
|
40
|
-
// Regression guard for the multi-form fix: after threading `formId`
|
|
41
|
-
// through the Tiptap adapter hooks, every editor registers scoped
|
|
42
|
-
// by its surrounding `FormRenderer`'s id. A global producer (no
|
|
43
|
-
// `formId` stamped on the suggestion) calls
|
|
44
|
-
// `getPendingSuggestionApplier(undefined, fieldName)` — without the
|
|
45
|
-
// fallback, the wildcard slot is empty and the suggestion silently
|
|
46
|
-
// never applies. The fallback returns any matching scoped entry.
|
|
47
|
-
const scopedApply = (): void => {}
|
|
48
|
-
registerPendingSuggestionApplier('form-edit', 'bio', scopedApply)
|
|
49
|
-
assert.equal(getPendingSuggestionApplier(undefined, 'bio'), scopedApply)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('global lookup prefers the explicit wildcard slot over scoped fallback', () => {
|
|
53
|
-
const wild = (): void => {}
|
|
54
|
-
const scoped = (): void => {}
|
|
55
|
-
registerPendingSuggestionApplier('form-edit', 'bio', scoped)
|
|
56
|
-
registerPendingSuggestionApplier(undefined, 'bio', wild)
|
|
57
|
-
// Wildcard wins — it was the intent of the original API ("formId
|
|
58
|
-
// defaults to '*'") and a deliberately-registered wildcard applier
|
|
59
|
-
// is presumed authoritative.
|
|
60
|
-
assert.equal(getPendingSuggestionApplier(undefined, 'bio'), wild)
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
it('scoped lookup prefers exact match over wildcard slot', () => {
|
|
64
|
-
const wild = (): void => {}
|
|
65
|
-
const scoped = (): void => {}
|
|
66
|
-
registerPendingSuggestionApplier(undefined, 'bio', wild)
|
|
67
|
-
registerPendingSuggestionApplier('form-edit', 'bio', scoped)
|
|
68
|
-
assert.equal(getPendingSuggestionApplier('form-edit', 'bio'), scoped)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('returns undefined when no entry matches the fieldName at all', () => {
|
|
72
|
-
registerPendingSuggestionApplier('form-A', 'bio', () => {})
|
|
73
|
-
assert.equal(getPendingSuggestionApplier('form-A', 'subtitle'), undefined)
|
|
74
|
-
assert.equal(getPendingSuggestionApplier(undefined, 'subtitle'), undefined)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('unregister cleanup drops the entry', () => {
|
|
78
|
-
const apply = (): void => {}
|
|
79
|
-
const unregister = registerPendingSuggestionApplier('form-1', 'bio', apply)
|
|
80
|
-
assert.equal(getPendingSuggestionApplier('form-1', 'bio'), apply)
|
|
81
|
-
unregister()
|
|
82
|
-
assert.equal(getPendingSuggestionApplier('form-1', 'bio'), undefined)
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it('re-registering replaces the previous entry and its unregister no-ops', () => {
|
|
86
|
-
const first = (): void => {}
|
|
87
|
-
const second = (): void => {}
|
|
88
|
-
const off1 = registerPendingSuggestionApplier('form-1', 'bio', first)
|
|
89
|
-
registerPendingSuggestionApplier('form-1', 'bio', second) // wins
|
|
90
|
-
assert.equal(getPendingSuggestionApplier('form-1', 'bio'), second)
|
|
91
|
-
// First's unregister must NOT delete the second's entry — the
|
|
92
|
-
// registry tracks identity to defend against unmount-after-remount
|
|
93
|
-
// racing the cleanup of the just-replaced entry.
|
|
94
|
-
off1()
|
|
95
|
-
assert.equal(getPendingSuggestionApplier('form-1', 'bio'), second)
|
|
96
|
-
})
|
|
97
|
-
})
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import type { PendingSuggestion } from './PendingSuggestionsContext.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* A function that applies a `PendingSuggestion` to its target field —
|
|
5
|
-
* registered by the field renderer (or editor adapter) on mount, looked
|
|
6
|
-
* up by aggregate consumers (e.g. a chat-sidebar pending-pill's
|
|
7
|
-
* "Approve all" button) that live outside the form's React tree.
|
|
8
|
-
*
|
|
9
|
-
* The applier is responsible for the apply *only*. Dismissing the
|
|
10
|
-
* suggestion from the queue is the caller's job — the apply path is
|
|
11
|
-
* decoupled from the queue-side bookkeeping so a future Phase that
|
|
12
|
-
* mirrors approvals to a server can do both via different code paths.
|
|
13
|
-
*/
|
|
14
|
-
export type PendingSuggestionApplier = (suggestion: PendingSuggestion) => void
|
|
15
|
-
|
|
16
|
-
interface RegistryEntry {
|
|
17
|
-
formId: string | undefined
|
|
18
|
-
fieldName: string
|
|
19
|
-
apply: PendingSuggestionApplier
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const _entries = new Map<string, RegistryEntry>()
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Compose the registry key. `formId` defaults to `'*'` (global form
|
|
26
|
-
* scope) so renderers in non-multi-form pages don't have to thread an
|
|
27
|
-
* id. Form-scoped registrations always win over the wildcard when both
|
|
28
|
-
* exist for the same field name.
|
|
29
|
-
*/
|
|
30
|
-
function keyFor(formId: string | undefined, fieldName: string): string {
|
|
31
|
-
return `${formId ?? '*'}::${fieldName}`
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Register an applier for `(formId, fieldName)`. Returns an unregister
|
|
36
|
-
* function for `useEffect` cleanup. Re-registering with the same key
|
|
37
|
-
* replaces the previous entry — the most recently mounted renderer
|
|
38
|
-
* wins (typical in multi-instance form scenarios where an old form
|
|
39
|
-
* unmounts after a new one mounts during navigation).
|
|
40
|
-
*/
|
|
41
|
-
export function registerPendingSuggestionApplier(
|
|
42
|
-
formId: string | undefined,
|
|
43
|
-
fieldName: string,
|
|
44
|
-
apply: PendingSuggestionApplier,
|
|
45
|
-
): () => void {
|
|
46
|
-
const key = keyFor(formId, fieldName)
|
|
47
|
-
const entry: RegistryEntry = { formId, fieldName, apply }
|
|
48
|
-
_entries.set(key, entry)
|
|
49
|
-
return () => {
|
|
50
|
-
// Only delete if this entry is still the one we registered — a
|
|
51
|
-
// re-register from another instance may have replaced us.
|
|
52
|
-
if (_entries.get(key) === entry) _entries.delete(key)
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Look up an applier for `(formId, fieldName)`. Tries the form-scoped
|
|
58
|
-
* key first; falls back to the wildcard form ('*') so a producer that
|
|
59
|
-
* pushed a suggestion without `formId` still resolves an applier from
|
|
60
|
-
* a single-form page.
|
|
61
|
-
*
|
|
62
|
-
* Global-producer fallback: when the lookup `formId` is `undefined` AND
|
|
63
|
-
* no wildcard entry is registered, return any single scoped entry
|
|
64
|
-
* matching `fieldName`. This mirrors the consumer-side filter in
|
|
65
|
-
* `usePendingSuggestionsForField` which lets undefined formId on either
|
|
66
|
-
* side pass-through. Editors today register scoped by their surrounding
|
|
67
|
-
* `FormRenderer`'s id (`useFormId()`), so the wildcard slot is almost
|
|
68
|
-
* always empty — without this fallback, a producer that pushes without
|
|
69
|
-
* a formId on a single-form page would silently fail to resolve any
|
|
70
|
-
* applier. We pick the first scoped match (Map insertion order); when
|
|
71
|
-
* the page genuinely has multiple forms with the same field name,
|
|
72
|
-
* producers SHOULD stamp `formId` to disambiguate.
|
|
73
|
-
*/
|
|
74
|
-
export function getPendingSuggestionApplier(
|
|
75
|
-
formId: string | undefined,
|
|
76
|
-
fieldName: string,
|
|
77
|
-
): PendingSuggestionApplier | undefined {
|
|
78
|
-
if (formId !== undefined) {
|
|
79
|
-
const scoped = _entries.get(keyFor(formId, fieldName))
|
|
80
|
-
if (scoped) return scoped.apply
|
|
81
|
-
}
|
|
82
|
-
const wild = _entries.get(keyFor(undefined, fieldName))
|
|
83
|
-
if (wild) return wild.apply
|
|
84
|
-
if (formId === undefined) {
|
|
85
|
-
for (const entry of _entries.values()) {
|
|
86
|
-
if (entry.fieldName === fieldName) return entry.apply
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return undefined
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Test seam — clear the registry between tests. Not part of the public
|
|
94
|
-
* API.
|
|
95
|
-
*/
|
|
96
|
-
export function _clearAppliersForTests(): void {
|
|
97
|
-
_entries.clear()
|
|
98
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import type { ComponentType } from 'react'
|
|
2
|
-
import type { PendingSuggestion } from './PendingSuggestionsContext.js'
|
|
3
|
-
import type { ElementMeta } from '../schema/Element.js'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Props the per-field overlay component receives from `FieldShell` when
|
|
7
|
-
* one or more pending suggestions target the field.
|
|
8
|
-
*
|
|
9
|
-
* The overlay is responsible for both rendering AND applying the
|
|
10
|
-
* suggestion. On Approve, call `onApprove()` (which dismisses the
|
|
11
|
-
* suggestion from the queue) AFTER mutating the form's field value to
|
|
12
|
-
* `suggestion.suggestedValue` — the queue is just a notification surface,
|
|
13
|
-
* applying is the renderer's job.
|
|
14
|
-
*
|
|
15
|
-
* The Tiptap `RichTextField` renderer skips this slot entirely — it
|
|
16
|
-
* mirrors suggestions into the editor's inline AiSuggestion extension
|
|
17
|
-
* instead. Other field types (Text / Textarea / Select / Number / …)
|
|
18
|
-
* render the registered overlay below their input.
|
|
19
|
-
*/
|
|
20
|
-
export interface PendingSuggestionOverlayProps {
|
|
21
|
-
/** First suggestion targeting this field. Aggregate UIs handle stacks. */
|
|
22
|
-
suggestion: PendingSuggestion
|
|
23
|
-
/** Drop from queue (callable after applying). */
|
|
24
|
-
onApprove: () => void
|
|
25
|
-
/** Drop from queue without applying. */
|
|
26
|
-
onReject: () => void
|
|
27
|
-
/** Field type string (`text` / `select` / `toggle` / `slider` / `color` /
|
|
28
|
-
* …) so per-fieldType overlay renderers can branch. Sparse — older
|
|
29
|
-
* hosts may omit. Phase C of ai-review-mode. */
|
|
30
|
-
fieldType?: string
|
|
31
|
-
/** Resolved field meta — gives the overlay access to per-field config
|
|
32
|
-
* (`options` for Select, `min/max` for Slider, etc.) needed to render
|
|
33
|
-
* human-friendly comparisons rather than raw values. Sparse — older
|
|
34
|
-
* hosts may omit. */
|
|
35
|
-
el?: ElementMeta
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
let _component: ComponentType<PendingSuggestionOverlayProps> | null = null
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Register a component to render below any `FieldShell` whose field has at
|
|
42
|
-
* least one matching pending suggestion in the
|
|
43
|
-
* `<PendingSuggestionsContext>` queue. Called once at boot by a plugin
|
|
44
|
-
* (e.g. `@pilotiq-pro/ai`). No-op when no plugin registers — `FieldShell`
|
|
45
|
-
* skips the overlay slot.
|
|
46
|
-
*/
|
|
47
|
-
export function registerPendingSuggestionOverlay(C: ComponentType<PendingSuggestionOverlayProps>): void {
|
|
48
|
-
_component = C
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Returns the registered overlay component, or `null`. */
|
|
52
|
-
export function getPendingSuggestionOverlay(): ComponentType<PendingSuggestionOverlayProps> | null {
|
|
53
|
-
return _component
|
|
54
|
-
}
|