@backmatter/studio 0.0.6 → 0.0.8

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 (46) hide show
  1. package/dist/chunks/{EmbedPublishPage-DazYJLaD.js → EmbedPublishPage-CtIlaeGO.js} +1 -1
  2. package/dist/chunks/{UiKitPage-DyVyBp0I.js → UiKitPage-DcnSQ-Hf.js} +3 -3
  3. package/dist/chunks/{WorkspaceRoutes-CPvkgvxe.js → WorkspaceRoutes-DVpUOaMV.js} +20841 -18934
  4. package/dist/chunks/{dashboard-6aSS8oyj.js → dashboard-bNp6AbKE.js} +465 -443
  5. package/dist/chunks/{index-DgTNL4t3.js → index-DoDE-hwB.js} +2 -2
  6. package/dist/chunks/{status-bar-action-RNBujSgk.js → status-bar-action-C0fsAWvD.js} +5804 -5668
  7. package/dist/dashboard.js +2 -2
  8. package/dist/index.js +4 -4
  9. package/dist/site-widget.js +2 -2
  10. package/dist/studio.css +454 -62
  11. package/package.json +3 -2
  12. package/src/App.tsx +2 -0
  13. package/src/WorkspaceRoutes.tsx +2 -0
  14. package/src/api.ts +80 -0
  15. package/src/chat/ChatMessage.tsx +10 -3
  16. package/src/chat/Composer.tsx +18 -18
  17. package/src/chat/chat-helpers.ts +58 -1
  18. package/src/chrome/AppShell.tsx +47 -10
  19. package/src/chrome/AppSidebar.tsx +10 -160
  20. package/src/chrome/CommandPalette.tsx +211 -74
  21. package/src/chrome/ContentExplorerSidebar.tsx +1212 -0
  22. package/src/chrome/UserMenu.tsx +20 -3
  23. package/src/chrome/WorkspaceNavContext.tsx +1 -0
  24. package/src/chrome/command-palette-icons.tsx +152 -0
  25. package/src/chrome/content-path-tree.ts +95 -0
  26. package/src/chrome/content-repo-path.ts +52 -0
  27. package/src/chrome/explorer-icons.tsx +87 -0
  28. package/src/chrome/folder-create-context.ts +147 -0
  29. package/src/pages/BlockSettingsDialog.tsx +4 -0
  30. package/src/pages/CliAuthorizePage.tsx +43 -0
  31. package/src/pages/DocEditorPage.tsx +33 -4
  32. package/src/pages/MediaFieldInput.tsx +139 -0
  33. package/src/pages/MediaGalleryPage.tsx +748 -0
  34. package/src/pages/MediaLightbox.tsx +467 -0
  35. package/src/pages/SelectRepositoriesLink.tsx +28 -4
  36. package/src/pages/SettingsGeneralPage.tsx +25 -28
  37. package/src/pages/SettingsGithubPage.tsx +413 -124
  38. package/src/pages/SettingsUsagePage.tsx +19 -0
  39. package/src/pages/TypeDocsPage.tsx +2 -210
  40. package/src/pages/WorkspaceLayout.tsx +6 -1
  41. package/src/pages/media-grid.ts +190 -0
  42. package/src/pages/preview-url.ts +13 -2
  43. package/src/replica/useReplica.ts +9 -0
  44. package/src/ui/ConfirmDialog.tsx +1 -1
  45. package/src/workspace/permissions.ts +9 -0
  46. package/src/workspace-path.ts +27 -1
@@ -16,7 +16,7 @@ import { isStudioDev } from "../studio-config";
16
16
  import { useReplicaState } from "../replica/useReplica";
17
17
  import { useChatUIOptional } from "../chat/ChatProvider";
18
18
  import { useAppChrome } from "../components/AppChromeContext";
19
- import { colors, radius, space, type, z } from "../styles/tokens.stylex";
19
+ import { colors, space, type, z } from "../styles/tokens.stylex";
20
20
  import { useTheme } from "../theme/ThemeProvider";
21
21
  import { cx } from "../ui/popup";
22
22
  import { useEditorActions } from "./EditorActionsContext";
@@ -33,16 +33,33 @@ import {
33
33
  settingsPath,
34
34
  workspaceHomePath,
35
35
  } from "../workspace-path";
36
+ import { PaletteIcon, type PaletteIconId } from "./command-palette-icons";
37
+ import { SearchIcon } from "../chat/icons";
36
38
 
37
39
  type Item = {
38
40
  id: string;
39
41
  group: string;
40
42
  label: string;
43
+ icon: PaletteIconId;
41
44
  hint?: string;
42
45
  shortcut?: string;
46
+ /** Hidden when the palette opens; shown once the user types to search. */
47
+ searchOnly?: boolean;
43
48
  run: () => void;
44
49
  };
45
50
 
51
+ const GROUP_ORDER = [
52
+ "Editing",
53
+ "Git",
54
+ "AI",
55
+ "Go to",
56
+ "Appearance",
57
+ "Settings",
58
+ "Content",
59
+ "Workspaces",
60
+ "Documents",
61
+ ] as const;
62
+
46
63
  type ItemGroup = { value: string; items: Item[] };
47
64
 
48
65
  const THEME_LABEL = {
@@ -66,7 +83,8 @@ export function CommandPalette() {
66
83
  const workspaceNav = useWorkspaceNav();
67
84
  const editorActions = useEditorActions();
68
85
  const { onLogout } = useAppChrome();
69
- const { preference, cyclePreference } = useTheme();
86
+ const { preference, cyclePreference, resolved } = useTheme();
87
+ const glass = resolved === "dark" ? glassDark : glassLight;
70
88
  const chat = useChatUIOptional();
71
89
  const mod = modifierSymbol();
72
90
 
@@ -124,8 +142,9 @@ export function CommandPalette() {
124
142
  if (saveDocAction) {
125
143
  list.push({
126
144
  id: "action-save",
127
- group: "Actions",
145
+ group: "Editing",
128
146
  label: "Save",
147
+ icon: "save",
129
148
  shortcut: `${mod}S`,
130
149
  run: () => {
131
150
  setOpen(false);
@@ -139,7 +158,8 @@ export function CommandPalette() {
139
158
  const conflicts = countConflicts(git);
140
159
  list.push({
141
160
  id: "action-commit",
142
- group: "Actions",
161
+ group: "Git",
162
+ icon: "commit",
143
163
  label:
144
164
  conflicts > 0
145
165
  ? `Commit blocked: ${conflicts} ${conflicts === 1 ? "conflict" : "conflicts"}`
@@ -162,7 +182,8 @@ export function CommandPalette() {
162
182
  const workspaceId = workspaceNav.workspaceId;
163
183
  list.push({
164
184
  id: "action-pull",
165
- group: "Actions",
185
+ group: "Git",
186
+ icon: "pull",
166
187
  label: "Pull from GitHub",
167
188
  hint: "Re-import the branch if a push was missed",
168
189
  run: () => {
@@ -178,7 +199,8 @@ export function CommandPalette() {
178
199
  list.push(
179
200
  {
180
201
  id: "action-chat",
181
- group: "Actions",
202
+ group: "AI",
203
+ icon: "chat",
182
204
  label: "Open AI chat",
183
205
  shortcut: `${mod}J`,
184
206
  run: () => {
@@ -188,7 +210,8 @@ export function CommandPalette() {
188
210
  },
189
211
  {
190
212
  id: "action-chat-new",
191
- group: "Actions",
213
+ group: "AI",
214
+ icon: "chat-new",
192
215
  label: "New AI chat",
193
216
  run: () => {
194
217
  setOpen(false);
@@ -198,7 +221,8 @@ export function CommandPalette() {
198
221
  },
199
222
  {
200
223
  id: "action-chat-history",
201
- group: "Actions",
224
+ group: "AI",
225
+ icon: "chat-history",
202
226
  label: "AI chat history",
203
227
  run: () => {
204
228
  setOpen(false);
@@ -210,7 +234,8 @@ export function CommandPalette() {
210
234
  if (editorActions.canRevert && revertDoc) {
211
235
  list.push({
212
236
  id: "action-revert",
213
- group: "Actions",
237
+ group: "Editing",
238
+ icon: "revert",
214
239
  label: "Revert file",
215
240
  hint: "Drop my uncommitted changes to this document",
216
241
  run: () => {
@@ -222,35 +247,29 @@ export function CommandPalette() {
222
247
  list.push(
223
248
  {
224
249
  id: "action-workspaces",
225
- group: "Actions",
250
+ group: "Go to",
251
+ icon: "workspaces",
226
252
  label: "All workspaces",
227
253
  run: go("/workspaces"),
228
254
  },
229
255
  {
230
256
  id: "action-new-ws",
231
- group: "Actions",
257
+ group: "Go to",
258
+ icon: "workspace-new",
232
259
  label: "Create workspace",
233
260
  run: go("/workspaces/new"),
234
261
  },
235
262
  {
236
263
  id: "action-account",
237
- group: "Actions",
264
+ group: "Go to",
265
+ icon: "account",
238
266
  label: "User settings",
239
267
  run: go("/account"),
240
268
  },
241
- {
242
- id: "action-theme",
243
- group: "Appearance",
244
- label: `Theme: ${THEME_LABEL[preference]}`,
245
- hint: "Toggle",
246
- run: () => {
247
- cyclePreference();
248
- setOpen(false);
249
- },
250
- },
251
269
  {
252
270
  id: "action-logout",
253
- group: "Actions",
271
+ group: "Go to",
272
+ icon: "logout",
254
273
  label: "Log out",
255
274
  run: () => {
256
275
  setOpen(false);
@@ -258,12 +277,25 @@ export function CommandPalette() {
258
277
  },
259
278
  },
260
279
  );
280
+ list.push({
281
+ id: "action-theme",
282
+ group: "Appearance",
283
+ icon: "theme",
284
+ label: `Theme: ${THEME_LABEL[preference]}`,
285
+ hint: "Toggle",
286
+ run: () => {
287
+ cyclePreference();
288
+ setOpen(false);
289
+ },
290
+ });
261
291
  if (isStudioDev()) {
262
292
  list.push({
263
293
  id: "action-ui-kit",
264
- group: "Actions",
294
+ group: "Go to",
295
+ icon: "ui-kit",
265
296
  label: "Open UI kit",
266
297
  hint: "/__ui",
298
+ searchOnly: true,
267
299
  run: go("/__ui"),
268
300
  });
269
301
  }
@@ -271,7 +303,9 @@ export function CommandPalette() {
271
303
  list.push({
272
304
  id: `ws-${ws.id}`,
273
305
  group: "Workspaces",
306
+ icon: "workspace",
274
307
  label: ws.name,
308
+ searchOnly: true,
275
309
  run: go(workspaceHomePath(ws.slug)),
276
310
  });
277
311
  }
@@ -280,34 +314,44 @@ export function CommandPalette() {
280
314
  {
281
315
  id: "settings-general",
282
316
  group: "Settings",
317
+ icon: "settings-general",
283
318
  label: "General settings",
319
+ searchOnly: true,
284
320
  run: go(settingsPath(workspaceNav.workspaceSlug, "general")),
285
321
  },
286
322
  {
287
323
  id: "settings-github",
288
324
  group: "Settings",
325
+ icon: "settings-github",
289
326
  label: "GitHub settings",
327
+ searchOnly: true,
290
328
  run: go(settingsPath(workspaceNav.workspaceSlug, "github")),
291
329
  },
292
330
  {
293
331
  id: "settings-schema",
294
332
  group: "Settings",
333
+ icon: "settings-schema",
295
334
  label: "Schema settings",
335
+ searchOnly: true,
296
336
  run: go(settingsPath(workspaceNav.workspaceSlug, "schema")),
297
337
  },
298
338
  {
299
339
  id: "settings-mcp",
300
340
  group: "Settings",
341
+ icon: "connectors",
301
342
  label: "Connectors",
343
+ searchOnly: true,
302
344
  run: go(settingsPath(workspaceNav.workspaceSlug, "mcp")),
303
345
  },
304
346
  );
305
347
  for (const contentType of workspaceNav.contentTypes) {
306
348
  list.push({
307
349
  id: `type-${contentType}`,
308
- group: "Documents",
350
+ group: "Content",
351
+ icon: "content-type",
309
352
  label: contentType,
310
- hint: "Type",
353
+ hint: "Content type",
354
+ searchOnly: true,
311
355
  run: go(contentTypePath(workspaceNav.workspaceSlug, contentType)),
312
356
  });
313
357
  }
@@ -315,8 +359,17 @@ export function CommandPalette() {
315
359
  list.push({
316
360
  id: `doc-${doc.locale}-${doc.type}-${doc.slug}`,
317
361
  group: "Documents",
362
+ icon: "document",
318
363
  label: `${doc.type}/${doc.slug}`,
319
- hint: doc.status === "clean" ? doc.locale : `${doc.locale} · ${doc.status === "added" ? "new" : doc.status}`,
364
+ searchOnly: true,
365
+ hint:
366
+ doc.status === "clean"
367
+ ? undefined
368
+ : doc.status === "added"
369
+ ? "New"
370
+ : doc.status === "modified"
371
+ ? "Modified"
372
+ : doc.status,
320
373
  run: go(
321
374
  contentDocPath(
322
375
  workspaceNav.workspaceSlug,
@@ -347,16 +400,19 @@ export function CommandPalette() {
347
400
  ]);
348
401
 
349
402
  const groups = useMemo<ItemGroup[]>(() => {
350
- const order: string[] = [];
351
403
  const map = new Map<string, Item[]>();
352
404
  for (const item of items) {
353
- if (!map.has(item.group)) {
354
- map.set(item.group, []);
355
- order.push(item.group);
356
- }
357
- map.get(item.group)!.push(item);
405
+ const bucket = map.get(item.group);
406
+ if (bucket) bucket.push(item);
407
+ else map.set(item.group, [item]);
358
408
  }
359
- return order.map((group) => ({ value: group, items: map.get(group)! }));
409
+ const rank = (name: string) => {
410
+ const index = GROUP_ORDER.indexOf(name as (typeof GROUP_ORDER)[number]);
411
+ return index === -1 ? GROUP_ORDER.length : index;
412
+ };
413
+ return [...map.entries()]
414
+ .sort(([a], [b]) => rank(a) - rank(b))
415
+ .map(([group, groupItems]) => ({ value: group, items: groupItems }));
360
416
  }, [items]);
361
417
 
362
418
  return (
@@ -375,21 +431,26 @@ export function CommandPalette() {
375
431
  >
376
432
  <Dialog.Root open={open} onOpenChange={setOpen}>
377
433
  <Dialog.Portal>
378
- <Dialog.Backdrop {...stylex.props(styles.backdrop)} />
379
- <Dialog.Popup {...stylex.props(styles.popup)} initialFocus={inputRef}>
434
+ <Dialog.Backdrop {...stylex.props(styles.backdrop, glass.backdrop)} />
435
+ <Dialog.Popup {...stylex.props(styles.popup, glass.popup)} initialFocus={inputRef}>
380
436
  <Dialog.Title {...stylex.props(styles.srOnly)}>Command palette</Dialog.Title>
381
437
  <Dialog.Description {...stylex.props(styles.srOnly)}>
382
438
  Jump to pages or run save, commit, and other actions.
383
439
  </Dialog.Description>
384
- <Combobox.Input
385
- ref={inputRef}
386
- placeholder="Go to or run…"
387
- aria-label="Command palette search"
388
- autoComplete="off"
389
- spellCheck={false}
390
- className={cx(styles.input)}
391
- />
392
- <p {...stylex.props(styles.caption)}>↑↓ move · ↵ run · esc close</p>
440
+ <div {...stylex.props(styles.inputRow)}>
441
+ <span {...stylex.props(styles.inputIcon)} aria-hidden>
442
+ <SearchIcon size={15} />
443
+ </span>
444
+ <Combobox.Input
445
+ ref={inputRef}
446
+ placeholder="Go to or run…"
447
+ aria-label="Command palette search"
448
+ autoComplete="off"
449
+ spellCheck={false}
450
+ data-no-ring=""
451
+ className={cx(styles.input)}
452
+ />
453
+ </div>
393
454
  <div {...stylex.props(styles.results)}>
394
455
  <Combobox.Empty className={cx(styles.empty)}>No matches</Combobox.Empty>
395
456
  <Combobox.List>
@@ -404,10 +465,15 @@ export function CommandPalette() {
404
465
  key={item.id}
405
466
  value={item}
406
467
  className={(state) =>
407
- cx(styles.item, state.highlighted && styles.itemActive)
468
+ cx(styles.item, state.highlighted && glass.itemActive)
408
469
  }
409
470
  >
410
- <span>{item.label}</span>
471
+ <span {...stylex.props(styles.itemMain)}>
472
+ <span {...stylex.props(styles.itemIcon)}>
473
+ <PaletteIcon id={item.icon} />
474
+ </span>
475
+ <span {...stylex.props(styles.itemLabel)}>{item.label}</span>
476
+ </span>
411
477
  <span {...stylex.props(styles.meta)}>
412
478
  {item.hint ? (
413
479
  <span {...stylex.props(styles.hint)}>{item.hint}</span>
@@ -431,6 +497,7 @@ export function CommandPalette() {
431
497
  }
432
498
 
433
499
  function filterItem(item: Item, query: string) {
500
+ if (!query.trim() && item.searchOnly) return false;
434
501
  return (
435
502
  matches(query, item.label) ||
436
503
  matches(query, item.group) ||
@@ -439,12 +506,55 @@ function filterItem(item: Item, query: string) {
439
506
  );
440
507
  }
441
508
 
509
+ const glassLight = stylex.create({
510
+ backdrop: {
511
+ backgroundColor: "rgba(24, 24, 27, 0.28)",
512
+ backdropFilter: "blur(6px)",
513
+ },
514
+ popup: {
515
+ backgroundColor: "rgba(255, 255, 255, 0.78)",
516
+ backdropFilter: "blur(28px) saturate(1.15)",
517
+ borderColor: "rgba(255, 255, 255, 0.72)",
518
+ boxShadow:
519
+ "0 28px 80px rgba(24, 24, 27, 0.16), 0 0 0 1px rgba(255, 255, 255, 0.55) inset",
520
+ "@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)))": {
521
+ backgroundColor: colors.bgSurface,
522
+ borderColor: colors.border,
523
+ boxShadow: "0 16px 48px rgba(24, 24, 27, 0.2)",
524
+ },
525
+ },
526
+ itemActive: {
527
+ backgroundColor: "rgba(24, 24, 27, 0.06)",
528
+ },
529
+ });
530
+
531
+ const glassDark = stylex.create({
532
+ backdrop: {
533
+ backgroundColor: "rgba(0, 0, 0, 0.42)",
534
+ backdropFilter: "blur(8px)",
535
+ },
536
+ popup: {
537
+ backgroundColor: "rgba(36, 36, 40, 0.72)",
538
+ backdropFilter: "blur(28px) saturate(1.25)",
539
+ borderColor: "rgba(255, 255, 255, 0.1)",
540
+ boxShadow:
541
+ "0 28px 90px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(255, 255, 255, 0.07) inset",
542
+ "@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)))": {
543
+ backgroundColor: colors.bgSurface,
544
+ borderColor: colors.border,
545
+ boxShadow: "0 16px 48px rgba(0, 0, 0, 0.45)",
546
+ },
547
+ },
548
+ itemActive: {
549
+ backgroundColor: "rgba(255, 255, 255, 0.09)",
550
+ },
551
+ });
552
+
442
553
  const styles = stylex.create({
443
554
  backdrop: {
444
555
  position: "fixed",
445
556
  inset: 0,
446
557
  zIndex: z.overlay,
447
- backgroundColor: colors.overlay,
448
558
  },
449
559
  popup: {
450
560
  position: "fixed",
@@ -453,53 +563,63 @@ const styles = stylex.create({
453
563
  zIndex: z.palette,
454
564
  width: "min(520px, calc(100vw - 32px))",
455
565
  transform: "translateX(-50%)",
456
- borderRadius: radius.md,
566
+ borderRadius: 14,
457
567
  borderWidth: 1,
458
568
  borderStyle: "solid",
459
- borderColor: colors.border,
460
- backgroundColor: colors.bgSurface,
461
569
  overflow: "hidden",
462
570
  outline: "none",
463
571
  "@media (prefers-reduced-motion: reduce)": {
464
572
  transitionDuration: "0ms",
465
573
  },
466
574
  },
575
+ inputRow: {
576
+ display: "flex",
577
+ alignItems: "center",
578
+ gap: space.md,
579
+ paddingInline: space.xl,
580
+ paddingTop: space.lg,
581
+ paddingBottom: space.md,
582
+ },
583
+ inputIcon: {
584
+ display: "inline-flex",
585
+ flexShrink: 0,
586
+ color: colors.textMuted,
587
+ },
467
588
  input: {
468
589
  display: "block",
469
590
  width: "100%",
591
+ minWidth: 0,
470
592
  borderWidth: 0,
471
593
  backgroundColor: "transparent",
472
594
  color: colors.text,
473
- paddingInline: space.xl,
474
- paddingTop: space.lg,
475
- paddingBottom: space.sm,
595
+ padding: 0,
476
596
  fontSize: type.lg,
477
597
  outline: "none",
478
- },
479
- caption: {
480
- margin: 0,
481
- paddingInline: space.xl,
482
- paddingBottom: space.md,
483
- borderBottomWidth: 1,
484
- borderBottomStyle: "solid",
485
- borderBottomColor: colors.border,
486
- color: colors.textMuted,
487
- fontFamily: type.mono,
488
- fontSize: type.xs,
489
- lineHeight: 1.45,
598
+ boxShadow: "none",
599
+ ":focus": {
600
+ outline: "none",
601
+ },
602
+ ":focus-visible": {
603
+ outline: "none",
604
+ },
605
+ "::placeholder": {
606
+ color: colors.textMuted,
607
+ },
490
608
  },
491
609
  results: {
492
- maxHeight: "320px",
610
+ maxHeight: "min(52vh, 440px)",
493
611
  overflowY: "auto",
494
- paddingBlock: space.sm,
612
+ paddingTop: 0,
613
+ paddingBottom: space.md,
495
614
  },
496
615
  group: {
497
616
  margin: 0,
498
617
  paddingInline: space.xl,
499
- paddingBlock: space.sm,
618
+ paddingTop: space.md,
619
+ paddingBottom: space.sm,
500
620
  fontSize: type.xs,
501
- fontWeight: 600,
502
- letterSpacing: "0.08em",
621
+ fontWeight: 500,
622
+ letterSpacing: "0.04em",
503
623
  textTransform: "uppercase",
504
624
  color: colors.textMuted,
505
625
  },
@@ -513,14 +633,31 @@ const styles = stylex.create({
513
633
  borderWidth: 0,
514
634
  backgroundColor: "transparent",
515
635
  paddingInline: space.xl,
516
- paddingBlock: space.md,
636
+ paddingBlock: 10,
517
637
  textAlign: "left",
518
638
  fontSize: type.md,
519
639
  fontWeight: 500,
520
640
  color: colors.text,
521
641
  },
522
- itemActive: {
523
- backgroundColor: colors.bgActive,
642
+ itemMain: {
643
+ display: "flex",
644
+ alignItems: "center",
645
+ gap: space.md,
646
+ minWidth: 0,
647
+ flex: 1,
648
+ },
649
+ itemIcon: {
650
+ display: "inline-flex",
651
+ flexShrink: 0,
652
+ alignItems: "center",
653
+ justifyContent: "center",
654
+ width: 18,
655
+ color: colors.textMuted,
656
+ },
657
+ itemLabel: {
658
+ overflow: "hidden",
659
+ textOverflow: "ellipsis",
660
+ whiteSpace: "nowrap",
524
661
  },
525
662
  hint: {
526
663
  color: colors.textMuted,