betterstart-cli 0.0.112 → 0.0.113

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.
@@ -112,7 +112,7 @@ export function MediaUrlImporter({ onImportFromUrl, accept, className }: MediaUr
112
112
  <form
113
113
  autoComplete="off"
114
114
  noValidate
115
- onSubmit={form.handleSubmit(handleImport, handleInvalid)}
115
+ onSubmit={(event) => form.handleSubmit(handleImport, handleInvalid)(event)}
116
116
  className="w-full"
117
117
  >
118
118
  <InputGroup className="mx-auto max-w-2xl bg-background">
@@ -23,9 +23,11 @@ export function useContentEditorSlashMenu(editorRef: React.RefObject<Editor | nu
23
23
  const activeSlashIndex =
24
24
  activeSlashIndexState >= filteredSlashCommands.length ? 0 : activeSlashIndexState
25
25
 
26
- slashMenuRef.current = slashMenu
27
- activeSlashIndexRef.current = activeSlashIndex
28
- filteredSlashCommandsRef.current = filteredSlashCommands
26
+ React.useEffect(() => {
27
+ slashMenuRef.current = slashMenu
28
+ activeSlashIndexRef.current = activeSlashIndex
29
+ filteredSlashCommandsRef.current = filteredSlashCommands
30
+ })
29
31
 
30
32
  const updateSlashMenu = React.useCallback((nextEditor: Editor) => {
31
33
  const nextSlashMenu = getSlashMenuState(nextEditor)
@@ -1,5 +1,8 @@
1
1
  'use client'
2
2
 
3
+ // oxlint-disable react/refs -- the CodeMirror widget is constructed during render and compared by
4
+ // callback identity in `eq()`. Dropping the ref-backed callbacks would rebuild the widget DOM on
5
+ // every render, so it needs a verified refactor.
3
6
  import * as React from 'react'
4
7
  import type { ContentEditorSelectionRequest } from '@admin/utils/editor/content-editor-selection-request'
5
8
  import type { MarkdownHeadingLevel } from '@admin/utils/editor/markdown-heading-level'
@@ -151,9 +154,12 @@ export function useContentEditorSourceMode({
151
154
  const [canUndoMarkdownChange, setCanUndoMarkdownChange] = React.useState(false)
152
155
  const [canRedoMarkdownChange, setCanRedoMarkdownChange] = React.useState(false)
153
156
  const valueRef = React.useRef(value)
154
- valueRef.current = value
155
157
  const onChangeRef = React.useRef(onChange)
156
- onChangeRef.current = onChange
158
+
159
+ React.useEffect(() => {
160
+ valueRef.current = value
161
+ onChangeRef.current = onChange
162
+ })
157
163
 
158
164
  const handleMediaSelected = React.useCallback(
159
165
  (url: string) => {
@@ -25,7 +25,9 @@ export function useContentEditorTableAddControls(
25
25
  const tableStateRef = React.useRef<TableAddControlsState | null>(null)
26
26
  const isEditorViewMounted = Boolean(getMountedEditorView(editor))
27
27
 
28
- tableStateRef.current = tableState
28
+ React.useEffect(() => {
29
+ tableStateRef.current = tableState
30
+ })
29
31
 
30
32
  const setTable = React.useCallback((next: TableAddControlsState) => {
31
33
  setTableState((current) => {
@@ -1,5 +1,8 @@
1
1
  'use client'
2
2
 
3
+ // oxlint-disable react/refs -- `latestMarkdownRef` is both the synchronous markdown buffer that
4
+ // callbacks write and the value rendered as `sourceValue`. Splitting it into state changes when the
5
+ // buffer is observable and risks the Markdown round-trip, so it needs a verified refactor.
3
6
  import * as React from 'react'
4
7
  import type { ContentEditorMode } from '@admin/utils/editor/content-editor-mode'
5
8
  import type { ContentEditorSelectionRequest } from '@admin/utils/editor/content-editor-selection-request'
@@ -75,8 +78,6 @@ export function useContentEditor({
75
78
  updateSlashMenu
76
79
  } = useContentEditorSlashMenu(editorRef)
77
80
 
78
- isSlashMenuOpenRef.current = slashMenu !== null
79
-
80
81
  if (value !== lastValuePropRef.current) {
81
82
  lastValuePropRef.current = value
82
83
  latestMarkdownRef.current = value
@@ -173,7 +174,10 @@ export function useContentEditor({
173
174
  }
174
175
  })
175
176
 
176
- editorRef.current = editor
177
+ React.useEffect(() => {
178
+ editorRef.current = editor
179
+ isSlashMenuOpenRef.current = slashMenu !== null
180
+ })
177
181
 
178
182
  const syncRichContent = React.useCallback(
179
183
  (nextValue: string = latestMarkdownRef.current, selectionOffset?: number) => {
@@ -89,11 +89,13 @@ export function useUpload(options: UseUploadOptions = {}): UseUploadReturn {
89
89
  const uploadAbortControllerRef = React.useRef<AbortController | null>(null)
90
90
  const progressIntervalRef = React.useRef<ReturnType<typeof setInterval> | null>(null)
91
91
  const progressResetTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)
92
- if (previousIsActiveRef.current !== isActive) {
93
- previousIsActiveRef.current = isActive
94
- activeActivityIdRef.current += 1
95
- }
96
- activeRef.current = isActive
92
+ React.useEffect(() => {
93
+ if (previousIsActiveRef.current !== isActive) {
94
+ previousIsActiveRef.current = isActive
95
+ activeActivityIdRef.current += 1
96
+ }
97
+ activeRef.current = isActive
98
+ })
97
99
 
98
100
  const clearProgressResetTimeout = React.useCallback(() => {
99
101
  if (progressResetTimeoutRef.current) {
@@ -32,7 +32,7 @@ import { Input } from '@admin/components/ui/input'
32
32
  import { Spinner } from '@admin/components/ui/spinner'
33
33
  import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
34
34
  import { useRouter } from 'next/navigation'
35
- import { useForm } from 'react-hook-form'
35
+ import { useForm, useWatch } from 'react-hook-form'
36
36
  import { toast } from 'sonner'
37
37
  import { z } from 'zod/v3'
38
38
 
@@ -94,8 +94,8 @@ export function ProfileForm({ user }: ProfileFormProps) {
94
94
  reValidateMode: 'onChange'
95
95
  })
96
96
 
97
- const emailDirty = profileForm.watch('email') !== user.email
98
- const imageValue = profileForm.watch('image')
97
+ const emailDirty = useWatch({ control: profileForm.control, name: 'email' }) !== user.email
98
+ const imageValue = useWatch({ control: profileForm.control, name: 'image' })
99
99
 
100
100
  function onProfileSubmit(values: ProfileValues) {
101
101
  startProfileTransition(async () => {
@@ -35,7 +35,7 @@ import { webhookEventSources } from '@admin/data/webhook-events'
35
35
  import { useWebhookSubscriptions } from '@admin/hooks/use-webhooks'
36
36
  import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
37
37
  import { useMutation, useQueryClient } from '@tanstack/react-query'
38
- import { useForm } from 'react-hook-form'
38
+ import { useForm, useWatch } from 'react-hook-form'
39
39
  import { toast } from 'sonner'
40
40
  import { z } from 'zod/v3'
41
41
 
@@ -81,7 +81,7 @@ export function WebhookEndpointDialog({ webhook, open, onOpenChange }: WebhookEn
81
81
  events: subscribedEvents
82
82
  }
83
83
  })
84
- const events = form.watch('events')
84
+ const events = useWatch({ control: form.control, name: 'events' })
85
85
 
86
86
  React.useEffect(() => {
87
87
  if (!open) return
package/dist/cli.js CHANGED
@@ -15626,7 +15626,14 @@ function buildUiImports(ctx) {
15626
15626
  if (ctx.hasSectionCardHeading) cardImports.push("CardHeader", "CardTitle");
15627
15627
  const formImports = [
15628
15628
  ctx.hasFormRoot === false ? "" : "Form",
15629
- ...ctx.hasFormControls === false ? [] : ["FormControl", "FormDescription", "FormField", "FormItem", "FormLabel", "FormMessage"]
15629
+ ...ctx.hasFormControls === false ? [] : [
15630
+ "FormControl",
15631
+ "FormDescription",
15632
+ "FormField",
15633
+ "FormItem",
15634
+ "FormLabel",
15635
+ ctx.hasFormMessage === false ? "" : "FormMessage"
15636
+ ]
15630
15637
  ].filter(Boolean);
15631
15638
  const uiImports = [
15632
15639
  `import { ${cardImports.join(", ")} } from '@admin/components/ui/card'`,
@@ -16360,7 +16367,9 @@ ${defaultValues}
16360
16367
  defaultValues
16361
16368
  })
16362
16369
  const defaultValuesRef = React.useRef(defaultValues)
16363
- defaultValuesRef.current = defaultValues
16370
+ React.useEffect(() => {
16371
+ defaultValuesRef.current = defaultValues
16372
+ })
16364
16373
  React.useEffect(
16365
16374
  () => () => {
16366
16375
  form.reset(defaultValuesRef.current)
@@ -19575,6 +19584,7 @@ function generateForm(schema, pagesDir, options = {}) {
19575
19584
  tabFieldNames
19576
19585
  });
19577
19586
  const formControlFreeFieldTypes = /* @__PURE__ */ new Set(["separator", "tabs"]);
19587
+ const formMessageFreeFieldTypes = /* @__PURE__ */ new Set(["boolean", "group", "section", "separator", "tabs"]);
19578
19588
  const inputFreeFieldTypes = /* @__PURE__ */ new Set([
19579
19589
  "boolean",
19580
19590
  "image",
@@ -19638,6 +19648,10 @@ function generateForm(schema, pagesDir, options = {}) {
19638
19648
  return false;
19639
19649
  };
19640
19650
  const contentHasRenderedIconPostfix = hasRenderedIconPostfix2(allFormFields);
19651
+ const contentUsesFormMessage = contentHasRenderedIconPostfix || flatFields.some((field) => {
19652
+ if (field.primaryKey || field.hidden) return false;
19653
+ return !formMessageFreeFieldTypes.has(field.type);
19654
+ });
19641
19655
  const uiImports = buildUiImports({
19642
19656
  hasBoolean,
19643
19657
  hasTextarea,
@@ -19665,6 +19679,7 @@ function generateForm(schema, pagesDir, options = {}) {
19665
19679
  hasFormRoot: true,
19666
19680
  hasButton: hasRelationship || hasSelectCombobox || hasNestedList,
19667
19681
  hasFormControls: contentUsesFormControls || contentHasRenderedIconPostfix,
19682
+ hasFormMessage: contentUsesFormMessage,
19668
19683
  hasInput: contentUsesInput
19669
19684
  });
19670
19685
  const lucideIcons = [];
@@ -23010,6 +23025,7 @@ function generateSingleForm(schema, pagesDir, options = {}) {
23010
23025
  "tabs"
23011
23026
  ]);
23012
23027
  const formControlFreeFieldTypes = /* @__PURE__ */ new Set(["separator", "tabs"]);
23028
+ const formMessageFreeFieldTypes = /* @__PURE__ */ new Set(["boolean", "group", "section", "separator", "tabs"]);
23013
23029
  const cardArtifacts = cardGroups.map((group2) => {
23014
23030
  const analysis = analyzeGroup(group2.fields);
23015
23031
  const groupFlatFields = group2.flatFields;
@@ -23040,6 +23056,10 @@ function generateSingleForm(schema, pagesDir, options = {}) {
23040
23056
  if (field.type === "list" && (!field.fields || field.fields.length === 0)) return false;
23041
23057
  return !formControlFreeFieldTypes.has(field.type);
23042
23058
  }) || hasIconPostfix;
23059
+ const hasFormMessage = hasIconPostfix || groupFlatFields.some((field) => {
23060
+ if (field.primaryKey || field.hidden) return false;
23061
+ return !formMessageFreeFieldTypes.has(field.type);
23062
+ });
23043
23063
  const groupUiImports = buildUiImports({
23044
23064
  hasBoolean: hasFieldType(group2.fields, "boolean"),
23045
23065
  hasTextarea: hasFieldType(group2.fields, "text"),
@@ -23065,6 +23085,7 @@ function generateSingleForm(schema, pagesDir, options = {}) {
23065
23085
  hasCardContent: true,
23066
23086
  hasSectionCardHeading: true,
23067
23087
  hasFormRoot: true,
23088
+ hasFormMessage,
23068
23089
  hasButton: true,
23069
23090
  hasFormControls,
23070
23091
  hasInput
@@ -33067,7 +33088,12 @@ function scaffoldOxfmt(cwd, linter, adminDir) {
33067
33088
  "import/consistent-type-specifier-style": ["error", "prefer-top-level"]
33068
33089
  },
33069
33090
  // The generated admin vendors shadcn and Tiptap primitives whose a11y and hook shapes
33070
- // BetterStart owns, so the rest of the project stays fully linted.
33091
+ // BetterStart owns, so the rest of the project stays fully linted. TanStack Table/Virtual
33092
+ // and react-hook-form expose no React Compiler-compatible alternative to the APIs the admin
33093
+ // is built on, so `react/incompatible-library` is scoped off there rather than per call site.
33094
+ // Admin state that tracks Tiptap, CodeMirror, scroll position, and DOM measurements is
33095
+ // synchronised from effects, which is what effects are for, so `react/set-state-in-effect`
33096
+ // is scoped off there too.
33071
33097
  overrides: [
33072
33098
  {
33073
33099
  files: [`**/${admin}/**`, `**/(${namespace})/**`],
@@ -33076,6 +33102,8 @@ function scaffoldOxfmt(cwd, linter, adminDir) {
33076
33102
  "jsx-a11y/no-autofocus": "off",
33077
33103
  "jsx-a11y/anchor-has-content": "off",
33078
33104
  "react-hooks/exhaustive-deps": "off",
33105
+ "react/incompatible-library": "off",
33106
+ "react/set-state-in-effect": "off",
33079
33107
  "unicorn/no-new-array": "off",
33080
33108
  "unicorn/no-empty-file": "off"
33081
33109
  }
@@ -33083,7 +33111,8 @@ function scaffoldOxfmt(cwd, linter, adminDir) {
33083
33111
  {
33084
33112
  files: [`**/${admin}/components/ui/**`],
33085
33113
  rules: {
33086
- "react/no-multi-comp": "off"
33114
+ "react/no-multi-comp": "off",
33115
+ "react/set-state-in-effect": "off"
33087
33116
  }
33088
33117
  }
33089
33118
  ],