@backmatter/studio 0.0.17 → 0.0.18

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.
@@ -1,27 +1,23 @@
1
1
  // packages/studio/src/pages/CodeSessionPage.tsx
2
- import { useCallback, useEffect, useRef, useState, type FormEvent } from "react";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
3
  import type { RouteModel } from "@backmatter/core";
4
4
  import { useOutletContext } from "react-router";
5
5
  import * as stylex from "@stylexjs/stylex";
6
- import { Combobox } from "@base-ui/react/combobox";
7
6
  import {
8
- IconArrowRotateClockwise,
9
- IconArrowUpRight,
10
7
  IconCheckmark1Small,
11
8
  IconCrossSmall,
12
9
  IconEyeOpen,
13
10
  IconFiles,
14
- IconGlobe,
15
11
  } from "@central-icons-react/round-outlined-radius-2-stroke-1.5";
16
12
  import { ChatPanel } from "../chat/ChatPanel";
17
13
  import { ChatProvider, ChatSessionScope, useChatBusy } from "../chat/ChatProvider";
18
14
  import { useReplicaState } from "../replica/useReplica";
15
+ import { routesForContentFiles } from "../content-routes";
19
16
  import { colors, radius, space, type } from "../styles/tokens.stylex";
20
17
  import { apiCredentials, apiHeaders, apiUrl } from "../studio-config";
21
18
  import { Text } from "../ui/Text";
22
- import { cx, popup, popupClassName } from "../ui/popup";
23
19
  import type { WorkspaceOutletContext } from "../workspace/types";
24
- import { bootSteps, hostPath, type BootStep, type HostPhase } from "./code-session-boot";
20
+ import { bootSteps, type BootStep, type HostPhase } from "./code-session-boot";
25
21
  import {
26
22
  frameAddress,
27
23
  framedLocation,
@@ -30,7 +26,7 @@ import {
30
26
  previewUrl,
31
27
  type HostPreview,
32
28
  } from "./code-session-frame";
33
- import { contentRoutes, matchingRoutes } from "./code-session-routes";
29
+ import { PreviewAddress } from "./PreviewAddress";
34
30
  import { CodeSessionFiles, type FilesView } from "./CodeSessionFiles";
35
31
  import { PreviewSizeSwitch, PreviewViewport } from "./PreviewSize";
36
32
  import { persistPreviewSize, readPreviewSize, type PreviewSize } from "./preview-size";
@@ -194,7 +190,7 @@ export function CodeSessionScreen({
194
190
  </TabButton>
195
191
  </div>
196
192
 
197
- <AddressBar
193
+ <PreviewAddress
198
194
  path={location}
199
195
  enabled={frameSrc !== null && tab === "preview"}
200
196
  routes={routes}
@@ -324,170 +320,6 @@ function TabButton({
324
320
  );
325
321
  }
326
322
 
327
- function AddressBar({
328
- path,
329
- enabled,
330
- routes,
331
- routesPending,
332
- onOpenRoutes,
333
- onPath,
334
- onReload,
335
- onOpen,
336
- }: {
337
- path: string;
338
- enabled: boolean;
339
- routes: readonly string[];
340
- routesPending: boolean;
341
- onOpenRoutes?: () => void;
342
- onPath: (path: string) => void;
343
- onReload: () => void;
344
- onOpen: () => void;
345
- }) {
346
- const [query, setQuery] = useState<string | null>(null);
347
- const [open, setOpen] = useState(false);
348
- const picked = useRef(false);
349
- const fieldRef = useRef<HTMLButtonElement>(null);
350
- const inputRef = useRef<HTMLInputElement>(null);
351
- const shown = query ? matchingRoutes(routes, query) : [...routes];
352
-
353
- useEffect(() => {
354
- if (!open) return;
355
- const input = inputRef.current;
356
- input?.focus();
357
- input?.select();
358
- }, [open]);
359
-
360
- return (
361
- <div {...stylex.props(styles.address)}>
362
- <button
363
- type="button"
364
- aria-label="Reload preview"
365
- disabled={!enabled}
366
- onClick={onReload}
367
- {...stylex.props(styles.iconButton)}
368
- >
369
- <IconArrowRotateClockwise size={14} ariaHidden />
370
- </button>
371
- <Combobox.Root
372
- items={shown}
373
- filteredItems={shown}
374
- inputValue={query ?? ""}
375
- disabled={!enabled}
376
- open={open}
377
- value={routes.includes(path) ? path : null}
378
- onOpenChange={(next) => {
379
- if (!enabled && next) return;
380
- setOpen(next);
381
- if (next) onOpenRoutes?.();
382
- else setQuery(null);
383
- }}
384
- onInputValueChange={(value, details) => {
385
- if (details.reason === "input-change" || details.reason === "input-clear") {
386
- setQuery(value);
387
- }
388
- }}
389
- onValueChange={(value) => {
390
- if (typeof value !== "string") return;
391
- picked.current = true;
392
- setQuery(null);
393
- onPath(hostPath(value));
394
- queueMicrotask(() => {
395
- picked.current = false;
396
- });
397
- }}
398
- >
399
- <Combobox.Trigger
400
- ref={fieldRef}
401
- aria-label="Preview path"
402
- disabled={!enabled}
403
- data-no-ring
404
- className={cx(styles.addressTrigger)}
405
- >
406
- <IconGlobe size={13} ariaHidden />
407
- <span {...stylex.props(styles.addressPath)}>{path}</span>
408
- </Combobox.Trigger>
409
- <Combobox.Portal>
410
- <Combobox.Positioner
411
- anchor={fieldRef}
412
- side="bottom"
413
- align="start"
414
- sideOffset={6}
415
- className={cx(popup.positioner)}
416
- >
417
- <Combobox.Popup
418
- aria-label="Pages"
419
- className={(state) => popupClassName(state, styles.routePopup)}
420
- >
421
- <form
422
- onSubmit={(event: FormEvent<HTMLFormElement>) => {
423
- event.preventDefault();
424
- const fromList = picked.current;
425
- picked.current = false;
426
- if (fromList) return;
427
- const typed = event.currentTarget.querySelector("input")?.value;
428
- if (typed?.trim()) onPath(hostPath(typed));
429
- setOpen(false);
430
- setQuery(null);
431
- }}
432
- >
433
- <Combobox.Input
434
- ref={inputRef}
435
- placeholder="Find a page"
436
- aria-label="Find a page"
437
- spellCheck={false}
438
- autoComplete="off"
439
- data-no-ring
440
- className={cx(popup.quietInput, styles.routeSearch)}
441
- onKeyDown={(event) => {
442
- if (event.key !== "Enter") return;
443
- // A highlighted row is chosen by the combobox. A bare Enter goes to the typed path.
444
- if (event.currentTarget.getAttribute("aria-activedescendant")) return;
445
- const typed = event.currentTarget.value.trim();
446
- if (!typed) return;
447
- picked.current = true;
448
- onPath(hostPath(typed));
449
- }}
450
- />
451
- </form>
452
- <div {...stylex.props(popup.separator)} />
453
- <Combobox.Empty className={cx(popup.empty)}>
454
- {routesPending ? "Loading pages" : "No pages"}
455
- </Combobox.Empty>
456
- <Combobox.List className={cx(styles.routeList)}>
457
- {(route: string) => (
458
- <Combobox.Item
459
- key={route}
460
- value={route}
461
- className={(state) =>
462
- cx(
463
- popup.item,
464
- styles.routeItem,
465
- state.highlighted && popup.itemHighlighted,
466
- state.selected && popup.itemSelected,
467
- )
468
- }
469
- >
470
- {route}
471
- </Combobox.Item>
472
- )}
473
- </Combobox.List>
474
- </Combobox.Popup>
475
- </Combobox.Positioner>
476
- </Combobox.Portal>
477
- </Combobox.Root>
478
- <button
479
- type="button"
480
- aria-label="Open preview in a new tab"
481
- disabled={!enabled}
482
- onClick={onOpen}
483
- {...stylex.props(styles.iconButton)}
484
- >
485
- <IconArrowUpRight size={14} ariaHidden />
486
- </button>
487
- </div>
488
- );
489
- }
490
-
491
323
  function BootCard({
492
324
  title,
493
325
  steps,
@@ -920,7 +752,7 @@ export default function CodeSessionPage() {
920
752
  live={live}
921
753
  onReload={() => navigate(location)}
922
754
  onOpen={() => void openInNewTab()}
923
- routes={contentRoutes(models, fileList ?? [])}
755
+ routes={routesForContentFiles(models, fileList ?? [])}
924
756
  routesPending={routesPending}
925
757
  onOpenRoutes={openRoutes}
926
758
  files={{
@@ -1161,76 +993,9 @@ const styles = stylex.create({
1161
993
  backgroundColor: colors.bgSurface,
1162
994
  boxShadow: "0 1px 2px rgba(0, 0, 0, 0.08)",
1163
995
  },
1164
- address: {
1165
- display: "flex",
1166
- alignItems: "center",
1167
- gap: space.xs,
1168
- minWidth: 0,
1169
- },
1170
996
  sizeSwitch: {
1171
997
  justifySelf: "end",
1172
998
  },
1173
- addressTrigger: {
1174
- display: "flex",
1175
- flex: 1,
1176
- alignItems: "center",
1177
- gap: space.xs,
1178
- minWidth: 0,
1179
- height: "28px",
1180
- paddingInline: space.sm,
1181
- borderWidth: 0,
1182
- borderRadius: "999px",
1183
- font: "inherit",
1184
- textAlign: "left",
1185
- cursor: { default: "pointer", ":disabled": "default" },
1186
- color: { default: colors.textMuted, ":disabled": colors.textMuted },
1187
- backgroundColor: colors.bgPanelInset,
1188
- },
1189
- addressPath: {
1190
- flex: 1,
1191
- minWidth: 0,
1192
- overflow: "hidden",
1193
- textOverflow: "ellipsis",
1194
- whiteSpace: "nowrap",
1195
- fontFamily: type.mono,
1196
- fontSize: type.xs,
1197
- color: colors.text,
1198
- },
1199
- routeSearch: {
1200
- fontFamily: type.mono,
1201
- fontSize: type.xs,
1202
- },
1203
- routePopup: {
1204
- width: "var(--anchor-width)",
1205
- minWidth: "220px",
1206
- maxHeight: "min(320px, var(--available-height))",
1207
- display: "flex",
1208
- flexDirection: "column",
1209
- overflowY: "hidden",
1210
- },
1211
- routeList: {
1212
- minHeight: 0,
1213
- overflowY: "auto",
1214
- },
1215
- routeItem: {
1216
- fontFamily: type.mono,
1217
- },
1218
- iconButton: {
1219
- display: "inline-flex",
1220
- alignItems: "center",
1221
- justifyContent: "center",
1222
- flexShrink: 0,
1223
- width: "28px",
1224
- height: "28px",
1225
- borderWidth: 0,
1226
- borderRadius: radius.sm,
1227
- cursor: { default: "pointer", ":disabled": "default" },
1228
- color: { default: colors.textMuted, ":disabled": colors.border },
1229
- backgroundColor: {
1230
- default: "transparent",
1231
- [hover]: { default: null, ":hover:not(:disabled)": colors.bgHover },
1232
- },
1233
- },
1234
999
  canvas: {
1235
1000
  position: "relative",
1236
1001
  display: "flex",
@@ -3,6 +3,7 @@ import { Snake } from "loading-dev";
3
3
  import { useEffect, useMemo, useRef, useState } from "react";
4
4
  import { useOutletContext, useParams, useSearchParams, Link, useNavigate } from "react-router";
5
5
  import * as stylex from "@stylexjs/stylex";
6
+ import { matchRoute, repoPathFromModel } from "@backmatter/core";
6
7
  import {
7
8
  fetchDoc,
8
9
  fetchDocs,
@@ -21,6 +22,7 @@ import {
21
22
  import { FieldControl } from "../fields/FieldControl";
22
23
  import { loadRelationDocuments } from "../fields/load-relation-documents";
23
24
  import { omitField } from "../fields/omit-field";
25
+ import { linkDocumentTypes } from "../fields/link-targets";
24
26
  import { relationTargets } from "../fields/relation-targets";
25
27
  import { useReplicaState } from "../replica/useReplica";
26
28
  import { useSetEditorActions } from "../chrome/EditorActionsContext";
@@ -34,7 +36,6 @@ import {
34
36
  } from "../chrome/EditorFocusContext";
35
37
  import { useSetEditorStatus } from "../chrome/EditorStatusContext";
36
38
  import { modifierSymbol } from "../chrome/hotkeys";
37
- import { ReloadIcon } from "../chrome/icons";
38
39
  import { colors, radius, space, type } from "../styles/tokens.stylex";
39
40
  import { Button, ButtonLink } from "../ui/Button";
40
41
  import { ConfirmDialog } from "../ui/ConfirmDialog";
@@ -50,6 +51,8 @@ import {
50
51
  persistPreviewPct,
51
52
  readPreviewPct,
52
53
  } from "./preview-split";
54
+ import { routesForContentFiles } from "../content-routes";
55
+ import { PreviewAddress } from "./PreviewAddress";
53
56
  import { PreviewSizeSwitch, PreviewViewport } from "./PreviewSize";
54
57
  import { persistPreviewSize, readPreviewSize, type PreviewSize } from "./preview-size";
55
58
  import {
@@ -82,10 +85,6 @@ import {
82
85
  const AUTOSAVE_MS = 400;
83
86
  const hover = "@media (hover: hover)";
84
87
 
85
- const spin = stylex.keyframes({
86
- to: { transform: "rotate(360deg)" },
87
- });
88
-
89
88
  const pulse = stylex.keyframes({
90
89
  "0%": { opacity: 0.4 },
91
90
  "50%": { opacity: 1 },
@@ -183,6 +182,9 @@ export function DocEditorPage() {
183
182
  for (const type of relationTargets(fields, models, objects)) {
184
183
  targets.add(type);
185
184
  }
185
+ for (const type of linkDocumentTypes(fields, models, objects)) {
186
+ targets.add(type);
187
+ }
186
188
  }
187
189
  return [...targets].sort();
188
190
  }, [model, blocks, richtext, models, objects]);
@@ -246,6 +248,9 @@ export function DocEditorPage() {
246
248
  editEpochRef.current = editEpoch;
247
249
 
248
250
  const [previewUrl, setPreviewUrl] = useState<string | null>(null);
251
+ const docIdentity = `${type}\0${slug}`;
252
+ const [browse, setBrowse] = useState<{ doc: string; path: string } | null>(null);
253
+ const browsedPath = browse?.doc === docIdentity ? browse.path : null;
249
254
  const isPage = docFormat === "page";
250
255
  const detailNames = fieldNames.filter((name) => fieldKinds[name] !== "blocks");
251
256
  const blocksField =
@@ -268,18 +273,28 @@ export function DocEditorPage() {
268
273
  branch,
269
274
  tree,
270
275
  });
276
+ const framePath = browsedPath ?? previewPath;
277
+ // Each document is the content file its model path names. A deleted document is gone.
278
+ const previewRoutes = routesForContentFiles(
279
+ models,
280
+ replica.docs(workspaceId).flatMap((doc) => {
281
+ if (doc.status === "deleted") return [];
282
+ const model = models.find((item) => item.type === doc.type);
283
+ return model ? [repoPathFromModel(model, doc.slug)] : [];
284
+ }),
285
+ );
271
286
 
272
287
  useEffect(() => {
273
- if (!frameIdentity || !previewPath || !workspaceId || !siteUrl) {
288
+ if (!frameIdentity || !framePath || !workspaceId || !siteUrl) {
274
289
  setPreviewUrl(null);
275
290
  return;
276
291
  }
277
292
  if (studioIsOnSite(siteUrl, window.location.origin)) {
278
- setPreviewUrl(onSitePreviewUrl(siteUrl, previewPath));
293
+ setPreviewUrl(onSitePreviewUrl(siteUrl, framePath));
279
294
  return;
280
295
  }
281
296
  let cancelled = false;
282
- void createPreviewTicket(workspaceId, { redirect: previewPath, branch, tree })
297
+ void createPreviewTicket(workspaceId, { redirect: framePath, branch, tree })
283
298
  .then((res) => {
284
299
  if (!cancelled) setPreviewUrl(previewEnableUrl(siteUrl, res.ticket));
285
300
  })
@@ -289,7 +304,7 @@ export function DocEditorPage() {
289
304
  return () => {
290
305
  cancelled = true;
291
306
  };
292
- }, [frameIdentity, previewPath, workspaceId, siteUrl, branch, tree]);
307
+ }, [frameIdentity, framePath, workspaceId, siteUrl, branch, tree]);
293
308
 
294
309
  useEffect(() => {
295
310
  if (relationTargetTypes.length === 0) {
@@ -488,6 +503,17 @@ export function DocEditorPage() {
488
503
  setPreviewKey((k) => k + 1);
489
504
  }
490
505
 
506
+ function onPreviewPath(next: string) {
507
+ const pathname = next.split(/[?#]/, 1)[0] || "/";
508
+ const doc = matchRoute(models, pathname);
509
+ if (doc && (doc.type !== type || doc.slug !== slug)) {
510
+ navigate(contentDocPath(workspace.slug, doc.type, doc.slug));
511
+ return;
512
+ }
513
+ if (next === framePath) remountPreview();
514
+ setBrowse({ doc: docIdentity, path: next });
515
+ }
516
+
491
517
  function choosePreviewSize(size: PreviewSize) {
492
518
  setPreviewSize(size);
493
519
  persistPreviewSize(size);
@@ -881,32 +907,7 @@ export function DocEditorPage() {
881
907
  style={{ ...previewSx.style, flexBasis: `${previewPct}%` }}
882
908
  >
883
909
  <div {...stylex.props(styles.previewBar)}>
884
- <div {...stylex.props(styles.urlGroup)}>
885
- <Tooltip label={previewLoading ? "Loading preview…" : "Reload preview"}>
886
- <button
887
- type="button"
888
- aria-label={previewLoading ? "Preview loading" : "Reload preview"}
889
- aria-busy={previewLoading || undefined}
890
- onClick={remountPreview}
891
- {...stylex.props(styles.reloadBtn)}
892
- >
893
- <span
894
- {...stylex.props(
895
- styles.reloadIcon,
896
- previewLoading && styles.reloadSpinning,
897
- )}
898
- >
899
- <ReloadIcon />
900
- </span>
901
- </button>
902
- </Tooltip>
903
- <span {...stylex.props(styles.url)} title={previewUrl ?? undefined}>
904
- {previewUrl
905
- ? previewUrl.replace(/^https?:\/\//, "")
906
- : !siteUrl
907
- ? "No site URL"
908
- : "Loading checkout…"}
909
- </span>
910
+ <div {...stylex.props(styles.previewLead)}>
910
911
  {previewLive === "rendered" ? (
911
912
  <span {...stylex.props(styles.previewLive)}>Updated</span>
912
913
  ) : null}
@@ -927,15 +928,20 @@ export function DocEditorPage() {
927
928
  </button>
928
929
  ) : null}
929
930
  </div>
930
- <PreviewSizeSwitch size={previewSize} onSize={choosePreviewSize} />
931
- <span
932
- {...stylex.props(
933
- styles.badge,
934
- inConflict && styles.badgeConflict,
935
- )}
936
- >
937
- {statusLabel ?? "…"}
938
- </span>
931
+ <PreviewAddress
932
+ path={framePath ?? (!siteUrl ? "No site URL" : "Loading checkout…")}
933
+ enabled={previewUrl !== null}
934
+ routes={previewRoutes}
935
+ routesPending={!replica.ready}
936
+ onPath={onPreviewPath}
937
+ onReload={remountPreview}
938
+ onOpen={() => {
939
+ if (previewUrl) window.open(previewUrl, "_blank", "noopener,noreferrer");
940
+ }}
941
+ />
942
+ <div {...stylex.props(styles.sizeSwitch)}>
943
+ <PreviewSizeSwitch size={previewSize} onSize={choosePreviewSize} />
944
+ </div>
939
945
  </div>
940
946
  {previewUrl ? (
941
947
  <PreviewViewport size={previewSize} dragging={dragging}>
@@ -1000,7 +1006,7 @@ export function DocEditorPage() {
1000
1006
  <div {...stylex.props(styles.editorColumn, !showPreview && styles.editorColumnSolo)}>
1001
1007
  <div {...stylex.props(styles.toolbar)}>
1002
1008
  <div {...stylex.props(styles.meta)}>
1003
- <Text as="h1" size="md" weight="semibold" truncate>
1009
+ <Text as="h1" size="sm" weight="medium" truncate>
1004
1010
  {type}/{slug}
1005
1011
  </Text>
1006
1012
  </div>
@@ -1230,74 +1236,34 @@ const styles = stylex.create({
1230
1236
  flexShrink: 0,
1231
1237
  flexDirection: "column",
1232
1238
  overflow: "hidden",
1233
- backgroundColor: colors.bgSurface,
1239
+ backgroundColor: colors.bgApp,
1234
1240
  },
1235
1241
  previewBar: {
1236
- display: "flex",
1237
- flexShrink: 0,
1242
+ display: "grid",
1243
+ gridTemplateColumns: "1fr minmax(0, 520px) 1fr",
1238
1244
  alignItems: "center",
1239
- justifyContent: "space-between",
1245
+ flexShrink: 0,
1240
1246
  gap: space.md,
1247
+ height: "44px",
1248
+ boxSizing: "border-box",
1241
1249
  paddingInline: space.md,
1242
- paddingBlock: space.sm,
1243
1250
  borderBottomWidth: 1,
1244
1251
  borderBottomStyle: "solid",
1245
1252
  borderBottomColor: colors.border,
1253
+ backgroundColor: colors.bgSurface,
1246
1254
  fontSize: type.xs,
1247
1255
  color: colors.textMuted,
1248
1256
  },
1249
- urlGroup: {
1257
+ previewLead: {
1250
1258
  display: "flex",
1251
- minWidth: 0,
1252
- flex: 1,
1253
1259
  alignItems: "center",
1254
1260
  gap: space.sm,
1255
- },
1256
- reloadBtn: {
1257
- display: "inline-flex",
1258
- flexShrink: 0,
1259
- alignItems: "center",
1260
- justifyContent: "center",
1261
- width: 22,
1262
- height: 22,
1263
- padding: 0,
1264
- borderWidth: 0,
1265
- borderRadius: radius.sm,
1266
- backgroundColor: "transparent",
1267
- color: colors.textMuted,
1268
- cursor: "pointer",
1269
- touchAction: "manipulation",
1270
- [hover]: {
1271
- ":hover": {
1272
- backgroundColor: colors.bgHover,
1273
- color: colors.text,
1274
- },
1275
- },
1276
- ":focus-visible": {
1277
- outlineWidth: 2,
1278
- outlineStyle: "solid",
1279
- outlineColor: colors.focus,
1280
- outlineOffset: 1,
1281
- },
1282
- },
1283
- reloadIcon: {
1284
- display: "inline-flex",
1285
- },
1286
- reloadSpinning: {
1287
- animationName: spin,
1288
- animationDuration: "700ms",
1289
- animationTimingFunction: "linear",
1290
- animationIterationCount: "infinite",
1291
- "@media (prefers-reduced-motion: reduce)": {
1292
- animationName: "none",
1293
- animationDuration: "0s",
1294
- },
1295
- },
1296
- url: {
1261
+ minWidth: 0,
1262
+ justifySelf: "start",
1297
1263
  overflow: "hidden",
1298
- textOverflow: "ellipsis",
1299
- whiteSpace: "nowrap",
1300
- fontFamily: type.mono,
1264
+ },
1265
+ sizeSwitch: {
1266
+ justifySelf: "end",
1301
1267
  },
1302
1268
  previewLive: {
1303
1269
  flexShrink: 0,
@@ -1310,28 +1276,21 @@ const styles = stylex.create({
1310
1276
  editPage: {
1311
1277
  flexShrink: 0,
1312
1278
  borderWidth: 0,
1313
- borderRadius: 4,
1279
+ borderRadius: radius.sm,
1314
1280
  backgroundColor: colors.bgHover,
1315
1281
  color: colors.text,
1316
1282
  cursor: "pointer",
1317
- fontSize: type.sm,
1318
- paddingInline: space.sm,
1319
- paddingBlock: 2,
1320
- },
1321
- badge: {
1322
- flexShrink: 0,
1323
- borderWidth: 1,
1324
- borderStyle: "solid",
1325
- borderColor: colors.border,
1326
- borderRadius: 4,
1283
+ font: "inherit",
1284
+ fontSize: type.xs,
1285
+ fontWeight: 500,
1327
1286
  paddingInline: space.sm,
1328
- paddingBlock: 2,
1329
- textTransform: "uppercase",
1330
- letterSpacing: "0.04em",
1331
- },
1332
- badgeConflict: {
1333
- borderColor: colors.danger,
1334
- color: colors.danger,
1287
+ paddingBlock: "2px",
1288
+ ":focus-visible": {
1289
+ outlineWidth: 2,
1290
+ outlineStyle: "solid",
1291
+ outlineColor: colors.focus,
1292
+ outlineOffset: 1,
1293
+ },
1335
1294
  },
1336
1295
  conflictBanner: {
1337
1296
  display: "flex",
@@ -1454,22 +1413,23 @@ const styles = stylex.create({
1454
1413
  toolbar: {
1455
1414
  display: "flex",
1456
1415
  flexShrink: 0,
1457
- flexWrap: "wrap",
1458
1416
  alignItems: "center",
1459
1417
  justifyContent: "space-between",
1460
- gap: space.sm,
1461
- paddingInline: space.lg,
1462
- paddingBlock: space.sm,
1418
+ gap: space.md,
1419
+ height: "44px",
1420
+ boxSizing: "border-box",
1421
+ paddingInline: space.md,
1463
1422
  borderBottomWidth: 1,
1464
1423
  borderBottomStyle: "solid",
1465
1424
  borderBottomColor: colors.border,
1425
+ backgroundColor: colors.bgSurface,
1466
1426
  },
1467
1427
  meta: {
1468
1428
  minWidth: 0,
1469
1429
  },
1470
1430
  actions: {
1471
1431
  display: "flex",
1472
- flexWrap: "wrap",
1432
+ flexShrink: 0,
1473
1433
  gap: space.sm,
1474
1434
  },
1475
1435
  formWrap: {