sanity 3.26.2-canary.52 → 3.26.2-canary.69

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.
Files changed (112) hide show
  1. package/lib/_chunks/{_internal-2CJ5wSKF.js → _internal-6Pl2wJGj.js} +422 -201
  2. package/lib/_chunks/_internal-6Pl2wJGj.js.map +1 -0
  3. package/lib/_chunks/{_internal-79flWvL8.js → _internal-yQwMw2ht.js} +419 -203
  4. package/lib/_chunks/_internal-yQwMw2ht.js.map +1 -0
  5. package/lib/_chunks/{_internalBrowser-QAFz3SKp.js → _internalBrowser-HWvRXvlU.js} +22 -22
  6. package/lib/_chunks/_internalBrowser-HWvRXvlU.js.map +1 -0
  7. package/lib/_chunks/{_internalBrowser-Y0qKZ-GA.js → _internalBrowser-TWIhitgI.js} +22 -22
  8. package/lib/_chunks/_internalBrowser-TWIhitgI.js.map +1 -0
  9. package/lib/_chunks/{deployApiAction-SHteit1G.js → deployApiAction-TZcCtXan.js} +2 -2
  10. package/lib/_chunks/{deployApiAction-SHteit1G.js.map → deployApiAction-TZcCtXan.js.map} +1 -1
  11. package/lib/_chunks/{deployApiAction-bRyJpGOS.js → deployApiAction-rVL67VYW.js} +2 -2
  12. package/lib/_chunks/{deployApiAction-bRyJpGOS.js.map → deployApiAction-rVL67VYW.js.map} +1 -1
  13. package/lib/_chunks/{getStudioConfig-JSkc4GE0.js → getStudioWorkspaces-HX9o9-hl.js} +20 -4
  14. package/lib/_chunks/getStudioWorkspaces-HX9o9-hl.js.map +1 -0
  15. package/lib/_chunks/{index-EO9iRnDS.js → index-2kSxso3r.js} +2 -2
  16. package/lib/_chunks/{index-EO9iRnDS.js.map → index-2kSxso3r.js.map} +1 -1
  17. package/lib/_chunks/{index-FQCCBbuC.js → index-751ZLh3z.js} +6 -6
  18. package/lib/_chunks/{index-FQCCBbuC.js.map → index-751ZLh3z.js.map} +1 -1
  19. package/lib/_chunks/{index-VNSHvpZr.js → index-HcF369ru.js} +2 -2
  20. package/lib/_chunks/{index-VNSHvpZr.js.map → index-HcF369ru.js.map} +1 -1
  21. package/lib/_chunks/{index-Xs2xnLUV.js → index-MAAxgUnl.js} +6 -6
  22. package/lib/_chunks/{index-Xs2xnLUV.js.map → index-MAAxgUnl.js.map} +1 -1
  23. package/lib/_chunks/{index-AaK2CidU.js → index-NweJPfGb.js} +2 -2
  24. package/lib/_chunks/{index-AaK2CidU.js.map → index-NweJPfGb.js.map} +1 -1
  25. package/lib/_chunks/{index-R7R6AyHF.js → index-zobOqko7.js} +2 -2
  26. package/lib/_chunks/{index-R7R6AyHF.js.map → index-zobOqko7.js.map} +1 -1
  27. package/lib/_chunks/{listApisAction-6lGkFZrU.js → listApisAction-IvKV4iAd.js} +2 -2
  28. package/lib/_chunks/{listApisAction-6lGkFZrU.js.map → listApisAction-IvKV4iAd.js.map} +1 -1
  29. package/lib/_chunks/{listApisAction-CqCkBz-F.js → listApisAction-oca2uykY.js} +2 -2
  30. package/lib/_chunks/{listApisAction-CqCkBz-F.js.map → listApisAction-oca2uykY.js.map} +1 -1
  31. package/lib/_chunks/pane-9HEeITpx.js +5 -0
  32. package/lib/_chunks/pane-9HEeITpx.js.map +1 -0
  33. package/lib/_chunks/pane-QyVrOLqS.js +2 -0
  34. package/lib/_chunks/pane-QyVrOLqS.js.map +1 -0
  35. package/lib/_chunks/pane-TXXUUvdc.js +5 -0
  36. package/lib/_chunks/pane-TXXUUvdc.js.map +1 -0
  37. package/lib/_chunks/pane-cQxQtBcL.js +2 -0
  38. package/lib/_chunks/pane-cQxQtBcL.js.map +1 -0
  39. package/lib/_chunks/{structure-qJLnDJXq.js → structure-iqIDIH7-.js} +3 -3
  40. package/lib/_chunks/structure-iqIDIH7-.js.map +1 -0
  41. package/lib/_chunks/{structure-588eAwLd.js → structure-o_wMXC_G.js} +3 -3
  42. package/lib/_chunks/structure-o_wMXC_G.js.map +1 -0
  43. package/lib/_chunks/validateAction-4Jl_y_iB.js +154 -0
  44. package/lib/_chunks/validateAction-4Jl_y_iB.js.map +1 -0
  45. package/lib/_chunks/{validateAction-GUvMkXiN.js → validateAction-6q8Sqwaz.js} +25 -73
  46. package/lib/_chunks/validateAction-6q8Sqwaz.js.map +1 -0
  47. package/lib/_chunks/validateAction-Bz67ApRP.js +143 -0
  48. package/lib/_chunks/validateAction-Bz67ApRP.js.map +1 -0
  49. package/lib/_chunks/{validateAction-NIrqtyYJ.js → validateAction-shi462sq.js} +27 -75
  50. package/lib/_chunks/validateAction-shi462sq.js.map +1 -0
  51. package/lib/_internal/cli/threads/getGraphQLAPIs.js +2 -2
  52. package/lib/_internal/cli/threads/getGraphQLAPIs.js.map +1 -1
  53. package/lib/_internal/cli/threads/validateDocuments.js +5 -3
  54. package/lib/_internal/cli/threads/validateDocuments.js.map +1 -1
  55. package/lib/_internal/cli/threads/validateSchema.js +55 -0
  56. package/lib/_internal/cli/threads/validateSchema.js.map +1 -0
  57. package/lib/_internal.esm.js +1 -1
  58. package/lib/_internal.js +1 -1
  59. package/lib/_internalBrowser.esm.js +1 -1
  60. package/lib/_internalBrowser.js +1 -1
  61. package/lib/desk.esm.js +1 -1
  62. package/lib/desk.js +1 -1
  63. package/lib/exports/index.d.ts +25 -0
  64. package/lib/index.cjs.mjs +1 -0
  65. package/lib/index.esm.js +2 -2
  66. package/lib/index.js +2 -1
  67. package/lib/index.js.map +1 -1
  68. package/lib/structure.esm.js +1 -1
  69. package/lib/structure.js +1 -1
  70. package/package.json +15 -15
  71. package/src/_internal/cli/actions/schema/formatSchemaValidation.ts +96 -0
  72. package/src/_internal/cli/actions/schema/validateAction.ts +120 -0
  73. package/src/_internal/cli/actions/validation/reporters/prettyReporter/formatDocumentValidation.ts +17 -95
  74. package/src/_internal/cli/commands/index.ts +2 -0
  75. package/src/_internal/cli/commands/migration/createMigrationCommand.ts +18 -10
  76. package/src/_internal/cli/commands/migration/listMigrationsCommand.ts +34 -31
  77. package/src/_internal/cli/commands/migration/prettyMutationFormatter.ts +214 -0
  78. package/src/_internal/cli/commands/migration/runMigrationCommand.ts +102 -58
  79. package/src/_internal/cli/commands/migration/templates/minimalAdvanced.ts +1 -1
  80. package/src/_internal/cli/commands/migration/templates/minimalSimple.ts +1 -1
  81. package/src/_internal/cli/commands/migration/templates/renameField.ts +3 -3
  82. package/src/_internal/cli/commands/migration/templates/renameType.ts +1 -1
  83. package/src/_internal/cli/commands/migration/templates/stringToPTE.ts +1 -1
  84. package/src/_internal/cli/commands/migration/utils/mutationFormatter.ts +1 -1
  85. package/src/_internal/cli/commands/schema/validateSchemaCommand.ts +35 -0
  86. package/src/_internal/cli/threads/getGraphQLAPIs.ts +2 -2
  87. package/src/_internal/cli/threads/validateDocuments.ts +6 -3
  88. package/src/_internal/cli/threads/validateSchema.ts +73 -0
  89. package/src/_internal/cli/util/{getStudioConfig.ts → getStudioWorkspaces.ts} +30 -8
  90. package/src/_internal/cli/util/tree.ts +110 -0
  91. package/src/core/config/index.ts +1 -0
  92. package/src/core/config/prepareConfig.ts +2 -5
  93. package/src/core/config/resolveSchemaTypes.ts +29 -0
  94. package/src/core/studio/screens/schemaErrors/SchemaProblemGroups.tsx +4 -2
  95. package/src/structure/comments/src/components/pte/blocks/MentionInlineBlock.tsx +13 -6
  96. package/lib/_chunks/_internal-2CJ5wSKF.js.map +0 -1
  97. package/lib/_chunks/_internal-79flWvL8.js.map +0 -1
  98. package/lib/_chunks/_internalBrowser-QAFz3SKp.js.map +0 -1
  99. package/lib/_chunks/_internalBrowser-Y0qKZ-GA.js.map +0 -1
  100. package/lib/_chunks/getStudioConfig-JSkc4GE0.js.map +0 -1
  101. package/lib/_chunks/pane-QmJb9ZTN.js +0 -2
  102. package/lib/_chunks/pane-QmJb9ZTN.js.map +0 -1
  103. package/lib/_chunks/pane-SK7FWNTJ.js +0 -2
  104. package/lib/_chunks/pane-SK7FWNTJ.js.map +0 -1
  105. package/lib/_chunks/pane-y4hpcKe1.js +0 -5
  106. package/lib/_chunks/pane-y4hpcKe1.js.map +0 -1
  107. package/lib/_chunks/pane-yQXBQyyI.js +0 -5
  108. package/lib/_chunks/pane-yQXBQyyI.js.map +0 -1
  109. package/lib/_chunks/structure-588eAwLd.js.map +0 -1
  110. package/lib/_chunks/structure-qJLnDJXq.js.map +0 -1
  111. package/lib/_chunks/validateAction-GUvMkXiN.js.map +0 -1
  112. package/lib/_chunks/validateAction-NIrqtyYJ.js.map +0 -1
@@ -0,0 +1,73 @@
1
+ import {isMainThread, parentPort, workerData as _workerData} from 'worker_threads'
2
+ import {type SchemaValidationProblem, type SchemaValidationProblemGroup} from '@sanity/types'
3
+ import {validateSchema, groupProblems} from '@sanity/schema/_internal'
4
+ import {getStudioConfig} from '../util/getStudioWorkspaces'
5
+ import {mockBrowserEnvironment} from '../util/mockBrowserEnvironment'
6
+ import {resolveSchemaTypes} from 'sanity'
7
+
8
+ export interface ValidateSchemaWorkerData {
9
+ workDir: string
10
+ workspace?: string
11
+ level?: SchemaValidationProblem['severity']
12
+ }
13
+
14
+ export interface ValidateSchemaWorkerResult {
15
+ validation: SchemaValidationProblemGroup[]
16
+ }
17
+
18
+ const {
19
+ workDir,
20
+ workspace: workspaceName,
21
+ level = 'warning',
22
+ } = _workerData as ValidateSchemaWorkerData
23
+
24
+ if (isMainThread || !parentPort) {
25
+ throw new Error('This module must be run as a worker thread')
26
+ }
27
+
28
+ const cleanup = mockBrowserEnvironment(workDir)
29
+
30
+ try {
31
+ const workspaces = getStudioConfig({basePath: workDir})
32
+
33
+ if (!workspaces.length) {
34
+ throw new Error(`Configuration did not return any workspaces.`)
35
+ }
36
+
37
+ let workspace
38
+ if (workspaceName) {
39
+ workspace = workspaces.find((w) => w.name === workspaceName)
40
+ if (!workspace) {
41
+ throw new Error(`Could not find any workspaces with name \`${workspaceName}\``)
42
+ }
43
+ } else {
44
+ if (workspaces.length !== 1) {
45
+ throw new Error(
46
+ "Multiple workspaces found. Please specify which workspace to use with '--workspace'.",
47
+ )
48
+ }
49
+ workspace = workspaces[0]
50
+ }
51
+
52
+ const schemaTypes = resolveSchemaTypes({
53
+ config: workspace,
54
+ context: {dataset: workspace.dataset, projectId: workspace.projectId},
55
+ })
56
+
57
+ const validation = groupProblems(validateSchema(schemaTypes).getTypes())
58
+
59
+ const result: ValidateSchemaWorkerResult = {
60
+ validation: validation
61
+ .map((group) => ({
62
+ ...group,
63
+ problems: group.problems.filter((problem) =>
64
+ level === 'error' ? problem.severity === 'error' : true,
65
+ ),
66
+ }))
67
+ .filter((group) => group.problems.length),
68
+ }
69
+
70
+ parentPort?.postMessage(result)
71
+ } finally {
72
+ cleanup()
73
+ }
@@ -1,10 +1,9 @@
1
1
  /* eslint-disable no-sync */
2
2
  import fs from 'fs'
3
3
  import path from 'path'
4
- import {first} from 'rxjs/operators'
5
4
  import {firstValueFrom} from 'rxjs'
6
5
  import {mockBrowserEnvironment} from './mockBrowserEnvironment'
7
- import {resolveConfig, Config, Workspace} from 'sanity'
6
+ import {resolveConfig, Config, Workspace, WorkspaceOptions} from 'sanity'
8
7
 
9
8
  const candidates = [
10
9
  'sanity.config.js',
@@ -13,15 +12,18 @@ const candidates = [
13
12
  'sanity.config.tsx',
14
13
  ]
15
14
 
16
- /**
17
- * Note: Don't run this on the main thread, use it a forked process
18
- */
19
- export async function getStudioConfig(options: {
15
+ interface GetStudioWorkspacesOptions {
20
16
  configPath?: string
21
17
  basePath: string
22
- }): Promise<Workspace[]> {
23
- const {basePath, configPath: cfgPath} = options
18
+ }
24
19
 
20
+ /**
21
+ * Note: Don't run this on the main thread, use it a forked process
22
+ */
23
+ export function getStudioConfig({
24
+ basePath,
25
+ configPath: cfgPath,
26
+ }: GetStudioWorkspacesOptions): WorkspaceOptions[] {
25
27
  let cleanup
26
28
  try {
27
29
  cleanup = mockBrowserEnvironment(basePath)
@@ -51,7 +53,27 @@ export async function getStudioConfig(options: {
51
53
  }
52
54
 
53
55
  if (!config) throw new Error('Configuration did not export expected config shape')
56
+ const normalized = Array.isArray(config)
57
+ ? config
58
+ : [{...config, name: config.name || 'default', basePath: config.basePath || '/'}]
59
+
60
+ return normalized
61
+ } finally {
62
+ cleanup?.()
63
+ }
64
+ }
54
65
 
66
+ /**
67
+ * Note: Don't run this on the main thread, use it a forked process
68
+ */
69
+ export async function getStudioWorkspaces(
70
+ options: GetStudioWorkspacesOptions,
71
+ ): Promise<Workspace[]> {
72
+ let cleanup
73
+
74
+ try {
75
+ cleanup = mockBrowserEnvironment(options.basePath)
76
+ const config = getStudioConfig(options)
55
77
  const workspaces = await firstValueFrom(resolveConfig(config))
56
78
  if (!workspaces) throw new Error('Failed to resolve configuration')
57
79
  return workspaces
@@ -0,0 +1,110 @@
1
+ import {type Path, pathToString} from 'sanity'
2
+
3
+ interface BaseNode {
4
+ path: Path
5
+ }
6
+
7
+ export interface Tree<Node extends BaseNode> {
8
+ nodes?: Node[]
9
+ children?: Record<string, Tree<Node>>
10
+ }
11
+
12
+ /**
13
+ * Recursively calculates the max length of all the keys in the given validation
14
+ * tree respecting extra length due to indentation depth. Used to calculate the
15
+ * padding for the rest of the tree.
16
+ */
17
+ export const maxKeyLength = (children: Record<string, Tree<BaseNode>> = {}, depth = 0): number => {
18
+ return Object.entries(children)
19
+ .map(([key, child]) =>
20
+ Math.max(key.length + depth * 2, maxKeyLength(child.children, depth + 1)),
21
+ )
22
+ .reduce((max, next) => (next > max ? next : max), 0)
23
+ }
24
+
25
+ interface Options<Node extends BaseNode> {
26
+ node?: Record<string, Tree<Node>>
27
+ paddingLength: number
28
+ indent?: string
29
+ getNodes?: (node: Tree<Node>) => Node[] | undefined
30
+ getMessage: (node: Node) => string
31
+ }
32
+
33
+ /**
34
+ * Recursively formats a given tree into a printed user-friendly tree structure
35
+ */
36
+ export const formatTree = <Node extends BaseNode>({
37
+ node = {},
38
+ paddingLength,
39
+ indent = '',
40
+ getNodes: getLeaves = ({nodes}) => nodes,
41
+ getMessage,
42
+ }: Options<Node>): string => {
43
+ const entries = Object.entries(node)
44
+
45
+ return entries
46
+ .map(([key, child], index) => {
47
+ const isLast = index === entries.length - 1
48
+ const nextIndent = `${indent}${isLast ? ' ' : '│ '}`
49
+ const leaves = getLeaves(child)
50
+
51
+ const nested = formatTree({
52
+ node: child.children,
53
+ paddingLength,
54
+ indent: nextIndent,
55
+ getNodes: getLeaves,
56
+ getMessage,
57
+ })
58
+
59
+ if (!leaves?.length) {
60
+ const current = `${indent}${isLast ? '└' : '├'}─ ${key}`
61
+ return [current, nested].filter(Boolean).join('\n')
62
+ }
63
+
64
+ const [first, ...rest] = leaves
65
+ const firstPadding = '.'.repeat(paddingLength - indent.length - key.length)
66
+ const elbow = isLast ? '└' : '├'
67
+ const subsequentPadding = ' '.repeat(paddingLength - indent.length + 2)
68
+
69
+ const firstMessage = `${indent}${elbow}─ ${key} ${firstPadding} ${getMessage(first)}`
70
+ const subsequentMessages = rest
71
+ .map((marker) => `${nextIndent}${subsequentPadding} ${getMessage(marker)}`)
72
+ .join('\n')
73
+
74
+ const current = [firstMessage, subsequentMessages].filter(Boolean).join('\n')
75
+ return [current, nested].filter(Boolean).join('\n')
76
+ })
77
+ .join('\n')
78
+ }
79
+
80
+ /**
81
+ * Converts a set of markers with paths into a tree of markers where the paths
82
+ * are embedded in the tree
83
+ */
84
+ export function convertToTree<const Node extends BaseNode>(nodes: Node[]): Tree<Node> {
85
+ const root: Tree<Node> = {}
86
+
87
+ // add the markers to the tree
88
+ function addNode(node: Node, tree: Tree<Node> = root) {
89
+ // if we've traversed the whole path
90
+ if (!node.path.length) {
91
+ if (!tree.nodes) tree.nodes = [] // ensure markers is defined
92
+
93
+ // then add the marker to the front
94
+ tree.nodes.push(node)
95
+ return
96
+ }
97
+
98
+ const [current, ...rest] = node.path
99
+ const key = pathToString([current])
100
+
101
+ // ensure the current node has children and the next node
102
+ if (!tree.children) tree.children = {}
103
+ if (!(key in tree.children)) tree.children[key] = {}
104
+
105
+ addNode({...node, path: rest}, tree.children[key])
106
+ }
107
+
108
+ for (const node of nodes) addNode(node)
109
+ return root
110
+ }
@@ -12,3 +12,4 @@ export * from './components'
12
12
  export * from './studio'
13
13
  export * from './flattenConfig'
14
14
  export * from './form'
15
+ export * from './resolveSchemaTypes'
@@ -44,7 +44,6 @@ import {
44
44
  partialIndexingEnabledReducer,
45
45
  resolveProductionUrlReducer,
46
46
  schemaTemplatesReducer,
47
- schemaTypesReducer,
48
47
  toolsReducer,
49
48
  } from './configPropertyReducers'
50
49
  import {resolveConfigProperty} from './resolveConfigProperty'
@@ -52,6 +51,7 @@ import {ConfigResolutionError} from './ConfigResolutionError'
52
51
  import {SchemaError} from './SchemaError'
53
52
  import {createDefaultIcon} from './createDefaultIcon'
54
53
  import {documentFieldActionsReducer, initialDocumentFieldActions} from './document'
54
+ import {resolveSchemaTypes} from './resolveSchemaTypes'
55
55
 
56
56
  type InternalSource = WorkspaceSummary['__internal']['sources'][number]
57
57
 
@@ -118,12 +118,9 @@ export function prepareConfig(
118
118
 
119
119
  let schemaTypes
120
120
  try {
121
- schemaTypes = resolveConfigProperty({
122
- propertyName: 'schema.types',
121
+ schemaTypes = resolveSchemaTypes({
123
122
  config: source,
124
123
  context: {projectId, dataset},
125
- initialValue: [],
126
- reducer: schemaTypesReducer,
127
124
  })
128
125
  } catch (e) {
129
126
  throw new ConfigResolutionError({
@@ -0,0 +1,29 @@
1
+ import {SchemaTypeDefinition} from '@sanity/types'
2
+ import {ConfigPropertyReducer, PluginOptions} from './types'
3
+ import {schemaTypesReducer} from './configPropertyReducers'
4
+ import {resolveConfigProperty} from './resolveConfigProperty'
5
+
6
+ type ConfigContext<T> = T extends ConfigPropertyReducer<any, infer TContext> ? TContext : never
7
+ type SchemaTypeContext = ConfigContext<typeof schemaTypesReducer>
8
+
9
+ interface ResolveSchemaTypesOptions {
10
+ config: PluginOptions
11
+ context: SchemaTypeContext
12
+ }
13
+
14
+ /**
15
+ * @internal
16
+ * @hidden
17
+ */
18
+ export function resolveSchemaTypes({
19
+ config,
20
+ context,
21
+ }: ResolveSchemaTypesOptions): SchemaTypeDefinition[] {
22
+ return resolveConfigProperty({
23
+ propertyName: 'schema.types',
24
+ config,
25
+ context,
26
+ initialValue: [],
27
+ reducer: schemaTypesReducer,
28
+ })
29
+ }
@@ -77,7 +77,9 @@ export function SchemaProblemGroups(props: {problemGroups: SchemaValidationProbl
77
77
  >
78
78
  {group.path.map((segment, j) => {
79
79
  if (segment.kind === 'type') {
80
- const text = `${_renderSegmentName(segment.name)}:${segment.type}`
80
+ const text = `${_renderSegmentName(
81
+ segment.name || `<anonymous ${segment.type}>`,
82
+ )}:${segment.type}`
81
83
  return (
82
84
  <Text title={text} key={j} size={1} textOverflow="ellipsis">
83
85
  <SegmentSpan>{text}</SegmentSpan>
@@ -132,7 +134,7 @@ function getTypeInfo(problem: SchemaValidationProblemGroup): {name: string; type
132
134
  // a possible API improvement is to add schemaType info to the problem group interface itself
133
135
  const first = problem.path[0]
134
136
  if (first.kind === 'type') {
135
- return {name: first.name, type: first.type}
137
+ return {name: first.name || `<anonymous ${first.type}>`, type: first.type}
136
138
  }
137
139
  return null
138
140
  }
@@ -1,10 +1,9 @@
1
1
  import React from 'react'
2
- import {Flex, Text} from '@sanity/ui'
2
+ import {Flex, Text, TextSkeleton} from '@sanity/ui'
3
3
  import styled, {css} from 'styled-components'
4
4
  import {Tooltip} from '../../../../../../ui-components'
5
- import {commentsLocaleNamespace} from '../../../../i18n'
6
5
  import {CommentsAvatar} from '../../avatars'
7
- import {useCurrentUser, useTranslation, useUser} from 'sanity'
6
+ import {useCurrentUser, useUser} from 'sanity'
8
7
 
9
8
  const Span = styled.span(({theme}) => {
10
9
  const {regular} = theme.sanity.fonts?.text.weights
@@ -34,10 +33,18 @@ export function MentionInlineBlock(props: MentionInlineBlockProps) {
34
33
  const {selected, userId} = props
35
34
  const [user, loading] = useUser(userId)
36
35
  const currentUser = useCurrentUser()
37
- const {t} = useTranslation(commentsLocaleNamespace)
38
36
 
39
- // eslint-disable-next-line i18next/no-literal-string
40
- if (!user || loading) return <Span>@Loading</Span> // todo: improve
37
+ if (!user || loading)
38
+ return (
39
+ <TextSkeleton
40
+ data-testid="comment-mentions-loading-skeleton"
41
+ style={{width: '10ch'}}
42
+ size={0}
43
+ muted
44
+ radius={1}
45
+ animated
46
+ />
47
+ )
41
48
 
42
49
  return (
43
50
  <Tooltip