@arbidocs/blocks 0.4.2 → 0.4.4

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 (156) hide show
  1. package/README.md +42 -0
  2. package/dist/agents/definitions/AgentCreator.d.ts +11 -2
  3. package/dist/agents/definitions/index.d.ts +1 -0
  4. package/dist/agents/definitions/persona.d.ts +16 -0
  5. package/dist/agents/index.cjs +199 -146
  6. package/dist/agents/index.js +3 -2
  7. package/dist/agents/tasks/TaskRunsDialog.d.ts +15 -3
  8. package/dist/agents/tasks/index.d.ts +1 -0
  9. package/dist/agents/tasks/taskRequest.d.ts +61 -0
  10. package/dist/chat/AssistantDock.d.ts +39 -0
  11. package/dist/chat/ChatDocuments.d.ts +17 -0
  12. package/dist/chat/ChatPage.d.ts +16 -0
  13. package/dist/chat/ChatThreads.d.ts +28 -0
  14. package/dist/chat/chat-page-types.d.ts +121 -0
  15. package/dist/chat/chat-surface.d.ts +9 -0
  16. package/dist/chat/index.cjs +247 -205
  17. package/dist/chat/index.d.ts +6 -0
  18. package/dist/chat/index.js +4 -2
  19. package/dist/{chunk-OBLSURHV.js → chunk-2NNTQLBY.js} +3 -3
  20. package/dist/{chunk-OBLSURHV.js.map → chunk-2NNTQLBY.js.map} +1 -1
  21. package/dist/chunk-2VBJ763Y.js +296 -0
  22. package/dist/chunk-2VBJ763Y.js.map +1 -0
  23. package/dist/{chunk-2ORA62OF.js → chunk-67MKU5U7.js} +3 -3
  24. package/dist/{chunk-2ORA62OF.js.map → chunk-67MKU5U7.js.map} +1 -1
  25. package/dist/{chunk-AE5FHFZ5.cjs → chunk-6CHMNKXC.cjs} +6 -6
  26. package/dist/{chunk-AE5FHFZ5.cjs.map → chunk-6CHMNKXC.cjs.map} +1 -1
  27. package/dist/{chunk-TYZUCAT7.js → chunk-7YADKWZT.js} +43 -6
  28. package/dist/chunk-7YADKWZT.js.map +1 -0
  29. package/dist/chunk-ATXPAD6S.cjs +1952 -0
  30. package/dist/chunk-ATXPAD6S.cjs.map +1 -0
  31. package/dist/{chunk-YBXCZEWS.js → chunk-B673CCW5.js} +29 -6
  32. package/dist/chunk-B673CCW5.js.map +1 -0
  33. package/dist/{chunk-ITGHMJOM.js → chunk-BBEMPS6C.js} +169 -26
  34. package/dist/chunk-BBEMPS6C.js.map +1 -0
  35. package/dist/{chunk-XNBAXRY7.cjs → chunk-DGVKBOY5.cjs} +188 -32
  36. package/dist/chunk-DGVKBOY5.cjs.map +1 -0
  37. package/dist/{chunk-2K33YGM6.cjs → chunk-DN3NFNIA.cjs} +63 -294
  38. package/dist/chunk-DN3NFNIA.cjs.map +1 -0
  39. package/dist/{chunk-XQ7PKKDY.cjs → chunk-G4YOH5DU.cjs} +3 -3
  40. package/dist/{chunk-XQ7PKKDY.cjs.map → chunk-G4YOH5DU.cjs.map} +1 -1
  41. package/dist/{chunk-AXIZ2MDV.js → chunk-HER7MTRR.js} +3 -3
  42. package/dist/{chunk-AXIZ2MDV.js.map → chunk-HER7MTRR.js.map} +1 -1
  43. package/dist/{chunk-SYDHKKRZ.cjs → chunk-IZHWP6WQ.cjs} +47 -5
  44. package/dist/chunk-IZHWP6WQ.cjs.map +1 -0
  45. package/dist/chunk-JPO3F4J7.js +1899 -0
  46. package/dist/chunk-JPO3F4J7.js.map +1 -0
  47. package/dist/{chunk-WJMEXTVD.cjs → chunk-KDG6FER6.cjs} +28 -2
  48. package/dist/chunk-KDG6FER6.cjs.map +1 -0
  49. package/dist/chunk-KHX2BX3S.cjs +307 -0
  50. package/dist/chunk-KHX2BX3S.cjs.map +1 -0
  51. package/dist/{chunk-WTNMWEBC.js → chunk-NJL4LIXX.js} +4 -3
  52. package/dist/chunk-NJL4LIXX.js.map +1 -0
  53. package/dist/{chunk-J2BGSWXM.cjs → chunk-O637DUKS.cjs} +27 -27
  54. package/dist/{chunk-J2BGSWXM.cjs.map → chunk-O637DUKS.cjs.map} +1 -1
  55. package/dist/{chunk-WW6EOVZA.js → chunk-OXF2IFXG.js} +26 -3
  56. package/dist/chunk-OXF2IFXG.js.map +1 -0
  57. package/dist/{chunk-6HIPFEID.cjs → chunk-PBS5MFIY.cjs} +4 -4
  58. package/dist/{chunk-6HIPFEID.cjs.map → chunk-PBS5MFIY.cjs.map} +1 -1
  59. package/dist/{chunk-AZQDNYH4.js → chunk-RCP6THBS.js} +9 -1249
  60. package/dist/chunk-RCP6THBS.js.map +1 -0
  61. package/dist/{chunk-GDFWJ73C.cjs → chunk-RIL7G7AS.cjs} +4 -1286
  62. package/dist/chunk-RIL7G7AS.cjs.map +1 -0
  63. package/dist/{chunk-DREI7EQ2.cjs → chunk-SS23F63U.cjs} +9 -9
  64. package/dist/{chunk-DREI7EQ2.cjs.map → chunk-SS23F63U.cjs.map} +1 -1
  65. package/dist/{chunk-7PEELPMA.js → chunk-TI4UTYRL.js} +62 -285
  66. package/dist/chunk-TI4UTYRL.js.map +1 -0
  67. package/dist/{chunk-6P6AEQJB.js → chunk-WJZBJUPX.js} +4 -4
  68. package/dist/{chunk-6P6AEQJB.js.map → chunk-WJZBJUPX.js.map} +1 -1
  69. package/dist/{chunk-UFHMLHIR.cjs → chunk-XAJIKT6Y.cjs} +32 -11
  70. package/dist/chunk-XAJIKT6Y.cjs.map +1 -0
  71. package/dist/{chunk-G3G7LZPM.cjs → chunk-XKKGGUU2.cjs} +29 -3
  72. package/dist/chunk-XKKGGUU2.cjs.map +1 -0
  73. package/dist/{chunk-UWGIB3VQ.js → chunk-XNOASBGW.js} +33 -12
  74. package/dist/chunk-XNOASBGW.js.map +1 -0
  75. package/dist/{chunk-CTZ5745I.js → chunk-XQKGYGCJ.js} +3 -3
  76. package/dist/{chunk-CTZ5745I.js.map → chunk-XQKGYGCJ.js.map} +1 -1
  77. package/dist/{chunk-54Q3RNJU.cjs → chunk-ZVPTFTGV.cjs} +4 -3
  78. package/dist/chunk-ZVPTFTGV.cjs.map +1 -0
  79. package/dist/citations/index.cjs +11 -11
  80. package/dist/citations/index.js +2 -2
  81. package/dist/collaboration/contacts/AddContactsParts.d.ts +9 -4
  82. package/dist/collaboration/contacts/index.d.ts +1 -0
  83. package/dist/collaboration/contacts/parseEmails.d.ts +26 -0
  84. package/dist/collaboration/index.cjs +51 -31
  85. package/dist/collaboration/index.js +1 -1
  86. package/dist/components/MemberAvatarStack.d.ts +7 -2
  87. package/dist/connect/index.cjs +41 -12
  88. package/dist/connect/index.cjs.map +1 -1
  89. package/dist/connect/index.d.ts +3 -2
  90. package/dist/connect/index.js +20 -4
  91. package/dist/connect/index.js.map +1 -1
  92. package/dist/connection/ConnectionProvider.d.ts +2 -0
  93. package/dist/connection/ai.d.ts +13 -1
  94. package/dist/connection/useConnectedShellWorkspaces.d.ts +15 -0
  95. package/dist/facts/index.cjs +3 -3
  96. package/dist/facts/index.cjs.map +1 -1
  97. package/dist/facts/index.js +2 -2
  98. package/dist/facts/index.js.map +1 -1
  99. package/dist/files/index.cjs +4 -3
  100. package/dist/files/index.cjs.map +1 -1
  101. package/dist/files/index.js +4 -3
  102. package/dist/files/index.js.map +1 -1
  103. package/dist/home/index.cjs +18 -17
  104. package/dist/home/index.cjs.map +1 -1
  105. package/dist/home/index.js +3 -2
  106. package/dist/home/index.js.map +1 -1
  107. package/dist/index.cjs +824 -695
  108. package/dist/index.cjs.map +1 -1
  109. package/dist/index.d.ts +1 -1
  110. package/dist/index.js +16 -15
  111. package/dist/index.js.map +1 -1
  112. package/dist/lib/format.d.ts +31 -0
  113. package/dist/retrieval/index.cjs +36 -36
  114. package/dist/retrieval/index.js +1 -1
  115. package/dist/settings/index.cjs +13 -11
  116. package/dist/settings/index.cjs.map +1 -1
  117. package/dist/settings/index.js +13 -11
  118. package/dist/settings/index.js.map +1 -1
  119. package/dist/shell/SplitLayout.d.ts +32 -0
  120. package/dist/shell/index.cjs +72 -68
  121. package/dist/shell/index.d.ts +1 -0
  122. package/dist/shell/index.js +2 -2
  123. package/dist/site/index.cjs +71 -71
  124. package/dist/site/index.js +3 -3
  125. package/dist/studio/index.cjs +79 -76
  126. package/dist/studio/index.cjs.map +1 -1
  127. package/dist/studio/index.js +9 -6
  128. package/dist/studio/index.js.map +1 -1
  129. package/dist/workspaces/WorkspaceGallery.d.ts +42 -0
  130. package/dist/workspaces/index.cjs +74 -7
  131. package/dist/workspaces/index.cjs.map +1 -1
  132. package/dist/workspaces/index.d.ts +2 -0
  133. package/dist/workspaces/index.js +73 -7
  134. package/dist/workspaces/index.js.map +1 -1
  135. package/package.json +3 -3
  136. package/src/blocks.css +10 -13
  137. package/dist/chunk-2K33YGM6.cjs.map +0 -1
  138. package/dist/chunk-54Q3RNJU.cjs.map +0 -1
  139. package/dist/chunk-7PEELPMA.js.map +0 -1
  140. package/dist/chunk-AZQDNYH4.js.map +0 -1
  141. package/dist/chunk-G3G7LZPM.cjs.map +0 -1
  142. package/dist/chunk-GDFWJ73C.cjs.map +0 -1
  143. package/dist/chunk-ITGHMJOM.js.map +0 -1
  144. package/dist/chunk-SCFYPAG6.cjs +0 -18
  145. package/dist/chunk-SCFYPAG6.cjs.map +0 -1
  146. package/dist/chunk-SYDHKKRZ.cjs.map +0 -1
  147. package/dist/chunk-TYZUCAT7.js.map +0 -1
  148. package/dist/chunk-UFHMLHIR.cjs.map +0 -1
  149. package/dist/chunk-UWGIB3VQ.js.map +0 -1
  150. package/dist/chunk-WJMEXTVD.cjs.map +0 -1
  151. package/dist/chunk-WTNMWEBC.js.map +0 -1
  152. package/dist/chunk-WW6EOVZA.js.map +0 -1
  153. package/dist/chunk-XNBAXRY7.cjs.map +0 -1
  154. package/dist/chunk-YBXCZEWS.js.map +0 -1
  155. package/dist/chunk-YDHO4AVL.js +0 -16
  156. package/dist/chunk-YDHO4AVL.js.map +0 -1
@@ -0,0 +1,307 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var react = require('react');
5
+ var ui = require('@arbidocs/react/ui');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+ var lucideReact = require('lucide-react');
8
+
9
+ // src/provider/TooltipScope.tsx
10
+ var TooltipScopeContext = react.createContext(false);
11
+ function BlocksTooltipProvider({ children }) {
12
+ const isNested = react.useContext(TooltipScopeContext);
13
+ if (isNested) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
14
+ return /* @__PURE__ */ jsxRuntime.jsx(TooltipScopeContext.Provider, { value: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.TooltipProvider, { children }) });
15
+ }
16
+ var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
17
+ function focusableIn(container) {
18
+ return Array.from(container.querySelectorAll(FOCUSABLE)).filter(
19
+ (el) => !el.closest("[inert]") && el.getAttribute("aria-hidden") !== "true"
20
+ );
21
+ }
22
+ function useDrawerBehavior(isOpen, onClose, containerRef) {
23
+ const onCloseRef = react.useRef(onClose);
24
+ react.useEffect(() => {
25
+ onCloseRef.current = onClose;
26
+ });
27
+ react.useEffect(() => {
28
+ if (!isOpen) return;
29
+ const previousOverflow = document.body.style.overflow;
30
+ const opener = document.activeElement instanceof HTMLElement ? document.activeElement : null;
31
+ document.body.style.overflow = "hidden";
32
+ const container = containerRef?.current ?? null;
33
+ if (container) {
34
+ const first = focusableIn(container)[0];
35
+ if (first) first.focus();
36
+ else {
37
+ if (!container.hasAttribute("tabindex")) container.setAttribute("tabindex", "-1");
38
+ container.focus();
39
+ }
40
+ }
41
+ const onKey = (e) => {
42
+ if (e.key === "Escape") {
43
+ onCloseRef.current();
44
+ return;
45
+ }
46
+ if (e.key !== "Tab" || !container) return;
47
+ const items = focusableIn(container);
48
+ if (items.length === 0) {
49
+ e.preventDefault();
50
+ return;
51
+ }
52
+ const firstItem = items[0];
53
+ const lastItem = items[items.length - 1];
54
+ const active = document.activeElement;
55
+ if (!container.contains(active)) {
56
+ e.preventDefault();
57
+ firstItem.focus();
58
+ } else if (e.shiftKey && active === firstItem) {
59
+ e.preventDefault();
60
+ lastItem.focus();
61
+ } else if (!e.shiftKey && active === lastItem) {
62
+ e.preventDefault();
63
+ firstItem.focus();
64
+ }
65
+ };
66
+ window.addEventListener("keydown", onKey);
67
+ return () => {
68
+ document.body.style.overflow = previousOverflow;
69
+ window.removeEventListener("keydown", onKey);
70
+ if (opener?.isConnected) opener.focus();
71
+ };
72
+ }, [isOpen, containerRef]);
73
+ }
74
+ var DEFAULT_OVERLAY_ZONE_ID = "app-overlay-zone";
75
+ var OverlayZoneContext = react.createContext(DEFAULT_OVERLAY_ZONE_ID);
76
+ function useOverlayZoneId() {
77
+ return react.useContext(OverlayZoneContext);
78
+ }
79
+ function ShellRoot({
80
+ banner,
81
+ topbar,
82
+ sidebar,
83
+ tabBar,
84
+ aside,
85
+ overlays,
86
+ children,
87
+ className,
88
+ "data-testid": testId
89
+ }) {
90
+ return /* @__PURE__ */ jsxRuntime.jsx(BlocksTooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs(
91
+ "div",
92
+ {
93
+ "data-slot": "shell",
94
+ "data-testid": testId,
95
+ className: ui.cn("flex h-screen flex-col", className),
96
+ children: [
97
+ banner,
98
+ topbar,
99
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex flex-1 overflow-hidden", children: [
100
+ sidebar,
101
+ children,
102
+ aside,
103
+ overlays
104
+ ] }),
105
+ tabBar
106
+ ]
107
+ }
108
+ ) });
109
+ }
110
+ function ShellContent({
111
+ children,
112
+ scrollRef,
113
+ onClick,
114
+ overlayZoneId = DEFAULT_OVERLAY_ZONE_ID,
115
+ className
116
+ }) {
117
+ return /* @__PURE__ */ jsxRuntime.jsx(
118
+ "div",
119
+ {
120
+ "data-slot": "shell-content",
121
+ className: ui.cn("flex min-w-0 flex-1 flex-col", className),
122
+ onClick,
123
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative min-h-0 flex-1", children: [
124
+ /* @__PURE__ */ jsxRuntime.jsx(OverlayZoneContext.Provider, { value: overlayZoneId, children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: scrollRef, className: "@container h-full overflow-auto", children }) }),
125
+ /* @__PURE__ */ jsxRuntime.jsx("div", { id: overlayZoneId, "data-slot": "overlay-zone" })
126
+ ] })
127
+ }
128
+ );
129
+ }
130
+ function ShellTopbar({ children, actions, className }) {
131
+ return (
132
+ // `relative z-30` keeps dropdowns the header opens above the page region (a later sibling).
133
+ /* @__PURE__ */ jsxRuntime.jsxs(
134
+ "div",
135
+ {
136
+ "data-slot": "shell-topbar",
137
+ className: ui.cn("relative z-30 flex h-14 shrink-0 items-center bg-background px-2", className),
138
+ children: [
139
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto flex w-full max-w-6xl items-center", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden min-w-0 flex-1 px-6 sm:block", children }) }),
140
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-4 top-1/2 flex -translate-y-1/2 items-center gap-1", children: actions })
141
+ ]
142
+ }
143
+ )
144
+ );
145
+ }
146
+ function ShellDock({
147
+ isOpen,
148
+ onOpenChange,
149
+ width = "24rem",
150
+ isMobile = false,
151
+ label,
152
+ closeLabel = "Close",
153
+ children,
154
+ className,
155
+ "data-testid": testId
156
+ }) {
157
+ const mobileRef = react.useRef(null);
158
+ const closeMobile = react.useCallback(() => onOpenChange?.(false), [onOpenChange]);
159
+ useDrawerBehavior(isOpen && isMobile, closeMobile, mobileRef);
160
+ if (!isOpen) return null;
161
+ if (isMobile) {
162
+ return /* @__PURE__ */ jsxRuntime.jsxs(
163
+ "div",
164
+ {
165
+ ref: mobileRef,
166
+ role: "dialog",
167
+ "aria-modal": "true",
168
+ "aria-label": label,
169
+ "data-slot": "shell-dock",
170
+ "data-state": "open",
171
+ "data-mobile": "true",
172
+ "data-testid": testId,
173
+ className: ui.cn("fixed inset-0 z-40 flex flex-col bg-background", className),
174
+ children: [
175
+ onOpenChange && /* @__PURE__ */ jsxRuntime.jsx(
176
+ "button",
177
+ {
178
+ type: "button",
179
+ onClick: () => onOpenChange(false),
180
+ "aria-label": closeLabel,
181
+ className: "absolute right-2 top-2 z-10 rounded-md p-2 text-muted-foreground hover:bg-muted",
182
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-5" })
183
+ }
184
+ ),
185
+ children
186
+ ]
187
+ }
188
+ );
189
+ }
190
+ return /* @__PURE__ */ jsxRuntime.jsx(
191
+ "aside",
192
+ {
193
+ "aria-label": label,
194
+ "data-slot": "shell-dock",
195
+ "data-state": "open",
196
+ "data-testid": testId,
197
+ style: { width },
198
+ className: ui.cn(
199
+ "flex shrink-0 flex-col overflow-hidden border-l border-border bg-background",
200
+ className
201
+ ),
202
+ children
203
+ }
204
+ );
205
+ }
206
+ var Shell = {
207
+ Root: ShellRoot,
208
+ Content: ShellContent,
209
+ Topbar: ShellTopbar,
210
+ Dock: ShellDock
211
+ };
212
+ var DEFAULT_PAGE_CONTAINER_PADDING = "px-4 sm:px-6 lg:px-8";
213
+ function PageContainer({
214
+ children,
215
+ wide = false,
216
+ minScreen = false,
217
+ maxWidth = "max-w-6xl",
218
+ padding = DEFAULT_PAGE_CONTAINER_PADDING,
219
+ innerClassName,
220
+ className
221
+ }) {
222
+ return /* @__PURE__ */ jsxRuntime.jsx(
223
+ "div",
224
+ {
225
+ "data-slot": "page-container",
226
+ className: ui.cn(
227
+ "@container bg-background pb-safe",
228
+ minScreen ? "min-h-screen" : "h-full min-h-0",
229
+ className
230
+ ),
231
+ children: /* @__PURE__ */ jsxRuntime.jsx(
232
+ "div",
233
+ {
234
+ className: ui.cn(
235
+ "flex h-full w-full flex-col pb-safe",
236
+ padding,
237
+ !wide && ui.cn("mx-auto", maxWidth),
238
+ innerClassName
239
+ ),
240
+ children
241
+ }
242
+ )
243
+ }
244
+ );
245
+ }
246
+ var DEFAULT_SIDEBAR_TOGGLE_BUTTON_LABELS = {
247
+ closeSidebar: "Close sidebar",
248
+ openSidebar: "Open sidebar"
249
+ };
250
+ function SidebarToggleButton({
251
+ isOpen,
252
+ onToggle,
253
+ labels,
254
+ "data-testid": testId = "tabheader-sidebar-toggle"
255
+ }) {
256
+ const l = { ...DEFAULT_SIDEBAR_TOGGLE_BUTTON_LABELS, ...labels };
257
+ const label = isOpen ? l.closeSidebar : l.openSidebar;
258
+ return /* @__PURE__ */ jsxRuntime.jsx(
259
+ "button",
260
+ {
261
+ type: "button",
262
+ onClick: onToggle,
263
+ className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground pointer-coarse:p-2.5",
264
+ "aria-label": label,
265
+ title: label,
266
+ "data-testid": testId,
267
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelLeft, { className: "size-5" })
268
+ }
269
+ );
270
+ }
271
+ function ToolButton({
272
+ onClick,
273
+ label,
274
+ active = false,
275
+ "data-testid": testId,
276
+ children
277
+ }) {
278
+ return /* @__PURE__ */ jsxRuntime.jsx(
279
+ "button",
280
+ {
281
+ type: "button",
282
+ onClick,
283
+ title: label,
284
+ "aria-label": label,
285
+ "data-slot": "tool-button",
286
+ "data-testid": testId,
287
+ className: ui.cn(
288
+ "relative flex size-9 shrink-0 items-center justify-center rounded-md transition-colors hover:bg-accent hover:text-accent-foreground pointer-coarse:size-11",
289
+ active ? "bg-accent text-accent-foreground" : "text-muted-foreground"
290
+ ),
291
+ children
292
+ }
293
+ );
294
+ }
295
+
296
+ exports.BlocksTooltipProvider = BlocksTooltipProvider;
297
+ exports.DEFAULT_OVERLAY_ZONE_ID = DEFAULT_OVERLAY_ZONE_ID;
298
+ exports.DEFAULT_PAGE_CONTAINER_PADDING = DEFAULT_PAGE_CONTAINER_PADDING;
299
+ exports.DEFAULT_SIDEBAR_TOGGLE_BUTTON_LABELS = DEFAULT_SIDEBAR_TOGGLE_BUTTON_LABELS;
300
+ exports.PageContainer = PageContainer;
301
+ exports.Shell = Shell;
302
+ exports.SidebarToggleButton = SidebarToggleButton;
303
+ exports.ToolButton = ToolButton;
304
+ exports.useDrawerBehavior = useDrawerBehavior;
305
+ exports.useOverlayZoneId = useOverlayZoneId;
306
+ //# sourceMappingURL=chunk-KHX2BX3S.cjs.map
307
+ //# sourceMappingURL=chunk-KHX2BX3S.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provider/TooltipScope.tsx","../src/lib/use-drawer.ts","../src/shell/Shell.tsx","../src/shell/ToolButton.tsx"],"names":["createContext","useContext","jsx","Fragment","TooltipProvider","useRef","useEffect","jsxs","cn","useCallback","X","PanelLeft"],"mappings":"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","file":"chunk-KHX2BX3S.cjs","sourcesContent":["import { createContext, useContext, type ReactNode } from 'react'\nimport { TooltipProvider } from '@arbidocs/react/ui'\n\nconst TooltipScopeContext = createContext(false)\n\n/**\n * A TooltipProvider that mounts once: inside another `BlocksTooltipProvider` (e.g. a Sidebar under\n * Shell.Root) it renders its children as-is, so tooltips share one delay group. Blocks that show\n * tooltips use this instead of the kit's provider.\n */\nexport function BlocksTooltipProvider({ children }: { children: ReactNode }) {\n const isNested = useContext(TooltipScopeContext)\n if (isNested) return <>{children}</>\n return (\n <TooltipScopeContext.Provider value>\n <TooltipProvider>{children}</TooltipProvider>\n </TooltipScopeContext.Provider>\n )\n}\n","import { useEffect, useRef, type RefObject } from 'react'\n\nconst FOCUSABLE =\n 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\n\nfunction focusableIn(container: HTMLElement): HTMLElement[] {\n return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\n (el) => !el.closest('[inert]') && el.getAttribute('aria-hidden') !== 'true'\n )\n}\n\n/**\n * Behaviour of a modal surface (off-canvas drawer, full-screen dock) while it is open: the page\n * behind does not scroll, Escape closes it, focus moves inside and Tab/Shift+Tab cycle within it\n * (portalled popovers opened from inside stay reachable: only the wrap-around is intercepted),\n * and focus returns to the opener on close. Restores the previous body overflow on close/unmount.\n *\n * Pass `containerRef` (the element carrying `role=\"dialog\"`) to get the focus handling; without it\n * only scroll lock and Escape apply.\n */\nexport function useDrawerBehavior(\n isOpen: boolean,\n onClose: () => void,\n containerRef?: RefObject<HTMLElement | null>\n): void {\n // A fresh `onClose` identity per render must not re-run the effect (it would steal focus).\n const onCloseRef = useRef(onClose)\n useEffect(() => {\n onCloseRef.current = onClose\n })\n\n useEffect(() => {\n if (!isOpen) return\n const previousOverflow = document.body.style.overflow\n const opener = document.activeElement instanceof HTMLElement ? document.activeElement : null\n document.body.style.overflow = 'hidden'\n\n const container = containerRef?.current ?? null\n if (container) {\n const first = focusableIn(container)[0]\n if (first) first.focus()\n else {\n if (!container.hasAttribute('tabindex')) container.setAttribute('tabindex', '-1')\n container.focus()\n }\n }\n\n const onKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n onCloseRef.current()\n return\n }\n if (e.key !== 'Tab' || !container) return\n const items = focusableIn(container)\n if (items.length === 0) {\n e.preventDefault()\n return\n }\n const firstItem = items[0]\n const lastItem = items[items.length - 1]\n const active = document.activeElement\n if (!container.contains(active)) {\n e.preventDefault()\n firstItem.focus()\n } else if (e.shiftKey && active === firstItem) {\n e.preventDefault()\n lastItem.focus()\n } else if (!e.shiftKey && active === lastItem) {\n e.preventDefault()\n firstItem.focus()\n }\n }\n window.addEventListener('keydown', onKey)\n return () => {\n document.body.style.overflow = previousOverflow\n window.removeEventListener('keydown', onKey)\n if (opener?.isConnected) opener.focus()\n }\n }, [isOpen, containerRef])\n}\n","import {\n createContext,\n useCallback,\n useContext,\n useRef,\n type MouseEventHandler,\n type ReactNode,\n type Ref,\n} from 'react'\nimport { PanelLeft, X } from 'lucide-react'\nimport { cn } from '@arbidocs/react/ui'\nimport { useDrawerBehavior } from '../lib/use-drawer'\nimport { BlocksTooltipProvider } from '../provider/TooltipScope'\n\n/** Default id of the element overlay-mode panels portal into (see `Shell.Content`). */\nexport const DEFAULT_OVERLAY_ZONE_ID = 'app-overlay-zone'\n\nconst OverlayZoneContext = createContext(DEFAULT_OVERLAY_ZONE_ID)\n\n/** The id of the enclosing `Shell.Content` overlay zone, for panels that portal into it. */\nexport function useOverlayZoneId(): string {\n return useContext(OverlayZoneContext)\n}\n\nexport interface ShellRootProps {\n /** Full-width strip above everything (e.g. a deletion-pending banner). */\n banner?: ReactNode\n /** Top chrome: a Shell.Topbar on list pages, a MobileAppBar on phones; omit for none. */\n topbar?: ReactNode\n /** The Sidebar.Root (and its drawer backdrop). */\n sidebar?: ReactNode\n /** Phone bottom navigation. */\n tabBar?: ReactNode\n /** Right-hand column beside the content: a `Shell.Dock` (chat, inspector...). */\n aside?: ReactNode\n /** Extra layers inside the body row (connection monitor, …). */\n overlays?: ReactNode\n /** The Shell.Content region. */\n children: ReactNode\n className?: string\n 'data-testid'?: string\n}\n\n/**\n * The application frame: banner, top chrome, then a body row holding the sidebar beside the\n * content, then the phone tab bar. Every region is a slot, so a custom UI composes its own.\n */\nfunction ShellRoot({\n banner,\n topbar,\n sidebar,\n tabBar,\n aside,\n overlays,\n children,\n className,\n 'data-testid': testId,\n}: ShellRootProps) {\n return (\n <BlocksTooltipProvider>\n <div\n data-slot=\"shell\"\n data-testid={testId}\n className={cn('flex h-screen flex-col', className)}\n >\n {banner}\n {topbar}\n {/* `relative` anchors the sidebar's slide-out overlays to this row. */}\n <div className=\"relative flex flex-1 overflow-hidden\">\n {sidebar}\n {children}\n {aside}\n {overlays}\n </div>\n {tabBar}\n </div>\n </BlocksTooltipProvider>\n )\n}\n\nexport interface ShellContentProps {\n children: ReactNode\n /** Scrolling element, for scroll restoration. */\n scrollRef?: Ref<HTMLDivElement>\n onClick?: MouseEventHandler<HTMLDivElement>\n /**\n * Overlay-mode panels portal into this id and cover the content column only. Defaults to\n * `DEFAULT_OVERLAY_ZONE_ID`; descendants read it with `useOverlayZoneId()`.\n */\n overlayZoneId?: string\n className?: string\n}\n\n/**\n * The content column beside the sidebar. It is `relative` so overlay-mode panels (portalled into\n * the overlay zone) cover this region only, leaving the sidebar and its actions interactive.\n */\nfunction ShellContent({\n children,\n scrollRef,\n onClick,\n overlayZoneId = DEFAULT_OVERLAY_ZONE_ID,\n className,\n}: ShellContentProps) {\n return (\n <div\n data-slot=\"shell-content\"\n className={cn('flex min-w-0 flex-1 flex-col', className)}\n onClick={onClick}\n >\n <div className=\"relative min-h-0 flex-1\">\n <OverlayZoneContext.Provider value={overlayZoneId}>\n {/* The content column is a CSS container: pages and parts size by its width with\n container variants (`@3xl:`...), so an open dock or a narrow window is honoured,\n which viewport breakpoints cannot do. */}\n <div ref={scrollRef} className=\"@container h-full overflow-auto\">\n {children}\n </div>\n </OverlayZoneContext.Provider>\n <div id={overlayZoneId} data-slot=\"overlay-zone\" />\n </div>\n </div>\n )\n}\n\nexport interface ShellTopbarProps {\n /** Left side: guidance / breadcrumbs, centred in the page column. */\n children?: ReactNode\n /** Right side: the action cluster. */\n actions?: ReactNode\n className?: string\n}\n\n/** A minimal white header for pages with no sidebar (the workspaces list). */\nfunction ShellTopbar({ children, actions, className }: ShellTopbarProps) {\n return (\n // `relative z-30` keeps dropdowns the header opens above the page region (a later sibling).\n <div\n data-slot=\"shell-topbar\"\n className={cn('relative z-30 flex h-14 shrink-0 items-center bg-background px-2', className)}\n >\n <div className=\"mx-auto flex w-full max-w-6xl items-center\">\n <div className=\"hidden min-w-0 flex-1 px-6 sm:block\">{children}</div>\n </div>\n <div className=\"absolute right-4 top-1/2 flex -translate-y-1/2 items-center gap-1\">\n {actions}\n </div>\n </div>\n )\n}\n\nexport interface ShellDockProps {\n isOpen: boolean\n /** Called with `false` by the mobile close button. */\n onOpenChange?: (isOpen: boolean) => void\n /** Desktop width (CSS length or px). Default 24rem. */\n width?: number | string\n /** Phone layout: the dock covers the screen instead of sitting beside the content. */\n isMobile?: boolean\n /** Accessible name of the dock. */\n label: string\n /** Mobile close button label. */\n closeLabel?: string\n children: ReactNode\n className?: string\n 'data-testid'?: string\n}\n\n/**\n * A right-hand dock for a companion surface (chat, inspector). Controlled: the host owns\n * `isOpen`, the width and the mobile breakpoint. Pass it to `Shell.Root` as `aside`.\n */\nfunction ShellDock({\n isOpen,\n onOpenChange,\n width = '24rem',\n isMobile = false,\n label,\n closeLabel = 'Close',\n children,\n className,\n 'data-testid': testId,\n}: ShellDockProps) {\n // The phone dock covers the screen, so it is a modal: Escape closes, focus is trapped and\n // returns to the opener.\n const mobileRef = useRef<HTMLDivElement>(null)\n const closeMobile = useCallback(() => onOpenChange?.(false), [onOpenChange])\n useDrawerBehavior(isOpen && isMobile, closeMobile, mobileRef)\n if (!isOpen) return null\n if (isMobile) {\n return (\n <div\n ref={mobileRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={label}\n data-slot=\"shell-dock\"\n data-state=\"open\"\n data-mobile=\"true\"\n data-testid={testId}\n className={cn('fixed inset-0 z-40 flex flex-col bg-background', className)}\n >\n {onOpenChange && (\n <button\n type=\"button\"\n onClick={() => onOpenChange(false)}\n aria-label={closeLabel}\n className=\"absolute right-2 top-2 z-10 rounded-md p-2 text-muted-foreground hover:bg-muted\"\n >\n <X className=\"size-5\" />\n </button>\n )}\n {children}\n </div>\n )\n }\n return (\n <aside\n aria-label={label}\n data-slot=\"shell-dock\"\n data-state=\"open\"\n data-testid={testId}\n style={{ width }}\n className={cn(\n 'flex shrink-0 flex-col overflow-hidden border-l border-border bg-background',\n className\n )}\n >\n {children}\n </aside>\n )\n}\n\nexport const Shell = {\n Root: ShellRoot,\n Content: ShellContent,\n Topbar: ShellTopbar,\n Dock: ShellDock,\n}\n\nexport interface PageContainerProps {\n children: ReactNode\n /** Full width instead of the centred 72rem column. */\n wide?: boolean\n /** Stand-alone use: at least a screen tall, instead of filling the parent. */\n minScreen?: boolean\n /** Max width of the centred column (any Tailwind `max-w-*` class). Default `max-w-6xl`. */\n maxWidth?: string\n /**\n * Horizontal padding of the inner column (any Tailwind `px-*` classes, responsive variants\n * welcome). Default `px-4 sm:px-6 lg:px-8`; pass `px-0` for a full-bleed page, or the class\n * string of your own gutter. Use this prop, not `innerClassName`, to change the gutter: the\n * classes are concatenated without a merge step, so a second `px-*` would not reliably win.\n */\n padding?: string\n /** Extra classes for the inner column. */\n innerClassName?: string\n className?: string\n}\n\n/** Default gutter of {@link PageContainer}: tight on a phone, roomier from tablet up. */\nexport const DEFAULT_PAGE_CONTAINER_PADDING = 'px-4 sm:px-6 lg:px-8'\n\n/**\n * Page body wrapper: a white canvas with the standard 72rem column (or full width) and a responsive side gutter. Fills its\n * parent by default so sticky headers and h-full pages keep a definite height chain.\n */\nexport function PageContainer({\n children,\n wide = false,\n minScreen = false,\n maxWidth = 'max-w-6xl',\n padding = DEFAULT_PAGE_CONTAINER_PADDING,\n innerClassName,\n className,\n}: PageContainerProps) {\n return (\n <div\n data-slot=\"page-container\"\n className={cn(\n '@container bg-background pb-safe',\n minScreen ? 'min-h-screen' : 'h-full min-h-0',\n className\n )}\n >\n <div\n className={cn(\n 'flex h-full w-full flex-col pb-safe',\n padding,\n !wide && cn('mx-auto', maxWidth),\n innerClassName\n )}\n >\n {children}\n </div>\n </div>\n )\n}\n\n/** Fixed strings of the sidebar toggle. */\nexport interface SidebarToggleButtonLabels {\n closeSidebar: string\n openSidebar: string\n}\n\nexport const DEFAULT_SIDEBAR_TOGGLE_BUTTON_LABELS: SidebarToggleButtonLabels = {\n closeSidebar: 'Close sidebar',\n openSidebar: 'Open sidebar',\n}\n\nexport interface SidebarToggleButtonProps {\n /** The sidebar is open (pinned). */\n isOpen: boolean\n onToggle: () => void\n labels?: Partial<SidebarToggleButtonLabels>\n 'data-testid'?: string\n}\n\n/** Opens/closes the sidebar from a page header, so the control sits in the same spot everywhere. */\nexport function SidebarToggleButton({\n isOpen,\n onToggle,\n labels,\n 'data-testid': testId = 'tabheader-sidebar-toggle',\n}: SidebarToggleButtonProps) {\n const l = { ...DEFAULT_SIDEBAR_TOGGLE_BUTTON_LABELS, ...labels }\n const label = isOpen ? l.closeSidebar : l.openSidebar\n return (\n <button\n type=\"button\"\n onClick={onToggle}\n className=\"rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground pointer-coarse:p-2.5\"\n aria-label={label}\n title={label}\n data-testid={testId}\n >\n <PanelLeft className=\"size-5\" />\n </button>\n )\n}\n","import type { ReactNode } from 'react'\nimport { cn } from '@arbidocs/react/ui'\n\nexport interface ToolButtonProps {\n onClick: () => void\n /** Accessible name and native tooltip. */\n label: string\n /** The panel/page this opens is showing. */\n active?: boolean\n 'data-testid'?: string\n children: ReactNode\n}\n\n/** An icon button for a topbar or toolbar: 36px (44px coarse), selection tint while active. */\nexport function ToolButton({\n onClick,\n label,\n active = false,\n 'data-testid': testId,\n children,\n}: ToolButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n title={label}\n aria-label={label}\n data-slot=\"tool-button\"\n data-testid={testId}\n className={cn(\n 'relative flex size-9 shrink-0 items-center justify-center rounded-md transition-colors hover:bg-accent hover:text-accent-foreground pointer-coarse:size-11',\n active ? 'bg-accent text-accent-foreground' : 'text-muted-foreground'\n )}\n >\n {children}\n </button>\n )\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { ActionLink } from './chunk-S7PSMMRG.js';
3
3
  import { observeResize } from './chunk-NPLYHR2E.js';
4
+ import { formatCount } from './chunk-OXF2IFXG.js';
4
5
  import { testIdOr, prefixedTestId } from './chunk-7F6CBP3Z.js';
5
6
  import { ChevronDown, FolderPlus, Check, FileCheck2, Plus, Minus } from 'lucide-react';
6
7
  import { Collapsible, CollapsibleTrigger, cn, CollapsibleContent, DialogHeader, DialogTitle, DialogDescription } from '@arbidocs/react/ui';
@@ -132,7 +133,7 @@ var DEFAULT_SCOPE_PICKER_LABELS = {
132
133
  noFoldersYet: "No folders yet",
133
134
  noLabelsYet: "No labels yet \u2014 label files from the Files page to scope by them here.",
134
135
  filesToInclude: "Files to include",
135
- matchedOfTotal: (matched, total) => `${matched} of ${total} file${matched !== 1 ? "s" : ""}`,
136
+ matchedOfTotal: (matched, total) => `${matched} of ${formatCount(total, { one: "file", other: "files" })}`,
136
137
  selectedSuffix: " selected",
137
138
  folderCount: (count) => pluralize(count, "folder"),
138
139
  labelCount: (count) => pluralize(count, "label"),
@@ -500,5 +501,5 @@ function StartTiles({ items, caption, captionTestId, className, testId }) {
500
501
  }
501
502
 
502
503
  export { AddFilesFirst, DEFAULT_ADD_FILES_FIRST_LABELS, DEFAULT_SCOPE_PICKER_LABELS, EmptyStrip, FoldSection, QuickActionDialogHeader, ScopePicker, SectionHeader, SectionLabel, StartTiles, useScopeSelection };
503
- //# sourceMappingURL=chunk-WTNMWEBC.js.map
504
- //# sourceMappingURL=chunk-WTNMWEBC.js.map
504
+ //# sourceMappingURL=chunk-NJL4LIXX.js.map
505
+ //# sourceMappingURL=chunk-NJL4LIXX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/home/FoldSection.tsx","../src/home/AddFilesFirst.tsx","../src/home/QuickActionDialogHeader.tsx","../src/lib/useScopeSelection.ts","../src/home/ScopePicker.tsx","../src/home/sections.tsx","../src/lib/balanced-columns.ts","../src/home/StartTiles.tsx"],"names":["jsxs","jsx","cn","useMemo","useState"],"mappings":";;;;;;;;;AAsBO,SAAS,WAAA,CAAY;AAAA,EAC1B,KAAA;AAAA,EACA,KAAA,GAAQ,CAAA;AAAA,EACR,IAAA;AAAA,EACA,IAAA;AAAA,EACA,YAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAAqB;AACnB,EAAA,uBACE,IAAA,CAAC,WAAA,EAAA,EAAY,IAAA,EAAY,YAAA,EAA4B,aAAU,cAAA,EAC7D,QAAA,EAAA;AAAA,oBAAA,IAAA;AAAA,MAAC,kBAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAU,wMAAA;AAAA,QACV,aAAA,EAAa,MAAA;AAAA,QAEb,QAAA,EAAA;AAAA,0BAAA,GAAA;AAAA,YAAC,WAAA;AAAA,YAAA;AAAA,cACC,SAAA,EAAW,EAAA,CAAG,sCAAA,EAAwC,CAAC,QAAQ,YAAY;AAAA;AAAA,WAC7E;AAAA,0BACA,GAAA,CAAC,UAAM,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,UACZ,QAAQ,CAAA,oBAAK,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,8CAA8C,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,UACjF,QAAQ,CAAC,IAAA,wBACP,MAAA,EAAA,EAAK,SAAA,EAAU,8DAA8D,QAAA,EAAA,IAAA,EAAK;AAAA;AAAA;AAAA,KAEvF;AAAA,oBACA,GAAA,CAAC,sBAAoB,QAAA,EAAS;AAAA,GAAA,EAChC,CAAA;AAEJ;ACzCO,IAAM,8BAAA,GAAsD;AAAA,EACjE,OAAA,EAAS;AACX;AAkBO,SAAS,cAAc,EAAE,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,QAAO,EAAuB;AACpF,EAAA,MAAM,CAAA,GAAI,EAAE,GAAG,8BAAA,EAAgC,GAAG,MAAA,EAAO;AACzD,EAAA,uBACEA,IAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAU,4EAAA;AAAA,MACV,WAAA,EAAU,iBAAA;AAAA,MACV,aAAA,EAAa,MAAA;AAAA,MAEb,QAAA,EAAA;AAAA,wBAAAC,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,8EAAA,EACd,QAAA,kBAAAA,GAAAA,CAAC,UAAA,EAAA,EAAW,SAAA,EAAU,QAAA,EAAS,aAAA,EAAY,MAAA,EAAO,CAAA,EACpD,CAAA;AAAA,wBACAD,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,qBAAA,EACb,QAAA,EAAA;AAAA,0BAAAC,GAAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,uCAAA,EAAyC,YAAE,OAAA,EAAQ,CAAA;AAAA,0BAChEA,GAAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,iDAAiD,QAAA,EAAA,MAAA,EAAO;AAAA,SAAA,EACvE,CAAA;AAAA,QACC;AAAA;AAAA;AAAA,GACH;AAEJ;ACvBO,SAAS,uBAAA,CAAwB;AAAA,EACtC,IAAA;AAAA,EACA,KAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAA,EAAiC;AAC/B,EAAA,uBACED,IAAAA,CAAC,YAAA,EAAA,EAAa,SAAA,EAAU,qBAAA,EAAsB,aAAU,4BAAA,EACtD,QAAA,EAAA;AAAA,oBAAAA,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,8BAAA,EACb,QAAA,EAAA;AAAA,sBAAAC,GAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,uFAAA,EACZ,QAAA,EAAA,IAAA,EACH,CAAA;AAAA,sBACAA,GAAAA,CAAC,WAAA,EAAA,EAAY,SAAA,EAAU,uCAAuC,QAAA,EAAA,KAAA,EAAM;AAAA,KAAA,EACtE,CAAA;AAAA,oBACAA,GAAAA;AAAA,MAAC,iBAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAU,iEAAA;AAAA,QACV,aAAA,EAAa,iBAAA;AAAA,QAEZ,QAAA,EAAA;AAAA;AAAA;AACH,GAAA,EACF,CAAA;AAEJ;ACjBO,SAAS,iBAAA,CAAkB;AAAA,EAChC,WAAA;AAAA,EACA,UAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF,CAAA,EAA6B;AAC3B,EAAA,MAAM,eAAA,GAAkB,OAAA,CAAQ,MAAM,IAAI,GAAA,CAAI,KAAA,CAAM,OAAO,CAAA,EAAG,CAAC,KAAA,CAAM,OAAO,CAAC,CAAA;AAC7E,EAAA,MAAM,cAAA,GAAiB,OAAA,CAAQ,MAAM,IAAI,GAAA,CAAI,KAAA,CAAM,MAAM,CAAA,EAAG,CAAC,KAAA,CAAM,MAAM,CAAC,CAAA;AAG1E,EAAA,MAAM,WAAA,GAAc,OAAA;AAAA,IAClB,MAAM,CAAC,GAAI,UAAA,GAAa,CAAC,EAAE,CAAA,GAAI,EAAC,EAAI,GAAG,WAAW,CAAA;AAAA,IAClD,CAAC,aAAa,UAAU;AAAA,GAC1B;AACA,EAAA,MAAM,WAAA,GAAc,WAAA,CAAY,MAAA,GAAS,CAAA,IAAK,WAAA,CAAY,KAAA,CAAM,CAAC,CAAA,KAAM,eAAA,CAAgB,GAAA,CAAI,CAAC,CAAC,CAAA;AAC7F,EAAA,MAAM,YAAA,GAAe,eAAA,CAAgB,IAAA,GAAO,CAAA,IAAK,CAAC,WAAA;AAElD,EAAA,MAAM,WAAA,GAAc,CAAC,IAAA,KAAiB,WAAA,CAAY,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,UAAA,CAAW,IAAA,GAAO,GAAG,CAAC,CAAA;AAExF,EAAA,MAAM,YAAA,GAAe,CAAC,IAAA,KAAiB;AACrC,IAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,eAAe,CAAA;AACpC,IAAA,MAAM,SAAS,CAAC,IAAA,EAAM,GAAG,WAAA,CAAY,IAAI,CAAC,CAAA;AAC1C,IAAA,IAAI,IAAA,CAAK,IAAI,IAAI,CAAA,aAAc,CAAA,IAAK,MAAA,EAAQ,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA;AAAA,SACpD,KAAA,MAAW,CAAA,IAAK,MAAA,EAAQ,IAAA,CAAK,IAAI,CAAC,CAAA;AACvC,IAAA,aAAA,CAAc,EAAE,SAAS,CAAC,GAAG,IAAI,CAAA,EAAG,MAAA,EAAQ,KAAA,CAAM,MAAA,EAAQ,CAAA;AAAA,EAC5D,CAAA;AAEA,EAAA,MAAM,mBAAmB,MAAM;AAC7B,IAAA,aAAA,CAAc,EAAE,SAAS,WAAA,GAAc,KAAK,WAAA,EAAa,MAAA,EAAQ,KAAA,CAAM,MAAA,EAAQ,CAAA;AAAA,EACjF,CAAA;AAEA,EAAA,MAAM,WAAA,GAAc,CAAC,OAAA,KAAoB;AACvC,IAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,cAAc,CAAA;AACnC,IAAA,IAAI,KAAK,GAAA,CAAI,OAAO,CAAA,EAAG,IAAA,CAAK,OAAO,OAAO,CAAA;AAAA,SACrC,IAAA,CAAK,IAAI,OAAO,CAAA;AACrB,IAAA,aAAA,CAAc,EAAE,SAAS,KAAA,CAAM,OAAA,EAAS,QAAQ,CAAC,GAAG,IAAI,CAAA,EAAG,CAAA;AAAA,EAC7D,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,eAAA;AAAA,IACA,cAAA;AAAA,IACA,WAAA;AAAA,IACA,YAAA;AAAA,IACA,cAAc,KAAA,CAAM,OAAA,CAAQ,SAAS,CAAA,IAAK,KAAA,CAAM,OAAO,MAAA,GAAS,CAAA;AAAA,IAChE,YAAA;AAAA,IACA,gBAAA;AAAA,IACA;AAAA,GACF;AACF;AC5CA,IAAM,SAAA,GAAY,CAAC,CAAA,EAAW,IAAA,KAAiB,CAAA,EAAG,CAAC,CAAA,CAAA,EAAI,IAAI,CAAA,EAAG,CAAA,KAAM,CAAA,GAAI,GAAA,GAAM,EAAE,CAAA,CAAA;AAEzE,IAAM,2BAAA,GAAiD;AAAA,EAC5D,OAAA,EAAS,SAAA;AAAA,EACT,aAAA,EAAe,QAAA;AAAA,EACf,GAAA,EAAK,KAAA;AAAA,EACL,OAAA,EAAS,SAAA;AAAA,EACT,YAAA,EAAc,gBAAA;AAAA,EACd,WAAA,EAAa,6EAAA;AAAA,EACb,cAAA,EAAgB,kBAAA;AAAA,EAChB,cAAA,EAAgB,CAAC,OAAA,EAAS,KAAA,KACxB,GAAG,OAAO,CAAA,IAAA,EAAO,WAAA,CAAY,KAAA,EAAO,EAAE,GAAA,EAAK,MAAA,EAAQ,KAAA,EAAO,OAAA,EAAS,CAAC,CAAA,CAAA;AAAA,EACtE,cAAA,EAAgB,WAAA;AAAA,EAChB,WAAA,EAAa,CAAC,KAAA,KAAU,SAAA,CAAU,OAAO,QAAQ,CAAA;AAAA,EACjD,UAAA,EAAY,CAAC,KAAA,KAAU,SAAA,CAAU,OAAO,OAAO,CAAA;AAAA,EAC/C,IAAA,EAAM,CAAC,KAAA,KAAU,CAAA,EAAG,KAAK,CAAA,KAAA,CAAA;AAAA,EACzB,UAAU,CAAC,KAAA,KAAU,OAAO,SAAA,CAAU,KAAA,EAAO,MAAM,CAAC,CAAA,CAAA;AAAA,EACpD,cAAA,EAAgB;AAClB;AAmDA,SAAS,SAAA,CAAU;AAAA,EACjB,KAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,aAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF,CAAA,EAAmB;AACjB,EAAA,uBACED,IAAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,IAAA,EAAK,QAAA;AAAA,MACL,OAAA,EAAS,QAAA;AAAA,MACT,IAAA,EAAK,UAAA;AAAA,MACL,cAAA,EAAc,gBAAgB,OAAA,GAAU,QAAA;AAAA,MACxC,OAAO,EAAE,WAAA,EAAa,GAAG,CAAA,GAAI,KAAA,GAAQ,EAAE,CAAA,EAAA,CAAA,EAAK;AAAA,MAC5C,SAAA,EAAWE,EAAAA;AAAA,QACT,mMAAA;AAAA,QACA,QAAA,IAAY,eAAA;AAAA,QACZ,KAAA,IAAS,CAAC,QAAA,IAAY;AAAA,OACxB;AAAA,MACA,WAAA,EAAU,kBAAA;AAAA,MAEV,QAAA,EAAA;AAAA,wBAAAD,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,+CAAA,EACd,QAAA,kBAAAA,GAAAA;AAAA,UAAC,MAAA;AAAA,UAAA;AAAA,YACC,SAAA,EAAWC,EAAAA;AAAA,cACT,kFAAA;AAAA,cACA,QAAA,IAAY,gBACR,mDAAA,GACA;AAAA,aACN;AAAA,YAEC,QAAA,EAAA,aAAA,mBACCD,GAAAA,CAAC,KAAA,EAAA,EAAM,SAAA,EAAU,UAAA,EAAW,CAAA,GAE5B,QAAA,oBAAYA,GAAAA,CAAC,KAAA,EAAA,EAAM,SAAA,EAAU,UAAA,EAAW;AAAA;AAAA,SAE5C,EACF,CAAA;AAAA,QACC,KAAA,KAAU,EAAA,oBACTA,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAWC,EAAAA,CAAG,kBAAA,EAAoB,QAAA,IAAY,4BAA4B,CAAA,EAC7E,QAAA,EAAA,KAAA,EACH,CAAA;AAAA,wBAEFD,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,4DAA4D,QAAA,EAAA,KAAA,EAAM;AAAA;AAAA;AAAA,GACpF;AAEJ;AAQO,SAAS,WAAA,CAAY;AAAA,EAC1B,OAAA;AAAA,EACA,YAAA;AAAA,EACA,MAAA;AAAA,EACA,UAAA;AAAA,EACA,YAAA;AAAA,EACA,KAAA;AAAA,EACA,aAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAAqB;AACnB,EAAA,MAAM,CAAA,GAAI,EAAE,GAAG,2BAAA,EAA6B,GAAG,QAAA,EAAS;AACxD,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,SAAS,KAAK,CAAA;AACpD,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAAS,KAAK,CAAA;AAElD,EAAA,MAAM,WAAA,GAAcE,OAAAA,CAAQ,MAAM,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AACvE,EAAA,MAAM,YAAY,iBAAA,CAAkB;AAAA,IAClC,WAAA;AAAA,IACA,YAAY,YAAA,GAAe,CAAA;AAAA,IAC3B,KAAA;AAAA,IACA;AAAA,GACD,CAAA;AACD,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,MAAA,GAAS,CAAA,IAAK,YAAA,GAAe,CAAA;AAErD,EAAA,uBACEH,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,qBAAA,EAAsB,aAAU,cAAA,EAC7C,QAAA,EAAA;AAAA,oBAAAC,GAAAA;AAAA,MAAC,WAAA;AAAA,MAAA;AAAA,QACC,OAAO,CAAA,CAAE,OAAA;AAAA,QACT,KAAA,EAAO,MAAM,OAAA,CAAQ,MAAA;AAAA,QACrB,IAAA,EAAM,WAAA;AAAA,QACN,YAAA,EAAc,cAAA;AAAA,QACd,QAAQ,QAAA,CAAS,OAAA,EAAS,eAAe,EAAE,MAAA,EAAQ,+BAA+B,CAAA;AAAA,QAElF,QAAA,kBAAAD,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,mEAAA,EACZ,QAAA,EAAA;AAAA,UAAA,OAAA,oBACCA,KAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,4BAAAC,GAAAA;AAAA,cAAC,SAAA;AAAA,cAAA;AAAA,gBACC,OAAO,CAAA,CAAE,GAAA;AAAA,gBACT,KAAA,EAAO,UAAA;AAAA,gBACP,KAAA,EAAO,CAAA;AAAA,gBACP,UAAU,SAAA,CAAU,WAAA;AAAA,gBACpB,eAAe,SAAA,CAAU,YAAA;AAAA,gBACzB,UAAU,SAAA,CAAU;AAAA;AAAA,aACtB;AAAA,4BACAA,GAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,6BAAA,EAA8B;AAAA,WAAA,EAC/C,CAAA;AAAA,UAED,YAAA,GAAe,CAAA,oBACdD,IAAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,4BAAAC,GAAAA;AAAA,cAAC,SAAA;AAAA,cAAA;AAAA,gBACC,OAAO,CAAA,CAAE,OAAA;AAAA,gBACT,KAAA,EAAO,YAAA;AAAA,gBACP,KAAA,EAAO,CAAA;AAAA,gBACP,QAAA,EAAU,SAAA,CAAU,eAAA,CAAgB,GAAA,CAAI,EAAE,CAAA;AAAA,gBAC1C,KAAA,EAAK,IAAA;AAAA,gBACL,QAAA,EAAU,MAAM,SAAA,CAAU,YAAA,CAAa,EAAE;AAAA;AAAA,aAC3C;AAAA,YACC,QAAQ,MAAA,GAAS,CAAA,oBAAKA,GAAAA,CAAC,KAAA,EAAA,EAAI,WAAU,6BAAA,EAA8B;AAAA,WAAA,EACtE,CAAA;AAAA,UAED,OAAA,CAAQ,GAAA,CAAI,CAAC,MAAA,qBACZA,GAAAA;AAAA,YAAC,SAAA;AAAA,YAAA;AAAA,cAEC,OAAO,MAAA,CAAO,KAAA;AAAA,cACd,OAAO,MAAA,CAAO,KAAA;AAAA,cACd,OAAO,MAAA,CAAO,KAAA;AAAA,cACd,QAAA,EAAU,SAAA,CAAU,eAAA,CAAgB,GAAA,CAAI,OAAO,IAAI,CAAA;AAAA,cACnD,QAAA,EAAU,MAAM,SAAA,CAAU,YAAA,CAAa,OAAO,IAAI;AAAA,aAAA;AAAA,YAL7C,MAAA,CAAO;AAAA,WAOf,CAAA;AAAA,UACA,CAAC,2BAAWA,GAAAA,CAAC,OAAE,SAAA,EAAU,yCAAA,EAA2C,YAAE,YAAA,EAAa;AAAA,SAAA,EACtF;AAAA;AAAA,KACF;AAAA,oBAIAA,GAAAA;AAAA,MAAC,WAAA;AAAA,MAAA;AAAA,QACC,OAAO,CAAA,CAAE,aAAA;AAAA,QACT,KAAA,EAAO,MAAM,MAAA,CAAO,MAAA;AAAA,QACpB,IAAA,EAAM,UAAA;AAAA,QACN,YAAA,EAAc,aAAA;AAAA,QACd,QAAQ,QAAA,CAAS,OAAA,EAAS,cAAc,EAAE,MAAA,EAAQ,8BAA8B,CAAA;AAAA,QAE/E,QAAA,EAAA,MAAA,CAAO,MAAA,KAAW,CAAA,mBACjBA,GAAAA;AAAA,UAAC,GAAA;AAAA,UAAA;AAAA,YACC,SAAA,EAAU,yCAAA;AAAA,YACV,eAAa,QAAA,CAAS,OAAA,EAAS,UAAU,EAAE,MAAA,EAAQ,0BAA0B,CAAA;AAAA,YAE5E,QAAA,EAAA,CAAA,CAAE;AAAA;AAAA,SACL,mBAEAA,GAAAA,CAAC,KAAA,EAAA,EAAI,WAAU,wBAAA,EACZ,QAAA,EAAA,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,KAAU;AACrB,UAAA,MAAM,QAAA,GAAW,SAAA,CAAU,cAAA,CAAe,GAAA,CAAI,MAAM,EAAE,CAAA;AACtD,UAAA,uBACED,IAAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cAEC,IAAA,EAAK,QAAA;AAAA,cACL,OAAA,EAAS,MAAM,SAAA,CAAU,WAAA,CAAY,MAAM,EAAE,CAAA;AAAA,cAC7C,cAAA,EAAc,QAAA;AAAA,cACd,aAAA,EAAa,cAAA,CAAe,OAAA,EAAS,SAAA,EAAW,MAAM,EAAA,EAAI;AAAA,gBACxD,MAAA,EAAQ,CAAA,wBAAA,EAA2B,KAAA,CAAM,EAAE,CAAA;AAAA,eAC5C,CAAA;AAAA,cACD,SAAA,EAAWE,EAAAA;AAAA,gBACT,wLAAA;AAAA,gBACA,WACI,8CAAA,GACA;AAAA,eACN;AAAA,cAEA,QAAA,EAAA;AAAA,gCAAAD,GAAAA;AAAA,kBAAC,MAAA;AAAA,kBAAA;AAAA,oBACC,SAAA,EAAU,gCAAA;AAAA,oBACV,KAAA,EAAO,EAAE,eAAA,EAAiB,KAAA,CAAM,KAAA;AAAM;AAAA,iBACxC;AAAA,gCACAA,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,UAAA,EAAY,gBAAM,IAAA,EAAK,CAAA;AAAA,gCACvCD,IAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,wCAAA,EAAyC,QAAA,EAAA;AAAA,kBAAA,GAAA;AAAA,kBAAE,KAAA,CAAM,KAAA;AAAA,kBAAM;AAAA,iBAAA,EAAC,CAAA;AAAA,gBACvE,QAAA,oBAAYC,GAAAA,CAAC,KAAA,EAAA,EAAM,WAAU,mBAAA,EAAoB;AAAA;AAAA,aAAA;AAAA,YApB7C,KAAA,CAAM;AAAA,WAqBb;AAAA,QAEJ,CAAC,CAAA,EACH;AAAA;AAAA,KAEJ;AAAA,oBAEAD,IAAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAU,yBAAA;AAAA,QACV,eAAa,QAAA,CAAS,OAAA,EAAS,SAAS,EAAE,MAAA,EAAQ,wBAAwB,CAAA;AAAA,QAE1E,QAAA,EAAA;AAAA,0BAAAA,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,0EAAA,EACb,QAAA,EAAA;AAAA,4BAAAC,GAAAA,CAAC,UAAA,EAAA,EAAW,SAAA,EAAU,QAAA,EAAS,CAAA;AAAA,YAC9B,CAAA,CAAE;AAAA,WAAA,EACL,CAAA;AAAA,UACC,UAAU,YAAA,mBACTD,IAAAA,CAAC,KAAA,EAAA,EAAI,WAAU,+BAAA,EACb,QAAA,EAAA;AAAA,4BAAAC,GAAAA,CAAC,UAAK,SAAA,EAAU,yCAAA,EACb,YAAE,cAAA,CAAe,YAAA,EAAc,UAAU,CAAA,EAC5C,CAAA;AAAA,YACC,CAAA,CAAE,cAAA;AAAA,YACF,MAAM,OAAA,CAAQ,MAAA,GAAS,CAAA,oBAAKD,KAAC,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,cAAA,UAAA;AAAA,cAAI,CAAA,CAAE,WAAA,CAAY,KAAA,CAAM,OAAA,CAAQ,MAAM;AAAA,aAAA,EAAE,CAAA;AAAA,YAC1E,KAAA,CAAM,OAAA,CAAQ,MAAA,GAAS,CAAA,IAAK,KAAA,CAAM,MAAA,CAAO,MAAA,GAAS,CAAA,oBAAKC,GAAAA,CAAC,MAAA,EAAA,EAAK,QAAA,EAAA,IAAA,EAAE,CAAA;AAAA,YAC/D,KAAA,CAAM,MAAA,CAAO,MAAA,GAAS,CAAA,oBAAKA,GAAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAA,CAAA,CAAE,UAAA,CAAW,KAAA,CAAM,MAAA,CAAO,MAAM,CAAA,EAAE,CAAA;AAAA,4BACrED,IAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,SAAA,EACb,QAAA,EAAA;AAAA,cAAA,QAAA;AAAA,cACA,CAAA,CAAE,IAAA,CAAK,UAAA,GAAa,YAAY;AAAA,aAAA,EACnC;AAAA,WAAA,EACF,CAAA,mBAEAA,IAAAA,CAAC,KAAA,EAAA,EAAI,WAAU,+BAAA,EACb,QAAA,EAAA;AAAA,4BAAAC,IAAC,MAAA,EAAA,EAAK,SAAA,EAAU,2CACb,QAAA,EAAA,CAAA,CAAE,QAAA,CAAS,UAAU,CAAA,EACxB,CAAA;AAAA,YACC,CAAA,CAAE;AAAA,WAAA,EACL;AAAA;AAAA;AAAA;AAEJ,GAAA,EACF,CAAA;AAEJ;ACnTO,SAAS,YAAA,CAAa;AAAA,EAC3B,QAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA,EAIG;AACD,EAAA,uBACEA,GAAAA;AAAA,IAAC,GAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAWC,EAAAA,CAAG,uCAAA,EAAyC,SAAS,CAAA;AAAA,MAChE,WAAA,EAAU,eAAA;AAAA,MACV,aAAA,EAAa,MAAA;AAAA,MAEZ;AAAA;AAAA,GACH;AAEJ;AAkBO,SAAS,aAAA,CAAc;AAAA,EAC5B,IAAA,EAAM,IAAA;AAAA,EACN,KAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAAuB;AACrB,EAAA,uBACEF,IAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAWE,EAAAA,CAAG,yCAAA,EAA2C,SAAS,CAAA;AAAA,MAClE,WAAA,EAAU,gBAAA;AAAA,MAEV,QAAA,EAAA;AAAA,wBAAAF,IAAAA;AAAA,UAAC,UAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAM,QAAA;AAAA,YACN,QAAA,EAAU,MAAA;AAAA,YACV,QAAA,EAAU,CAAC,MAAA,IAAU,CAAC,QAAA;AAAA,YACtB,SAAA,EAAU,sRAAA;AAAA,YACV,aAAA,EAAa,UAAA;AAAA,YAEZ,QAAA,EAAA;AAAA,cAAA,IAAA,oBAAQC,GAAAA,CAAC,IAAA,EAAA,EAAK,SAAA,EAAU,uCAAA,EAAwC,eAAY,MAAA,EAAO,CAAA;AAAA,8BACpFA,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,2DAA2D,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,cAChF,UAAU,MAAA,oBACTA,IAAC,MAAA,EAAA,EAAK,SAAA,EAAU,8CAA8C,QAAA,EAAA,KAAA,EAAM;AAAA;AAAA;AAAA,SAExE;AAAA,QACC,0BAAUA,GAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,YAAY,QAAA,EAAA,MAAA,EAAO;AAAA;AAAA;AAAA,GAC/C;AAEJ;AAWO,SAAS,UAAA,CAAW;AAAA,EACzB,MAAM,IAAA,GAAO,IAAA;AAAA,EACb,KAAA;AAAA,EACA,WAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAAoB;AAClB,EAAA,uBACED,IAAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,IAAA,EAAK,QAAA;AAAA,MACL,OAAA,EAAS,QAAA;AAAA,MACT,UAAU,CAAC,QAAA;AAAA,MACX,SAAA,EAAU,0PAAA;AAAA,MACV,WAAA,EAAU,aAAA;AAAA,MACV,aAAA,EAAa,MAAA;AAAA,MAEb,QAAA,EAAA;AAAA,wBAAAC,GAAAA,CAAC,IAAA,EAAA,EAAK,SAAA,EAAU,uCAAA,EAAwC,eAAY,MAAA,EAAO,CAAA;AAAA,wBAC3ED,IAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,gBAAA,EACd,QAAA,EAAA;AAAA,0BAAAC,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,6CAAA,EAA+C,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,UACpE,+BACCA,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,oDAAoD,QAAA,EAAA,WAAA,EAAY;AAAA,SAAA,EAEpF;AAAA;AAAA;AAAA,GACF;AAEJ;;;ACtGO,SAAS,gBAAgB,KAAA,EAAe,KAAA,EAAe,OAAA,GAAU,GAAA,EAAK,MAAM,EAAA,EAAY;AAC7F,EAAA,IAAI,KAAA,IAAS,GAAG,OAAO,CAAA;AACvB,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,KAAA,EAAO,IAAA,CAAK,KAAA,CAAA,CAAO,KAAA,GAAQ,GAAA,KAAQ,OAAA,GAAU,GAAA,CAAI,CAAC,CAAC,CAAA;AACpF,EAAA,IAAI,GAAA,GAAM,GAAG,OAAO,CAAA;AACpB,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,IAAI,YAAY,MAAA,CAAO,iBAAA;AACvB,EAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,IAAW,GAAA,EAAK,OAAA,EAAA,EAAW;AAC/C,IAAA,MAAM,KAAA,GAAA,CAAS,OAAA,GAAW,KAAA,GAAQ,OAAA,IAAY,OAAA;AAC9C,IAAA,IAAI,KAAA,GAAQ,SAAA,IAAc,KAAA,KAAU,SAAA,IAAa,UAAU,IAAA,EAAO;AAChE,MAAA,IAAA,GAAO,OAAA;AACP,MAAA,SAAA,GAAY,KAAA;AAAA,IACd;AAAA,EACF;AACA,EAAA,OAAO,IAAA;AACT;ACmBO,SAAS,WAAW,EAAE,KAAA,EAAO,SAAS,aAAA,EAAe,SAAA,EAAW,QAAO,EAAoB;AAGhG,EAAA,MAAM,OAAA,GAAU,OAAuB,IAAI,CAAA;AAC3C,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIG,SAAwB,IAAI,CAAA;AAC1D,EAAA,MAAM,QAAQ,KAAA,CAAM,MAAA;AACpB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,OAAO,OAAA,CAAQ,OAAA;AACrB,IAAA,IAAI,CAAC,IAAA,IAAQ,KAAA,KAAU,CAAA,EAAG;AAC1B,IAAA,MAAM,UAAU,MAAM,UAAA,CAAW,gBAAgB,KAAA,EAAO,IAAA,CAAK,WAAW,CAAC,CAAA;AACzE,IAAA,OAAA,EAAQ;AACR,IAAA,OAAO,aAAA,CAAc,MAAM,OAAO,CAAA;AAAA,EACpC,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAEV,EAAA,IAAI,KAAA,KAAU,GAAG,OAAO,IAAA;AACxB,EAAA,uBACEJ,IAAAA;AAAA,IAAC,SAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAWE,EAAAA,CAAG,qBAAA,EAAuB,SAAS,CAAA;AAAA,MAC9C,WAAA,EAAU,aAAA;AAAA,MACV,aAAA,EAAa,MAAA;AAAA,MAEZ,QAAA,EAAA;AAAA,QAAA,OAAA,oBAAWD,GAAAA,CAAC,YAAA,EAAA,EAAa,MAAA,EAAQ,eAAgB,QAAA,EAAA,OAAA,EAAQ,CAAA;AAAA,wBAC1DA,GAAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,GAAA,EAAK,OAAA;AAAA,YACL,WAAA,EAAU,kBAAA;AAAA,YACV,gBAAc,OAAA,IAAW,MAAA;AAAA,YACzB,SAAA,EAAU,YAAA;AAAA,YACV,KAAA,EAAO;AAAA,cACL,mBAAA,EACE,OAAA,KAAY,IAAA,GACR,sCAAA,GACA,UAAU,OAAO,CAAA,iBAAA;AAAA,aACzB;AAAA,YAEC,QAAA,EAAA,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,KAAS;AACnB,cAAA,MAAM,OAAO,IAAA,CAAK,IAAA;AAClB,cAAA,uBACED,IAAAA;AAAA,gBAAC,UAAA;AAAA,gBAAA;AAAA,kBAEC,MAAM,IAAA,CAAK,IAAA;AAAA,kBACX,UAAU,IAAA,CAAK,QAAA;AAAA,kBACf,UAAU,IAAA,CAAK,QAAA;AAAA,kBACf,KAAA,EAAO,IAAA,CAAK,QAAA,GAAW,IAAA,CAAK,cAAA,GAAiB,MAAA;AAAA,kBAC7C,SAAA,EAAWE,EAAAA;AAAA,oBACT,0EAAA;AAAA,oBACA,mCAAA;AAAA,oBACA,yEAAA;AAAA,oBACA,qEAAA;AAAA,oBACA;AAAA,mBACF;AAAA,kBACA,WAAA,EAAU,YAAA;AAAA,kBACV,eAAa,IAAA,CAAK,MAAA;AAAA,kBAEjB,QAAA,EAAA;AAAA,oBAAA,IAAA,CAAK,2BACJD,GAAAA,CAAC,UAAK,SAAA,EAAU,qCAAA,EAAsC,eAAY,MAAA,EAC/D,QAAA,EAAA,IAAA,CAAK,QAAA,EACR,CAAA,GAEA,wBAAQA,GAAAA,CAAC,QAAK,SAAA,EAAU,6BAAA,EAA8B,eAAY,MAAA,EAAO,CAAA;AAAA,oCAE3ED,IAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,SAAA,EACd,QAAA,EAAA;AAAA,sCAAAC,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,6CAAA,EAA+C,eAAK,KAAA,EAAM,CAAA;AAAA,sCAC1EA,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uDAAA,EACb,QAAA,EAAA,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,cAAA,GAAiB,IAAA,CAAK,cAAA,GAAiB,IAAA,CAAK,WAAA,EACrE;AAAA,qBAAA,EACF;AAAA;AAAA,iBAAA;AAAA,gBA3BK,IAAA,CAAK;AAAA,eA4BZ;AAAA,YAEJ,CAAC;AAAA;AAAA;AACH;AAAA;AAAA,GACF;AAEJ","file":"chunk-NJL4LIXX.js","sourcesContent":["import type { ReactNode } from 'react'\nimport { ChevronDown } from 'lucide-react'\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger, cn } from '@arbidocs/react/ui'\n\nexport interface FoldSectionProps {\n label: string\n /** Shown as plain muted text beside the name; omit or pass 0 to hide. */\n count?: number\n /** A word or two of state for sections a count cannot summarise (shown while shut). */\n hint?: string\n open: boolean\n onOpenChange: (open: boolean) => void\n /** Trigger test id; the caller owns the naming. */\n testId: string\n children: ReactNode\n}\n\n/**\n * One foldable block of a dialog. The chevron and the name are the whole hit\n * target (44px on touch), and the count keeps the selection readable while the\n * section is shut, so you need not re-open it to check.\n */\nexport function FoldSection({\n label,\n count = 0,\n hint,\n open,\n onOpenChange,\n testId,\n children,\n}: FoldSectionProps) {\n return (\n <Collapsible open={open} onOpenChange={onOpenChange} data-slot=\"fold-section\">\n <CollapsibleTrigger\n className=\"mb-1.5 flex w-full items-center gap-1.5 rounded-md text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring [&:hover]:text-muted-foreground pointer-coarse:min-h-11\"\n data-testid={testId}\n >\n <ChevronDown\n className={cn('size-4 shrink-0 transition-transform', !open && '-rotate-90')}\n />\n <span>{label}</span>\n {count > 0 && <span className=\"text-xs tabular-nums text-muted-foreground\">{count}</span>}\n {hint && !open && (\n <span className=\"min-w-0 truncate text-xs font-normal text-muted-foreground\">{hint}</span>\n )}\n </CollapsibleTrigger>\n <CollapsibleContent>{children}</CollapsibleContent>\n </Collapsible>\n )\n}\n","import type { ReactNode } from 'react'\nimport { FolderPlus } from 'lucide-react'\n\n/** Fixed strings of the empty-files state. */\nexport interface AddFilesFirstLabels {\n heading: string\n}\n\nexport const DEFAULT_ADD_FILES_FIRST_LABELS: AddFilesFirstLabels = {\n heading: 'Add files first',\n}\n\nexport interface AddFilesFirstProps {\n /** Why this action needs files, in the action's own words. */\n reason: string\n /** The host's own \"Add\" control. */\n action: ReactNode\n testId?: string\n labels?: Partial<AddFilesFirstLabels>\n}\n\n/**\n * What a quick-action dialog shows when the workspace has no files yet.\n *\n * Greying the action out says you cannot do it without saying how to get\n * there. Offering the same \"Add\" menu the Files view carries turns the dead\n * end into the first step: add files here, then run the action.\n */\nexport function AddFilesFirst({ reason, action, testId, labels }: AddFilesFirstProps) {\n const l = { ...DEFAULT_ADD_FILES_FIRST_LABELS, ...labels }\n return (\n <div\n className=\"flex flex-col items-center gap-3 rounded-lg bg-muted px-4 py-8 text-center\"\n data-slot=\"add-files-first\"\n data-testid={testId}\n >\n <span className=\"flex size-11 items-center justify-center rounded-full bg-ai-soft text-ai-ink\">\n <FolderPlus className=\"size-5\" aria-hidden=\"true\" />\n </span>\n <div className=\"flex flex-col gap-1\">\n <p className=\"text-sm font-semibold text-foreground\">{l.heading}</p>\n <p className=\"text-xs leading-relaxed text-muted-foreground\">{reason}</p>\n </div>\n {action}\n </div>\n )\n}\n","import type { ReactNode } from 'react'\nimport { DialogDescription, DialogHeader, DialogTitle } from '@arbidocs/react/ui'\n\nexport interface QuickActionDialogHeaderProps {\n /** The tile's glyph, sized by the caller at `size-5`. */\n icon: ReactNode\n title: string\n /** One sentence on what the dialog does. Keep it to a line or two. */\n description: ReactNode\n /** Test id for the description, where a test asserts on the copy. */\n descriptionTestId?: string\n}\n\n/**\n * The header every \"Start\" quick-action dialog wears: the tile's own mark in\n * its disc, the action's name, and one line saying what the dialog will do.\n *\n * - The disc centres on the TITLE row, so it reads as the title's mark.\n * - The description keeps the title's left edge (`pl-13` = disc + gap), so the\n * header is one text column beside the mark rather than an L.\n * - `max-w-prose` + `text-pretty` cap the measure and stop two-word orphans.\n * - `pr-8` keeps the copy clear of the close button.\n */\nexport function QuickActionDialogHeader({\n icon,\n title,\n description,\n descriptionTestId,\n}: QuickActionDialogHeaderProps) {\n return (\n <DialogHeader className=\"space-y-0 text-left\" data-slot=\"quick-action-dialog-header\">\n <div className=\"flex items-center gap-3 pr-8\">\n <div className=\"flex size-10 shrink-0 items-center justify-center rounded-full bg-ai-soft text-ai-ink\">\n {icon}\n </div>\n <DialogTitle className=\"text-xl font-semibold leading-tight\">{title}</DialogTitle>\n </div>\n <DialogDescription\n className=\"mt-2 max-w-prose pl-13 pr-8 text-sm leading-relaxed text-pretty\"\n data-testid={descriptionTestId}\n >\n {description}\n </DialogDescription>\n </DialogHeader>\n )\n}\n","import { useMemo } from 'react'\n\n/** Which folders and labels a run is limited to. Empty = everything. */\nexport interface DocumentScope {\n /** Folder paths; `''` is the \"unfiled\" bucket. */\n folders: string[]\n /** Label (tag) ids. */\n labels: string[]\n}\n\nexport interface UseScopeSelectionOptions {\n /** Every folder path in the tree (not including the unfiled bucket). */\n folderPaths: string[]\n /** Whether the unfiled bucket is shown as a row. */\n hasUnfiled: boolean\n scope: DocumentScope\n onScopeChange: (scope: DocumentScope) => void\n}\n\n/**\n * Headless selection logic for a folder + label scope picker.\n *\n * Folders use hierarchical selection: ticking \"contracts\" also ticks\n * \"contracts/2024\", and unticking removes the whole subtree. \"All\" ticks every\n * row, which is NOT the same as an empty scope: empty means \"the whole\n * workspace, whatever lands in it later\", all-ticked means \"these folders, as\n * they are now\".\n */\nexport function useScopeSelection({\n folderPaths,\n hasUnfiled,\n scope,\n onScopeChange,\n}: UseScopeSelectionOptions) {\n const selectedFolders = useMemo(() => new Set(scope.folders), [scope.folders])\n const selectedLabels = useMemo(() => new Set(scope.labels), [scope.labels])\n\n /** Every row the folder list shows, \"unfiled\" included. */\n const allRowPaths = useMemo(\n () => [...(hasUnfiled ? [''] : []), ...folderPaths],\n [folderPaths, hasUnfiled]\n )\n const allSelected = allRowPaths.length > 0 && allRowPaths.every((p) => selectedFolders.has(p))\n const someSelected = selectedFolders.size > 0 && !allSelected\n\n const descendants = (path: string) => folderPaths.filter((p) => p.startsWith(path + '/'))\n\n const toggleFolder = (path: string) => {\n const next = new Set(selectedFolders)\n const family = [path, ...descendants(path)]\n if (next.has(path)) for (const p of family) next.delete(p)\n else for (const p of family) next.add(p)\n onScopeChange({ folders: [...next], labels: scope.labels })\n }\n\n const toggleAllFolders = () => {\n onScopeChange({ folders: allSelected ? [] : allRowPaths, labels: scope.labels })\n }\n\n const toggleLabel = (labelId: string) => {\n const next = new Set(selectedLabels)\n if (next.has(labelId)) next.delete(labelId)\n else next.add(labelId)\n onScopeChange({ folders: scope.folders, labels: [...next] })\n }\n\n return {\n selectedFolders,\n selectedLabels,\n allSelected,\n someSelected,\n hasSelection: scope.folders.length > 0 || scope.labels.length > 0,\n toggleFolder,\n toggleAllFolders,\n toggleLabel,\n }\n}\n","import { useMemo, useState } from 'react'\nimport { Check, FileCheck2, Minus } from 'lucide-react'\nimport { cn } from '@arbidocs/react/ui'\nimport { prefixedTestId, testIdOr } from '../chat/test-id'\nimport { useScopeSelection, type DocumentScope } from '../lib/useScopeSelection'\nimport { formatCount } from '../lib/format'\nimport { FoldSection } from './FoldSection'\n\n/**\n * Fixed strings of the picker. Passed as `uiLabels`: the `labels` prop is already the list of\n * document labels to scope by.\n */\nexport interface ScopePickerLabels {\n folders: string\n /** The Labels section heading. */\n labelsHeading: string\n all: string\n unfiled: string\n noFoldersYet: string\n noLabelsYet: string\n filesToInclude: string\n /** \"3 of 10 files\". */\n matchedOfTotal: (matched: number, total: number) => string\n selectedSuffix: string\n folderCount: (count: number) => string\n labelCount: (count: number) => string\n left: (count: number) => string\n /** \"All 10 files\". */\n allFiles: (total: number) => string\n includedSuffix: string\n}\n\nconst pluralize = (n: number, word: string) => `${n} ${word}${n !== 1 ? 's' : ''}`\n\nexport const DEFAULT_SCOPE_PICKER_LABELS: ScopePickerLabels = {\n folders: 'Folders',\n labelsHeading: 'Labels',\n all: 'All',\n unfiled: 'Unfiled',\n noFoldersYet: 'No folders yet',\n noLabelsYet: 'No labels yet — label files from the Files page to scope by them here.',\n filesToInclude: 'Files to include',\n matchedOfTotal: (matched, total) =>\n `${matched} of ${formatCount(total, { one: 'file', other: 'files' })}`,\n selectedSuffix: ' selected',\n folderCount: (count) => pluralize(count, 'folder'),\n labelCount: (count) => pluralize(count, 'label'),\n left: (count) => `${count} left`,\n allFiles: (total) => `All ${pluralize(total, 'file')}`,\n includedSuffix: ' in the workspace will be included',\n}\n\nexport interface ScopeFolder {\n /** Full path, e.g. `contracts/2024`. */\n path: string\n /** Last segment, shown. */\n label: string\n /** Documents under this folder. */\n count: number\n depth: number\n}\n\nexport interface ScopeLabel {\n id: string\n name: string\n /** Any CSS colour for the dot. */\n color: string\n /** Documents carrying this label. */\n count: number\n}\n\nexport interface ScopePickerProps {\n folders: ScopeFolder[]\n /** Documents with no folder; the \"Unfiled\" row is hidden at 0. */\n unfiledCount: number\n labels: ScopeLabel[]\n /** All documents the picker is scoping. */\n totalCount: number\n /** Documents the current scope matches (computed by the caller). */\n matchedCount: number\n scope: DocumentScope\n onScopeChange: (scope: DocumentScope) => void\n /** Reword the picker's fixed strings. */\n uiLabels?: Partial<ScopePickerLabels>\n /** Sub-element ids; defaults are the current ids. `labelChip` is a prefix (`<prefix>-<label id>`). */\n testIds?: Partial<\n Record<'foldersToggle' | 'labelsToggle' | 'noLabels' | 'labelChip' | 'summary', string>\n >\n}\n\ninterface FolderRowProps {\n label: string\n count: number\n depth: number\n isActive: boolean\n indeterminate?: boolean\n muted?: boolean\n onToggle: () => void\n}\n\n/** A checkbox row: the row is the checkbox (a nested real one would swallow the click). */\nfunction FolderRow({\n label,\n count,\n depth,\n isActive,\n indeterminate,\n muted,\n onToggle,\n}: FolderRowProps) {\n return (\n <button\n type=\"button\"\n onClick={onToggle}\n role=\"checkbox\"\n aria-checked={indeterminate ? 'mixed' : isActive}\n style={{ paddingLeft: `${4 + depth * 14}px` }}\n className={cn(\n 'flex w-full items-center gap-1 rounded-sm py-1 pr-2 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring [&:hover]:bg-accent pointer-coarse:min-h-11',\n isActive && 'bg-primary/10',\n muted && !isActive && 'text-muted-foreground'\n )}\n data-slot=\"scope-folder-row\"\n >\n <span className=\"flex w-4 shrink-0 items-center justify-center\">\n <span\n className={cn(\n 'flex size-3.5 items-center justify-center rounded-[3px] border transition-colors',\n isActive || indeterminate\n ? 'border-primary bg-primary text-primary-foreground'\n : 'border-muted-foreground/40'\n )}\n >\n {indeterminate ? (\n <Minus className=\"size-2.5\" />\n ) : (\n isActive && <Check className=\"size-2.5\" />\n )}\n </span>\n </span>\n {label !== '' && (\n <span className={cn('min-w-0 truncate', isActive && 'font-semibold text-primary')}>\n {label}\n </span>\n )}\n <span className=\"ml-auto pl-2 text-sm font-semibold text-muted-foreground\">{count}</span>\n </button>\n )\n}\n\n/**\n * Pick a folder + label scope for a run over documents. Controlled: the caller\n * owns the scope and computes `matchedCount`, so the submit button lives with\n * the caller. Both sections start shut: the default (every file) is what most\n * runs want, and the summary states it plainly.\n */\nexport function ScopePicker({\n folders,\n unfiledCount,\n labels,\n totalCount,\n matchedCount,\n scope,\n onScopeChange,\n uiLabels,\n testIds,\n}: ScopePickerProps) {\n const l = { ...DEFAULT_SCOPE_PICKER_LABELS, ...uiLabels }\n const [foldersOpen, setFoldersOpen] = useState(false)\n const [labelsOpen, setLabelsOpen] = useState(false)\n\n const folderPaths = useMemo(() => folders.map((f) => f.path), [folders])\n const selection = useScopeSelection({\n folderPaths,\n hasUnfiled: unfiledCount > 0,\n scope,\n onScopeChange,\n })\n const hasRows = folders.length > 0 || unfiledCount > 0\n\n return (\n <div className=\"flex flex-col gap-4\" data-slot=\"scope-picker\">\n <FoldSection\n label={l.folders}\n count={scope.folders.length}\n open={foldersOpen}\n onOpenChange={setFoldersOpen}\n testId={testIdOr(testIds?.foldersToggle, { testId: 'scope-picker-folders-toggle' })}\n >\n <div className=\"max-h-[180px] overflow-y-auto rounded-md border border-border p-1\">\n {hasRows && (\n <>\n <FolderRow\n label={l.all}\n count={totalCount}\n depth={0}\n isActive={selection.allSelected}\n indeterminate={selection.someSelected}\n onToggle={selection.toggleAllFolders}\n />\n <div className=\"my-1 border-t border-border\" />\n </>\n )}\n {unfiledCount > 0 && (\n <>\n <FolderRow\n label={l.unfiled}\n count={unfiledCount}\n depth={0}\n isActive={selection.selectedFolders.has('')}\n muted\n onToggle={() => selection.toggleFolder('')}\n />\n {folders.length > 0 && <div className=\"my-1 border-t border-border\" />}\n </>\n )}\n {folders.map((folder) => (\n <FolderRow\n key={folder.path}\n label={folder.label}\n count={folder.count}\n depth={folder.depth}\n isActive={selection.selectedFolders.has(folder.path)}\n onToggle={() => selection.toggleFolder(folder.path)}\n />\n ))}\n {!hasRows && <p className=\"px-2 py-1 text-sm text-muted-foreground\">{l.noFoldersYet}</p>}\n </div>\n </FoldSection>\n\n {/* Always present, like Folders: a workspace with no labels yet reads as\n \"you have not labelled anything\", not \"this cannot scope by label\". */}\n <FoldSection\n label={l.labelsHeading}\n count={scope.labels.length}\n open={labelsOpen}\n onOpenChange={setLabelsOpen}\n testId={testIdOr(testIds?.labelsToggle, { testId: 'scope-picker-labels-toggle' })}\n >\n {labels.length === 0 ? (\n <p\n className=\"px-2 py-1 text-sm text-muted-foreground\"\n data-testid={testIdOr(testIds?.noLabels, { testId: 'scope-picker-no-labels' })}\n >\n {l.noLabelsYet}\n </p>\n ) : (\n <div className=\"flex flex-wrap gap-1.5\">\n {labels.map((label) => {\n const isActive = selection.selectedLabels.has(label.id)\n return (\n <button\n key={label.id}\n type=\"button\"\n onClick={() => selection.toggleLabel(label.id)}\n aria-pressed={isActive}\n data-testid={prefixedTestId(testIds?.labelChip, label.id, {\n testId: `scope-picker-label-chip-${label.id}`,\n })}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-md border px-2 py-1 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring pointer-coarse:min-h-11',\n isActive\n ? 'border-primary/40 bg-primary/10 text-primary'\n : 'border-border [&:hover]:bg-accent [&:hover]:text-accent-foreground'\n )}\n >\n <span\n className=\"size-2.5 shrink-0 rounded-full\"\n style={{ backgroundColor: label.color }}\n />\n <span className=\"truncate\">{label.name}</span>\n <span className=\"shrink-0 text-xs text-muted-foreground\">({label.count})</span>\n {isActive && <Check className=\"size-3.5 shrink-0\" />}\n </button>\n )\n })}\n </div>\n )}\n </FoldSection>\n\n <div\n className=\"rounded-lg bg-muted p-3\"\n data-testid={testIdOr(testIds?.summary, { testId: 'scope-picker-summary' })}\n >\n <div className=\"mb-2 flex items-center gap-1.5 text-xs font-medium text-muted-foreground\">\n <FileCheck2 className=\"size-4\" />\n {l.filesToInclude}\n </div>\n {selection.hasSelection ? (\n <div className=\"text-sm text-muted-foreground\">\n <span className=\"text-base font-semibold text-foreground\">\n {l.matchedOfTotal(matchedCount, totalCount)}\n </span>\n {l.selectedSuffix}\n {scope.folders.length > 0 && <span> — {l.folderCount(scope.folders.length)}</span>}\n {scope.folders.length > 0 && scope.labels.length > 0 && <span>, </span>}\n {scope.labels.length > 0 && <span>{l.labelCount(scope.labels.length)}</span>}\n <span className=\"text-xs\">\n {' · '}\n {l.left(totalCount - matchedCount)}\n </span>\n </div>\n ) : (\n <div className=\"text-sm text-muted-foreground\">\n <span className=\"text-base font-semibold text-foreground\">\n {l.allFiles(totalCount)}\n </span>\n {l.includedSuffix}\n </div>\n )}\n </div>\n </div>\n )\n}\n","import type { ReactNode } from 'react'\nimport { Plus, type LucideIcon } from 'lucide-react'\nimport { cn } from '@arbidocs/react/ui'\nimport { ActionLink } from '../components/ActionLink'\n\n/** The small label that names a band of a page (\"Start\", \"Recent\"). */\nexport function SectionLabel({\n children,\n className,\n testId,\n}: {\n children: ReactNode\n className?: string\n testId?: string\n}) {\n return (\n <p\n className={cn('text-sm font-semibold text-foreground', className)}\n data-slot=\"section-label\"\n data-testid={testId}\n >\n {children}\n </p>\n )\n}\n\nexport interface SectionHeaderProps {\n icon?: LucideIcon\n title: string\n count?: number\n /** Makes the title a button (open the full list). Omit for a static title. */\n onOpen?: () => void\n /** Real link target of the title; `onOpen` still fires on a plain click. */\n openHref?: string\n /** Test id of the title button. */\n openTestId?: string\n /** The section's one quiet action, rendered at the trailing edge. */\n action?: ReactNode\n className?: string\n}\n\n/** Title + count + one quiet action: the head of a list section. */\nexport function SectionHeader({\n icon: Icon,\n title,\n count,\n onOpen,\n openHref,\n openTestId,\n action,\n className,\n}: SectionHeaderProps) {\n return (\n <div\n className={cn('flex items-center justify-between gap-2', className)}\n data-slot=\"section-header\"\n >\n <ActionLink\n href={openHref}\n onSelect={onOpen}\n disabled={!onOpen && !openHref}\n className=\"flex min-w-0 cursor-pointer items-center gap-2 rounded-md py-1.5 pr-2 text-left transition-colors hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-default disabled:hover:text-foreground pointer-coarse:min-h-11\"\n data-testid={openTestId}\n >\n {Icon && <Icon className=\"size-4 shrink-0 text-muted-foreground\" aria-hidden=\"true\" />}\n <span className=\"whitespace-nowrap text-sm font-semibold text-foreground\">{title}</span>\n {count !== undefined && (\n <span className=\"text-sm tabular-nums text-muted-foreground\">{count}</span>\n )}\n </ActionLink>\n {action && <div className=\"shrink-0\">{action}</div>}\n </div>\n )\n}\n\nexport interface EmptyStripProps {\n icon?: LucideIcon\n label: ReactNode\n description?: ReactNode\n onSelect?: () => void\n testId?: string\n}\n\n/** One tonal strip that stands in for an empty section: an invitation, not a void. */\nexport function EmptyStrip({\n icon: Icon = Plus,\n label,\n description,\n onSelect,\n testId,\n}: EmptyStripProps) {\n return (\n <button\n type=\"button\"\n onClick={onSelect}\n disabled={!onSelect}\n className=\"flex w-full items-center gap-3 rounded-lg bg-muted px-4 py-3 text-left transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-default disabled:hover:bg-muted pointer-coarse:min-h-11\"\n data-slot=\"empty-strip\"\n data-testid={testId}\n >\n <Icon className=\"size-5 shrink-0 text-muted-foreground\" aria-hidden=\"true\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"block text-sm font-semibold text-foreground\">{label}</span>\n {description && (\n <span className=\"block text-sm leading-snug text-muted-foreground\">{description}</span>\n )}\n </span>\n </button>\n )\n}\n","/**\n * The column count for a grid of `count` equal tiles in `width` pixels, chosen so the last row is\n * never left with a lone orphan: of the counts that fit (each column at least `minTile` wide),\n * take the one that leaves the fewest empty cells in the last row, preferring more columns on a\n * tie (never fewer than two columns when two fit). Four tiles resolve to 2x2 where three would fit\n * and 4 across when there is room for four; six resolve to 3x2.\n */\nexport function balancedColumns(count: number, width: number, minTile = 200, gap = 12): number {\n if (count <= 1) return 1\n const fit = Math.max(1, Math.min(count, Math.floor((width + gap) / (minTile + gap))))\n if (fit < 2) return 1\n let best = 2\n let bestEmpty = Number.POSITIVE_INFINITY\n for (let columns = 2; columns <= fit; columns++) {\n const empty = (columns - (count % columns)) % columns\n if (empty < bestEmpty || (empty === bestEmpty && columns > best)) {\n best = columns\n bestEmpty = empty\n }\n }\n return best\n}\n","import { useEffect, useRef, useState, type ReactNode } from 'react'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@arbidocs/react/ui'\nimport { ActionLink } from '../components/ActionLink'\nimport { balancedColumns } from '../lib/balanced-columns'\nimport { observeResize } from '../lib/observe-resize'\nimport { SectionLabel } from './sections'\n\n/** One \"start something\" tile: an AI-driven action, one click from underway. */\nexport interface StartTile {\n id: string\n /** A Lucide glyph; `iconNode` wins when both are given. */\n icon?: LucideIcon\n /** A ready-made mark (host icon maps), wearing the same AI ink. */\n iconNode?: ReactNode\n title: string\n description: string\n onSelect: () => void\n /** Real link target (ctrl-click / new tab, in-SPA through `BlocksProvider`'s `linkComponent`). `onSelect` still fires on a plain click. */\n href?: string\n /** Rendered non-interactive and dimmed; `disabledReason` replaces the description. */\n disabled?: boolean\n disabledReason?: string\n testId?: string\n}\n\nexport interface StartTilesProps {\n items: StartTile[]\n /** Optional band label above the tiles (\"Start\"). */\n caption?: string\n captionTestId?: string\n className?: string\n testId?: string\n}\n\n/**\n * A band of tonal action tiles. The glyph wears the AI ink, because these are\n * the AI-driven starts. Nothing is hover-only: tiles are always rendered, hover\n * only nudges tone, and touch targets are at least 44px.\n */\nexport function StartTiles({ items, caption, captionTestId, className, testId }: StartTilesProps) {\n // The tiles reflow by their own container width (a docked tablet sidebar narrows it without\n // changing the viewport), choosing a column count that never leaves one tile alone on a row.\n const gridRef = useRef<HTMLDivElement>(null)\n const [columns, setColumns] = useState<number | null>(null)\n const count = items.length\n useEffect(() => {\n const grid = gridRef.current\n if (!grid || count === 0) return\n const measure = () => setColumns(balancedColumns(count, grid.clientWidth))\n measure()\n return observeResize(grid, measure)\n }, [count])\n\n if (count === 0) return null\n return (\n <section\n className={cn('flex flex-col gap-3', className)}\n data-slot=\"start-tiles\"\n data-testid={testId}\n >\n {caption && <SectionLabel testId={captionTestId}>{caption}</SectionLabel>}\n <div\n ref={gridRef}\n data-slot=\"start-tiles-grid\"\n data-columns={columns ?? undefined}\n className=\"grid gap-3\"\n style={{\n gridTemplateColumns:\n columns === null\n ? 'repeat(auto-fit, minmax(200px, 1fr))'\n : `repeat(${columns}, minmax(0, 1fr))`,\n }}\n >\n {items.map((item) => {\n const Icon = item.icon\n return (\n <ActionLink\n key={item.id}\n href={item.href}\n onSelect={item.onSelect}\n disabled={item.disabled}\n title={item.disabled ? item.disabledReason : undefined}\n className={cn(\n 'flex h-full flex-col items-start gap-3 rounded-lg bg-muted p-4 text-left',\n 'transition-colors hover:bg-accent',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n 'disabled:cursor-default disabled:opacity-55 disabled:hover:bg-muted',\n 'pointer-coarse:min-h-11'\n )}\n data-slot=\"start-tile\"\n data-testid={item.testId}\n >\n {item.iconNode ? (\n <span className=\"shrink-0 text-ai-ink [&>svg]:size-5\" aria-hidden=\"true\">\n {item.iconNode}\n </span>\n ) : (\n Icon && <Icon className=\"size-5 shrink-0 text-ai-ink\" aria-hidden=\"true\" />\n )}\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-semibold text-foreground\">{item.title}</span>\n <span className=\"mt-1 block text-sm leading-snug text-muted-foreground\">\n {item.disabled && item.disabledReason ? item.disabledReason : item.description}\n </span>\n </span>\n </ActionLink>\n )\n })}\n </div>\n </section>\n )\n}\n"]}