@exegia/corpora-ui 0.19.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/dist-lib/components/blocks/auth/__tests__/auth-state-atom.test.d.ts +1 -0
  2. package/dist-lib/components/blocks/auth/auth-flow-atom.d.ts +77 -0
  3. package/dist-lib/components/blocks/auth/auth-session-atom.d.ts +28 -0
  4. package/dist-lib/components/blocks/auth/auth-state-type.d.ts +71 -0
  5. package/dist-lib/components/blocks/auth/auth-state.d.ts +9 -0
  6. package/dist-lib/components/blocks/auth/use-auth-state.d.ts +52 -0
  7. package/dist-lib/components/blocks/nav/sidebar/__tests__/ai-sidebar-atom.test.d.ts +1 -0
  8. package/dist-lib/components/blocks/nav/sidebar/__tests__/use-ai-sidebar.test.d.ts +1 -0
  9. package/dist-lib/components/blocks/nav/sidebar/ai-sidebar-atom.d.ts +217 -0
  10. package/dist-lib/components/blocks/nav/sidebar/ai-sidebar.d.ts +11 -2
  11. package/dist-lib/components/blocks/nav/sidebar/index.d.ts +3 -1
  12. package/dist-lib/components/blocks/nav/sidebar/sidebar-context.d.ts +3 -0
  13. package/dist-lib/components/blocks/nav/sidebar/sidebar-row.d.ts +3 -1
  14. package/dist-lib/components/blocks/nav/sidebar/type.d.ts +191 -29
  15. package/dist-lib/components/blocks/nav/sidebar/use-ai-sidebar-state.d.ts +29 -0
  16. package/dist-lib/components/blocks/nav/sidebar/use-ai-sidebar.d.ts +27 -31
  17. package/dist-lib/components/blocks/nav/sidebar/utils.d.ts +6 -1
  18. package/dist-lib/components/blocks/profile/index.d.ts +7 -0
  19. package/dist-lib/components/blocks/profile/profile-card-atom.d.ts +68 -0
  20. package/dist-lib/components/blocks/profile/profile-card-block.d.ts +28 -39
  21. package/dist-lib/components/blocks/profile/type.d.ts +80 -0
  22. package/dist-lib/components/blocks/profile/use-profile-card-state.d.ts +24 -0
  23. package/dist-lib/components/blocks/profile/use-profile-card.d.ts +34 -0
  24. package/dist-lib/components/blocks/scaffold/constants.d.ts +5 -0
  25. package/dist-lib/components/blocks/scaffold/scaffold-canvas.d.ts +2 -0
  26. package/dist-lib/components/blocks/scaffold/scaffold-panel.d.ts +1 -1
  27. package/dist-lib/components/blocks/scaffold/scaffold-tab.d.ts +6 -1
  28. package/dist-lib/components/blocks/scaffold/type.d.ts +28 -0
  29. package/dist-lib/components/blocks/scaffold/use-panel-visibility.d.ts +19 -0
  30. package/dist-lib/components/blocks/scaffold/utils.d.ts +6 -0
  31. package/dist-lib/components/blocks/shell/__tests__/shell-fit-atom.test.d.ts +1 -0
  32. package/dist-lib/components/blocks/shell/__tests__/shell-metrics.test.d.ts +1 -0
  33. package/dist-lib/components/blocks/shell/animated-panel-provider.d.ts +1 -1
  34. package/dist-lib/components/blocks/shell/animated-panel.d.ts +1 -1
  35. package/dist-lib/components/blocks/shell/index.d.ts +4 -0
  36. package/dist-lib/components/blocks/shell/shell-fit-atom.d.ts +68 -0
  37. package/dist-lib/components/blocks/shell/shell-metrics.d.ts +54 -0
  38. package/dist-lib/components/blocks/shell/type.d.ts +115 -3
  39. package/dist-lib/components/blocks/shell/use-shell-fit-state.d.ts +25 -0
  40. package/dist-lib/components/blocks/shell/use-shell-fit.d.ts +14 -0
  41. package/dist-lib/components/blocks/shell/use-shell-panels.d.ts +12 -1
  42. package/dist-lib/components/blocks/shell/utils.d.ts +32 -0
  43. package/dist-lib/components/composed/tree/__tests__/tree-atom.test.d.ts +1 -0
  44. package/dist-lib/components/composed/tree/__tests__/tree.test.d.ts +1 -0
  45. package/dist-lib/components/composed/tree/__tests__/use-tree.test.d.ts +1 -0
  46. package/dist-lib/components/composed/tree/constants.d.ts +35 -0
  47. package/dist-lib/components/composed/tree/index.d.ts +6 -0
  48. package/dist-lib/components/composed/tree/tree-atom.d.ts +160 -0
  49. package/dist-lib/components/composed/tree/tree-context.d.ts +4 -0
  50. package/dist-lib/components/composed/tree/tree-node.d.ts +12 -0
  51. package/dist-lib/components/composed/tree/tree.d.ts +19 -0
  52. package/dist-lib/components/composed/tree/type.d.ts +290 -0
  53. package/dist-lib/components/composed/tree/use-tree-dnd.d.ts +18 -0
  54. package/dist-lib/components/composed/tree/use-tree-state.d.ts +25 -0
  55. package/dist-lib/components/composed/tree/use-tree.d.ts +24 -0
  56. package/dist-lib/components/composed/tree/utils.d.ts +28 -0
  57. package/dist-lib/components/composed/user-avatar.d.ts +5 -28
  58. package/dist-lib/components/user-avatar/__tests__/user-avatar.test.d.ts +1 -0
  59. package/dist-lib/components/user-avatar/audio-wave.d.ts +2 -0
  60. package/dist-lib/components/user-avatar/component.d.ts +3 -0
  61. package/dist-lib/components/user-avatar/fallback.d.ts +5 -0
  62. package/dist-lib/components/user-avatar/index.d.ts +18 -0
  63. package/dist-lib/components/user-avatar/presence-badge.d.ts +16 -0
  64. package/dist-lib/components/user-avatar/type.d.ts +71 -0
  65. package/dist-lib/components/user-avatar/use-user-avatar-state.d.ts +26 -0
  66. package/dist-lib/components/user-avatar/use-user-avatar.d.ts +32 -0
  67. package/dist-lib/components/user-avatar/user-avatar-atom.d.ts +55 -0
  68. package/dist-lib/components/user-avatar/utils.d.ts +2 -0
  69. package/dist-lib/index.d.ts +6 -2
  70. package/dist-lib/index.js +3576 -1366
  71. package/dist-lib/index.js.map +1 -1
  72. package/dist-lib/state/exegia-provider.d.ts +51 -0
  73. package/dist-lib/state/index.d.ts +4 -0
  74. package/dist-lib/state/store.d.ts +17 -0
  75. package/package.json +16 -12
  76. package/src/components/beste/piece/browser-frame.tsx +9 -6
  77. package/src/components/blocks/auth/__tests__/auth-state-atom.test.tsx +247 -0
  78. package/src/components/blocks/auth/auth-flow-atom.ts +238 -0
  79. package/src/components/blocks/auth/auth-session-atom.ts +82 -0
  80. package/src/components/blocks/auth/auth-state-type.ts +97 -0
  81. package/src/components/blocks/auth/auth-state.ts +52 -0
  82. package/src/components/blocks/auth/use-auth-state.ts +127 -0
  83. package/src/components/blocks/nav/sidebar/__tests__/ai-sidebar-atom.test.tsx +325 -0
  84. package/src/components/blocks/nav/sidebar/__tests__/use-ai-sidebar.test.tsx +310 -0
  85. package/src/components/blocks/nav/sidebar/ai-sidebar-atom.ts +698 -0
  86. package/src/components/blocks/nav/sidebar/ai-sidebar.tsx +112 -76
  87. package/src/components/blocks/nav/sidebar/index.ts +53 -1
  88. package/src/components/blocks/nav/sidebar/sidebar-context.ts +15 -0
  89. package/src/components/blocks/nav/sidebar/sidebar-row.tsx +119 -50
  90. package/src/components/blocks/nav/sidebar/type.ts +228 -29
  91. package/src/components/blocks/nav/sidebar/use-ai-sidebar-state.ts +123 -0
  92. package/src/components/blocks/nav/sidebar/use-ai-sidebar.ts +455 -225
  93. package/src/components/blocks/nav/sidebar/utils.ts +29 -1
  94. package/src/components/blocks/profile/__tests__/profile-card-block.test.tsx +194 -0
  95. package/src/components/blocks/profile/index.ts +28 -0
  96. package/src/components/blocks/profile/profile-card-atom.ts +247 -0
  97. package/src/components/blocks/profile/profile-card-block.tsx +153 -74
  98. package/src/components/blocks/profile/type.ts +95 -0
  99. package/src/components/blocks/profile/use-profile-card-state.ts +67 -0
  100. package/src/components/blocks/profile/use-profile-card.ts +126 -0
  101. package/src/components/blocks/scaffold/__tests__/scaffold.test.tsx +160 -1
  102. package/src/components/blocks/scaffold/constants.ts +7 -0
  103. package/src/components/blocks/scaffold/panel-menu-button.tsx +3 -0
  104. package/src/components/blocks/scaffold/scaffold-canvas.tsx +48 -5
  105. package/src/components/blocks/scaffold/scaffold-inspector.tsx +3 -2
  106. package/src/components/blocks/scaffold/scaffold-main.tsx +1 -1
  107. package/src/components/blocks/scaffold/scaffold-panel.tsx +41 -5
  108. package/src/components/blocks/scaffold/scaffold-root.tsx +18 -3
  109. package/src/components/blocks/scaffold/scaffold-sidebar.tsx +1 -1
  110. package/src/components/blocks/scaffold/scaffold-tab.tsx +45 -7
  111. package/src/components/blocks/scaffold/type.ts +28 -0
  112. package/src/components/blocks/scaffold/use-panel-visibility.ts +180 -0
  113. package/src/components/blocks/scaffold/utils.ts +19 -0
  114. package/src/components/blocks/shell/__tests__/shell-fit-atom.test.tsx +360 -0
  115. package/src/components/blocks/shell/__tests__/shell-layout.test.tsx +192 -3
  116. package/src/components/blocks/shell/__tests__/shell-metrics.test.ts +108 -0
  117. package/src/components/blocks/shell/animated-panel-inset.tsx +5 -1
  118. package/src/components/blocks/shell/animated-panel-provider.tsx +77 -11
  119. package/src/components/blocks/shell/animated-panel-trigger.tsx +4 -0
  120. package/src/components/blocks/shell/animated-panel.tsx +126 -29
  121. package/src/components/blocks/shell/index.ts +18 -0
  122. package/src/components/blocks/shell/shell-fit-atom.ts +243 -0
  123. package/src/components/blocks/shell/shell-layout.tsx +55 -53
  124. package/src/components/blocks/shell/shell-metrics.ts +79 -0
  125. package/src/components/blocks/shell/type.ts +130 -3
  126. package/src/components/blocks/shell/use-shell-fit-state.ts +49 -0
  127. package/src/components/blocks/shell/use-shell-fit.ts +135 -0
  128. package/src/components/blocks/shell/use-shell-panels.ts +57 -4
  129. package/src/components/blocks/shell/utils.ts +44 -4
  130. package/src/components/composed/tree/CLAUDE.md +132 -0
  131. package/src/components/composed/tree/__tests__/tree-atom.test.tsx +217 -0
  132. package/src/components/composed/tree/__tests__/tree.test.tsx +525 -0
  133. package/src/components/composed/tree/__tests__/use-tree.test.tsx +333 -0
  134. package/src/components/composed/tree/constants.ts +60 -0
  135. package/src/components/composed/tree/index.ts +51 -0
  136. package/src/components/composed/tree/tree-atom.ts +590 -0
  137. package/src/components/composed/tree/tree-context.ts +13 -0
  138. package/src/components/composed/tree/tree-node.tsx +490 -0
  139. package/src/components/composed/tree/tree.tsx +286 -0
  140. package/src/components/composed/tree/type.ts +322 -0
  141. package/src/components/composed/tree/use-tree-dnd.ts +179 -0
  142. package/src/components/composed/tree/use-tree-state.ts +105 -0
  143. package/src/components/composed/tree/use-tree.ts +307 -0
  144. package/src/components/composed/tree/utils.ts +172 -0
  145. package/src/components/composed/user-avatar.tsx +11 -96
  146. package/src/components/docs/component-preview.tsx +11 -11
  147. package/src/components/user-avatar/__tests__/user-avatar.test.tsx +245 -0
  148. package/src/components/user-avatar/audio-wave.tsx +22 -0
  149. package/src/components/user-avatar/component.tsx +152 -0
  150. package/src/components/user-avatar/fallback.tsx +26 -0
  151. package/src/components/user-avatar/index.ts +34 -0
  152. package/src/components/user-avatar/presence-badge.tsx +56 -0
  153. package/src/components/user-avatar/type.ts +85 -0
  154. package/src/components/user-avatar/use-user-avatar-state.ts +60 -0
  155. package/src/components/user-avatar/use-user-avatar.ts +144 -0
  156. package/src/components/user-avatar/user-avatar-atom.ts +198 -0
  157. package/src/components/user-avatar/utils.ts +8 -0
  158. package/src/index.css +211 -153
  159. package/src/index.ts +35 -2
  160. package/src/state/exegia-provider.tsx +79 -0
  161. package/src/state/index.ts +4 -0
  162. package/src/state/store.ts +19 -0
@@ -0,0 +1,310 @@
1
+ import { describe, expect, mock, test } from "bun:test"
2
+ import { act, render, renderHook, screen } from "@testing-library/react"
3
+
4
+ import { AISidebar } from "../ai-sidebar"
5
+ import type { AISidebarController, SidebarResource } from "../type.ts"
6
+ import { useAISidebar } from "../use-ai-sidebar"
7
+
8
+ const RESOURCES: SidebarResource[] = [
9
+ {
10
+ id: "corpora",
11
+ label: "Corpora",
12
+ kind: "project",
13
+ children: [
14
+ {
15
+ id: "manuscripts",
16
+ label: "Manuscripts",
17
+ kind: "folder",
18
+ children: [{ id: "codex-a", label: "Codex Askewianus", kind: "file" }],
19
+ },
20
+ { id: "readme", label: "Field notes", kind: "file" },
21
+ ],
22
+ },
23
+ { id: "reading-list", label: "Reading list", kind: "bookmark" },
24
+ { id: "archive", label: "Archive", kind: "folder", disabled: true },
25
+ ]
26
+
27
+ const labels = (controller: AISidebarController) =>
28
+ controller.flat.map((row) => row.item.id)
29
+
30
+ describe("useAISidebar · expansion", () => {
31
+ test("expand, collapse and toggle change what flattens", () => {
32
+ const { result } = renderHook(() =>
33
+ useAISidebar({ defaultItems: RESOURCES })
34
+ )
35
+ expect(labels(result.current)).toEqual(["corpora", "reading-list", "archive"])
36
+
37
+ act(() => result.current.expand("corpora"))
38
+ expect(result.current.isExpanded("corpora")).toBe(true)
39
+ expect(labels(result.current)).toContain("manuscripts")
40
+
41
+ // Expanding twice is a no-op rather than a toggle.
42
+ act(() => result.current.expand("corpora"))
43
+ expect(result.current.isExpanded("corpora")).toBe(true)
44
+
45
+ act(() => result.current.toggleExpanded("corpora"))
46
+ expect(result.current.isExpanded("corpora")).toBe(false)
47
+ act(() => result.current.collapse("corpora"))
48
+ expect(result.current.isExpanded("corpora")).toBe(false)
49
+ })
50
+
51
+ test("expandAll opens every container that has children", () => {
52
+ const { result } = renderHook(() =>
53
+ useAISidebar({ defaultItems: RESOURCES })
54
+ )
55
+ act(() => result.current.expandAll())
56
+ expect(labels(result.current)).toEqual([
57
+ "corpora",
58
+ "manuscripts",
59
+ "codex-a",
60
+ "readme",
61
+ "reading-list",
62
+ "archive",
63
+ ])
64
+ // A childless folder is not an expandable row.
65
+ expect(result.current.isExpanded("archive")).toBe(false)
66
+
67
+ act(() => result.current.collapseAll())
68
+ expect([...result.current.expandedIds]).toEqual([])
69
+ })
70
+
71
+ test("reveal opens ancestors only", () => {
72
+ const { result } = renderHook(() =>
73
+ useAISidebar({ defaultItems: RESOURCES })
74
+ )
75
+ act(() => result.current.reveal("codex-a"))
76
+ expect(result.current.isExpanded("corpora")).toBe(true)
77
+ expect(result.current.isExpanded("manuscripts")).toBe(true)
78
+ expect(result.current.isExpanded("codex-a")).toBe(false)
79
+ })
80
+
81
+ test("controlled expandedIds win and changes are only reported", () => {
82
+ const onExpandedChange = mock((ids: string[]) => void ids)
83
+ const { result } = renderHook(() =>
84
+ useAISidebar({
85
+ defaultItems: RESOURCES,
86
+ expandedIds: ["corpora"],
87
+ onExpandedChange,
88
+ })
89
+ )
90
+ act(() => result.current.collapse("corpora"))
91
+ expect(result.current.isExpanded("corpora")).toBe(true)
92
+ expect(onExpandedChange).toHaveBeenCalledWith([])
93
+ })
94
+ })
95
+
96
+ describe("useAISidebar · selection and focus", () => {
97
+ test("select holds the id when uncontrolled and reports either way", () => {
98
+ const onActiveChange = mock((id: string) => void id)
99
+ const { result } = renderHook(() =>
100
+ useAISidebar({ defaultItems: RESOURCES, onActiveChange })
101
+ )
102
+ act(() => result.current.select("reading-list"))
103
+ expect(result.current.selectedId).toBe("reading-list")
104
+ expect(onActiveChange).toHaveBeenCalledWith("reading-list")
105
+
106
+ const controlled = renderHook(() =>
107
+ useAISidebar({
108
+ defaultItems: RESOURCES,
109
+ activeId: "corpora",
110
+ onActiveChange,
111
+ })
112
+ )
113
+ act(() => controlled.result.current.select("reading-list"))
114
+ expect(controlled.result.current.selectedId).toBe("corpora")
115
+ })
116
+
117
+ test("focus moves the roving target, and it follows rows that fold away", () => {
118
+ const { result } = renderHook(() =>
119
+ useAISidebar({ defaultItems: RESOURCES, defaultExpandedIds: ["corpora"] })
120
+ )
121
+ act(() => result.current.focus("readme"))
122
+ expect(result.current.focusedId).toBe("readme")
123
+
124
+ // Collapsing the parent hides the focused row — focus falls back to the
125
+ // first visible row rather than pointing at nothing.
126
+ act(() => result.current.collapse("corpora"))
127
+ expect(result.current.focusedId).toBe("corpora")
128
+ })
129
+ })
130
+
131
+ describe("useAISidebar · rename", () => {
132
+ test("renames hook-owned data and leaves rename mode", () => {
133
+ const { result } = renderHook(() =>
134
+ useAISidebar({ defaultItems: RESOURCES })
135
+ )
136
+ act(() => result.current.startRename("reading-list"))
137
+ expect(result.current.renamingId).toBe("reading-list")
138
+
139
+ act(() => result.current.rename("reading-list", " To read "))
140
+ expect(result.current.renamingId).toBeNull()
141
+ expect(result.current.getItem("reading-list")?.label).toBe("To read")
142
+ })
143
+
144
+ test("blank, unchanged and unknown targets are dropped", () => {
145
+ const onRename = mock(() => {})
146
+ const { result } = renderHook(() =>
147
+ useAISidebar({ defaultItems: RESOURCES, onRename })
148
+ )
149
+ act(() => result.current.rename("reading-list", " "))
150
+ act(() => result.current.rename("reading-list", "Reading list"))
151
+ act(() => result.current.rename("nope", "Whatever"))
152
+ expect(onRename).not.toHaveBeenCalled()
153
+
154
+ // Arming rename against a row that never renders is inert.
155
+ act(() => result.current.startRename("nope"))
156
+ expect(result.current.renamingId).toBeNull()
157
+ })
158
+
159
+ test("a rejected onRename rolls the label back and announces it", async () => {
160
+ const onRename = mock(() => Promise.reject(new Error("offline")))
161
+ const { result } = renderHook(() =>
162
+ useAISidebar({ defaultItems: RESOURCES, onRename })
163
+ )
164
+ await act(async () => {
165
+ result.current.rename("reading-list", "To read")
166
+ await Promise.resolve()
167
+ })
168
+ expect(result.current.getItem("reading-list")?.label).toBe("Reading list")
169
+ expect(result.current.announcement).toContain("Rename failed")
170
+ })
171
+ })
172
+
173
+ describe("useAISidebar · reorder", () => {
174
+ test("move reorders and announces", async () => {
175
+ const onMove = mock(() => {})
176
+ const { result } = renderHook(() =>
177
+ useAISidebar({ defaultItems: RESOURCES, onMove })
178
+ )
179
+ await act(async () =>
180
+ result.current.move({
181
+ itemId: "reading-list",
182
+ targetId: "corpora",
183
+ position: "before",
184
+ })
185
+ )
186
+ expect(result.current.items.map((item) => item.id)).toEqual([
187
+ "reading-list",
188
+ "corpora",
189
+ "archive",
190
+ ])
191
+ expect(onMove).toHaveBeenCalled()
192
+ expect(result.current.announcement).toContain("Moved Reading list before")
193
+ })
194
+
195
+ test("a rejected onMove restores the previous order", async () => {
196
+ const onMoveError = mock(() => {})
197
+ const { result } = renderHook(() =>
198
+ useAISidebar({
199
+ defaultItems: RESOURCES,
200
+ onMove: () => Promise.reject(new Error("409")),
201
+ onMoveError,
202
+ })
203
+ )
204
+ await act(async () =>
205
+ result.current.move({
206
+ itemId: "reading-list",
207
+ targetId: "corpora",
208
+ position: "before",
209
+ })
210
+ )
211
+ expect(result.current.items.map((item) => item.id)).toEqual([
212
+ "corpora",
213
+ "reading-list",
214
+ "archive",
215
+ ])
216
+ expect(result.current.announcement).toContain("Move failed")
217
+ expect(onMoveError).toHaveBeenCalled()
218
+ })
219
+
220
+ test("moves the data forbids are refused", async () => {
221
+ const { result } = renderHook(() =>
222
+ useAISidebar({ defaultItems: RESOURCES })
223
+ )
224
+ // Into its own subtree.
225
+ await act(async () =>
226
+ result.current.move({
227
+ itemId: "corpora",
228
+ targetId: "manuscripts",
229
+ position: "inside",
230
+ })
231
+ )
232
+ // Inside a row that cannot contain children.
233
+ await act(async () =>
234
+ result.current.move({
235
+ itemId: "corpora",
236
+ targetId: "reading-list",
237
+ position: "inside",
238
+ })
239
+ )
240
+ expect(result.current.items.map((item) => item.id)).toEqual([
241
+ "corpora",
242
+ "reading-list",
243
+ "archive",
244
+ ])
245
+ })
246
+ })
247
+
248
+ describe("useAISidebar · menu", () => {
249
+ test("openMenu and closeMenu track one row at a time", () => {
250
+ const { result } = renderHook(() =>
251
+ useAISidebar({ defaultItems: RESOURCES })
252
+ )
253
+ act(() => result.current.openMenu("reading-list"))
254
+ expect(result.current.menuOpenId).toBe("reading-list")
255
+
256
+ act(() => result.current.openMenu("nope"))
257
+ expect(result.current.menuOpenId).toBe("reading-list")
258
+
259
+ act(() => result.current.closeMenu())
260
+ expect(result.current.menuOpenId).toBeNull()
261
+ })
262
+ })
263
+
264
+ describe("AISidebar · controller form", () => {
265
+ function Harness({
266
+ onReady,
267
+ }: {
268
+ onReady: (controller: AISidebarController) => void
269
+ }) {
270
+ const controller = useAISidebar({ defaultItems: RESOURCES })
271
+ onReady(controller)
272
+ return <AISidebar controller={controller} />
273
+ }
274
+
275
+ test("renders from the controller and reacts to calls made outside it", async () => {
276
+ let sidebar!: AISidebarController
277
+ render(<Harness onReady={(next) => (sidebar = next)} />)
278
+ expect(screen.queryByRole("treeitem", { name: /Field notes/ })).toBeNull()
279
+
280
+ await act(async () => sidebar.expandAll())
281
+ expect(screen.getByRole("treeitem", { name: /Field notes/ })).toBeDefined()
282
+
283
+ await act(async () => sidebar.select("readme"))
284
+ expect(
285
+ screen.getByRole("treeitem", { name: /Field notes/ }).getAttribute("aria-selected")
286
+ ).toBe("true")
287
+ })
288
+
289
+ // Rename mode is not asserted through the rendered row here: mounting the
290
+ // input makes happy-dom fire focus/blur, and the row's blur handler
291
+ // commits and closes rename mode before an assertion can run. The hook
292
+ // tests above cover the state; the browser covers the rendering.
293
+
294
+ test("a move made from outside reorders the rendered rows", async () => {
295
+ let sidebar!: AISidebarController
296
+ render(<Harness onReady={(next) => (sidebar = next)} />)
297
+
298
+ await act(async () =>
299
+ sidebar.move({
300
+ itemId: "reading-list",
301
+ targetId: "corpora",
302
+ position: "before",
303
+ })
304
+ )
305
+ const rows = screen
306
+ .getAllByRole("treeitem")
307
+ .map((row) => row.getAttribute("aria-label") ?? row.textContent)
308
+ expect(rows[0]).toContain("Reading list")
309
+ })
310
+ })