@backmatter/studio 0.0.15 → 0.0.17

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 (80) hide show
  1. package/dist/chunks/{UiKitApp-BDsrXN12.js → UiKitApp-D7vllRQE.js} +2 -2
  2. package/dist/chunks/{WorkspaceRoutes-BTZ1Ah4F.js → WorkspaceRoutes-DH1LD-2Q.js} +30013 -27965
  3. package/dist/chunks/{dashboard-FT7dm5wi.js → dashboard-Dsaeud2x.js} +55 -54
  4. package/dist/chunks/{define-studio-BYtYDGLX.js → define-studio-DlYjgrwr.js} +12 -16
  5. package/dist/chunks/{index-DHJvh5Uy.js → index-CGMqw2NH.js} +1 -1
  6. package/dist/dashboard.js +2 -2
  7. package/dist/define.js +1 -1
  8. package/dist/index.js +4 -4
  9. package/dist/site-widget.css +49 -1
  10. package/dist/site-widget.js +8341 -8058
  11. package/dist/studio.css +279 -13
  12. package/package.json +2 -2
  13. package/src/WorkspaceRoutes.tsx +5 -2
  14. package/src/api.ts +82 -7
  15. package/src/chat/ChangeCard.tsx +8 -2
  16. package/src/chat/ChatMessage.tsx +171 -102
  17. package/src/chat/ChatPanel.tsx +54 -34
  18. package/src/chat/ChatProvider.tsx +122 -20
  19. package/src/chat/Composer.tsx +51 -8
  20. package/src/chat/PointerChip.tsx +9 -5
  21. package/src/chat/ThreadHistory.tsx +3 -3
  22. package/src/chat/chat-context.ts +67 -3
  23. package/src/chat/chat-helpers.ts +12 -0
  24. package/src/chat/chat-state.ts +11 -0
  25. package/src/chrome/AppShell.tsx +16 -5
  26. package/src/chrome/CommandPalette.tsx +12 -0
  27. package/src/chrome/ContentExplorerSidebar.tsx +23 -3
  28. package/src/chrome/Toast.tsx +115 -0
  29. package/src/chrome/UserMenu.tsx +34 -3
  30. package/src/chrome/WorkspaceStatusBar.tsx +221 -30
  31. package/src/chrome/commit-error.ts +13 -0
  32. package/src/chrome/content-api-url.ts +0 -78
  33. package/src/chrome/content-tree-visibility.ts +13 -3
  34. package/src/chrome/explorer-icons.tsx +5 -0
  35. package/src/chrome/pull-failure.ts +61 -0
  36. package/src/code-session-entry.ts +9 -0
  37. package/src/components/WorkspaceSwitcher.tsx +2 -2
  38. package/src/element-origin.ts +149 -0
  39. package/src/fields/FieldControl.tsx +748 -0
  40. package/src/fields/choose-field-input.ts +64 -0
  41. package/src/fields/load-relation-documents.ts +35 -0
  42. package/src/fields/omit-field.ts +12 -0
  43. package/src/fields/relation-targets.ts +44 -0
  44. package/src/fields/resolve-object-fields.ts +21 -0
  45. package/src/host-pointer-bootstrap.ts +11 -0
  46. package/src/host-pointer.ts +147 -0
  47. package/src/install-preview-pointer.ts +19 -3
  48. package/src/pages/BlockSettingsDialog.tsx +19 -115
  49. package/src/pages/BlocksList.tsx +43 -35
  50. package/src/pages/BodyProse.tsx +16 -1
  51. package/src/pages/CodeSessionFiles.tsx +221 -0
  52. package/src/pages/CodeSessionPage.tsx +1361 -0
  53. package/src/pages/DocEditorPage.tsx +120 -107
  54. package/src/pages/PreviewSize.tsx +181 -0
  55. package/src/pages/RenderedBody.tsx +17 -2
  56. package/src/pages/SettingsGeneralPage.tsx +67 -2
  57. package/src/pages/WorkspaceHomePage.tsx +313 -0
  58. package/src/pages/WorkspaceLayout.tsx +10 -1
  59. package/src/pages/branch-notice.ts +16 -0
  60. package/src/pages/code-session-boot.ts +43 -0
  61. package/src/pages/code-session-context.tsx +53 -0
  62. package/src/pages/code-session-files.ts +42 -0
  63. package/src/pages/code-session-frame.ts +52 -0
  64. package/src/pages/code-session-routes.ts +42 -0
  65. package/src/pages/home-rows.ts +140 -0
  66. package/src/pages/preview-size.ts +33 -0
  67. package/src/pages/session-preview.ts +109 -0
  68. package/src/pages/use-session-preview.ts +70 -0
  69. package/src/replica/store.ts +11 -0
  70. package/src/replica/types.ts +6 -0
  71. package/src/shell-resets.css +15 -0
  72. package/src/site-pointer.ts +24 -5
  73. package/src/studio-config.ts +1 -1
  74. package/src/ui/Input.tsx +178 -20
  75. package/src/ui/ListRow.tsx +12 -3
  76. package/src/ui/Page.tsx +12 -1
  77. package/src/ui/popup.ts +11 -2
  78. package/src/workspace-path.ts +35 -3
  79. package/src/chrome/ContentApiPopover.tsx +0 -215
  80. package/src/pages/WorkspaceHomeRedirect.tsx +0 -17
@@ -7,17 +7,23 @@ import { ChatPanel } from "../chat/ChatPanel";
7
7
  import { ChatProvider, ChatSessionScope } from "../chat/ChatProvider";
8
8
  import { colors } from "../styles/tokens.stylex";
9
9
  import { TooltipProvider } from "../ui/Tooltip";
10
- import { cmsWorkspaceSlug, isOrgPickerPath } from "../workspace-path";
10
+ import {
11
+ cmsWorkspaceSlug,
12
+ isOrgPickerPath,
13
+ isWorkspaceCodePath,
14
+ isWorkspaceListPath,
15
+ } from "../workspace-path";
11
16
  import { AppSidebar } from "./AppSidebar";
12
17
  import { WorkspaceRail } from "./ContentExplorerSidebar";
13
18
  import { CommandPalette } from "./CommandPalette";
14
- import { FloatingUserMenu } from "./UserMenu";
19
+ import { CornerUserMenu, FloatingUserMenu } from "./UserMenu";
15
20
  import { EditorActionsProvider } from "./EditorActionsContext";
16
21
  import { EditorFocusProvider } from "./EditorFocusContext";
17
22
  import { EditorStatusProvider } from "./EditorStatusContext";
18
23
  import { WorkspaceNavProvider, useWorkspaceNav } from "./WorkspaceNavContext";
19
24
  import { installParentChatHotkey } from "./hotkeys";
20
25
  import { WorkspaceStatusBar } from "./WorkspaceStatusBar";
26
+ import { CodeSessionProvider } from "../pages/code-session-context";
21
27
 
22
28
  export function AppShell({ children }: { children: React.ReactNode }) {
23
29
  return (
@@ -26,7 +32,9 @@ export function AppShell({ children }: { children: React.ReactNode }) {
26
32
  <EditorStatusProvider>
27
33
  <EditorActionsProvider>
28
34
  <EditorFocusProvider>
29
- <AppShellInner>{children}</AppShellInner>
35
+ <CodeSessionProvider>
36
+ <AppShellInner>{children}</AppShellInner>
37
+ </CodeSessionProvider>
30
38
  </EditorFocusProvider>
31
39
  </EditorActionsProvider>
32
40
  </EditorStatusProvider>
@@ -43,6 +51,7 @@ function AppShellInner({ children }: { children: React.ReactNode }) {
43
51
  const workspaceKey = cmsWorkspaceSlug(location.pathname);
44
52
  const workspaceNav = useWorkspaceNav();
45
53
  const orgPicker = isOrgPickerPath(location.pathname);
54
+ const workspaceList = isWorkspaceListPath(location.pathname);
46
55
  const embed = searchParams.get("embed") === "1";
47
56
  const [treeHost, setTreeHost] = useState<HTMLDivElement | null>(null);
48
57
  const inWorkspace = Boolean(workspaceKey && !embed && !orgPicker);
@@ -52,8 +61,9 @@ function AppShellInner({ children }: { children: React.ReactNode }) {
52
61
  return installParentChatHotkey();
53
62
  }, [embed]);
54
63
 
64
+ const codeSession = Boolean(workspaceKey && isWorkspaceCodePath(location.pathname));
55
65
  const chat =
56
- workspaceKey && !embed ? (
66
+ workspaceKey && !embed && !codeSession ? (
57
67
  <ChatSessionScope>
58
68
  <ChatBar />
59
69
  <ChatPanel />
@@ -64,7 +74,7 @@ function AppShellInner({ children }: { children: React.ReactNode }) {
64
74
  <div {...stylex.props(styles.shell)}>
65
75
  {workspaceKey && !embed ? <WorkspaceStatusBar workspaceId={workspaceKey} /> : null}
66
76
  <div {...stylex.props(styles.body)}>
67
- {orgPicker || embed ? null : inWorkspace ? (
77
+ {orgPicker || embed || workspaceList ? null : inWorkspace ? (
68
78
  <WorkspaceRail workspace={workspaceNav} treeHost={treeHost} />
69
79
  ) : (
70
80
  <AppSidebar />
@@ -78,6 +88,7 @@ function AppShellInner({ children }: { children: React.ReactNode }) {
78
88
  </div>
79
89
  </div>
80
90
  {orgPicker ? <FloatingUserMenu /> : null}
91
+ {workspaceList ? <CornerUserMenu /> : null}
81
92
  {embed ? null : <CommandPalette />}
82
93
  </div>
83
94
  );
@@ -27,6 +27,7 @@ import {
27
27
  } from "./hotkeys";
28
28
  import { parsePreviewMessage } from "@backmatter/core/preview";
29
29
  import { useWorkspaceNav } from "./WorkspaceNavContext";
30
+ import { codeSessionEntry } from "../code-session-entry";
30
31
  import {
31
32
  contentDocPath,
32
33
  contentTypePath,
@@ -310,6 +311,17 @@ export function CommandPalette() {
310
311
  });
311
312
  }
312
313
  if (workspaceNav) {
314
+ const codeHref = codeSessionEntry(workspaceNav.role, workspaceNav.workspaceSlug);
315
+ if (codeHref) {
316
+ list.push({
317
+ id: "code-session",
318
+ group: "Go to",
319
+ icon: "document",
320
+ label: "Code session",
321
+ hint: "Host and chat",
322
+ run: go(codeHref),
323
+ });
324
+ }
313
325
  list.push(
314
326
  {
315
327
  id: "settings-general",
@@ -16,13 +16,14 @@ import { Tooltip } from "../ui/Tooltip";
16
16
  import {
17
17
  contentDocPath,
18
18
  contentRailHref,
19
- isWorkspaceContentPath,
19
+ isWorkspaceCodePath,
20
20
  isWorkspaceEditorPath,
21
21
  isWorkspaceMediaPath,
22
22
  isWorkspaceSettingsPath,
23
23
  mediaPath,
24
24
  settingsPath,
25
25
  } from "../workspace-path";
26
+ import { codeSessionEntry } from "../code-session-entry";
26
27
  import { UserMenu } from "./UserMenu";
27
28
  import {
28
29
  groupContentTypes,
@@ -38,6 +39,7 @@ import {
38
39
  ExplorerHeaderListIcon,
39
40
  ExplorerHeaderNewFileIcon,
40
41
  ExplorerTreeNewFileIcon,
42
+ ExplorerRailCodeIcon,
41
43
  ExplorerRailFilesIcon,
42
44
  ExplorerRailHistoryIcon,
43
45
  ExplorerRailMediaIcon,
@@ -62,6 +64,7 @@ import {
62
64
  } from "./folder-create-context";
63
65
  import { canWriteContent } from "../workspace/permissions";
64
66
  import {
67
+ contentRailCurrent,
65
68
  contentTreeVisible,
66
69
  toggleContentTree,
67
70
  useContentTreeOpen,
@@ -264,6 +267,7 @@ function ContentExplorerSidebar({
264
267
  pendingCount={pendingCount}
265
268
  settingsActive={settingsActive}
266
269
  workspaceSlug={workspace.workspaceSlug}
270
+ role={workspace.role}
267
271
  treeOpen={treeOpen}
268
272
  />
269
273
  {tree && treeHost ? createPortal(tree, treeHost) : null}
@@ -271,22 +275,26 @@ function ContentExplorerSidebar({
271
275
  );
272
276
  }
273
277
 
274
- function ActivityRail({
278
+ export function ActivityRail({
275
279
  pendingCount,
276
280
  settingsActive,
277
281
  workspaceSlug,
282
+ role,
278
283
  treeOpen,
279
284
  }: {
280
285
  pendingCount: number;
281
286
  settingsActive: boolean;
282
287
  workspaceSlug: string;
288
+ role: string;
283
289
  treeOpen: boolean;
284
290
  }) {
285
291
  const navigate = useNavigate();
286
292
  const location = useLocation();
287
293
  const onDocument = isWorkspaceEditorPath(location.pathname);
288
294
  const filesLabel = onDocument ? (treeOpen ? "Hide files" : "Show files") : "Content";
289
- const filesActive = isWorkspaceContentPath(location.pathname) && (!onDocument || treeOpen);
295
+ const filesActive = contentRailCurrent(location.pathname, treeOpen);
296
+ const codeHref = codeSessionEntry(role, workspaceSlug);
297
+ const codeActive = isWorkspaceCodePath(location.pathname);
290
298
 
291
299
  return (
292
300
  <nav
@@ -351,6 +359,18 @@ function ActivityRail({
351
359
  <ExplorerRailMediaIcon />
352
360
  </Link>
353
361
  </Tooltip>
362
+ {codeHref ? (
363
+ <Tooltip label="Code session" side="right">
364
+ <Link
365
+ to={codeHref}
366
+ aria-label="Code session"
367
+ aria-current={codeActive ? "page" : undefined}
368
+ {...stylex.props(styles.railBtn, codeActive && styles.railBtnActive)}
369
+ >
370
+ <ExplorerRailCodeIcon />
371
+ </Link>
372
+ </Tooltip>
373
+ ) : null}
354
374
  </div>
355
375
  <div {...stylex.props(styles.railBottom)}>
356
376
  <Tooltip label="Settings" side="right">
@@ -0,0 +1,115 @@
1
+ // packages/studio/src/chrome/Toast.tsx
2
+ import * as stylex from "@stylexjs/stylex";
3
+ import { colors, radius, space, type, z } from "../styles/tokens.stylex";
4
+
5
+ const hover = "@media (hover: hover)";
6
+
7
+ export type ToastProblem = {
8
+ file: string;
9
+ text: string;
10
+ };
11
+
12
+ /** A dismissible notice that sits over the page, not in the status bar. */
13
+ export function Toast({
14
+ title,
15
+ problems,
16
+ onDismiss,
17
+ }: {
18
+ title: string;
19
+ problems: ToastProblem[];
20
+ onDismiss: () => void;
21
+ }) {
22
+ return (
23
+ <div role="alert" {...stylex.props(styles.toast)}>
24
+ <div {...stylex.props(styles.head)}>
25
+ <p {...stylex.props(styles.title)}>{title}</p>
26
+ <button type="button" onClick={onDismiss} {...stylex.props(styles.dismiss)}>
27
+ Dismiss
28
+ </button>
29
+ </div>
30
+ <ul {...stylex.props(styles.list)}>
31
+ {problems.map((problem, index) => (
32
+ <li key={`${problem.file}-${index}`} {...stylex.props(styles.item)}>
33
+ {problem.file ? <span {...stylex.props(styles.file)}>{problem.file}</span> : null}
34
+ <span {...stylex.props(styles.text)}>{problem.text}</span>
35
+ </li>
36
+ ))}
37
+ </ul>
38
+ </div>
39
+ );
40
+ }
41
+
42
+ const styles = stylex.create({
43
+ toast: {
44
+ position: "fixed",
45
+ zIndex: z.tooltip,
46
+ top: "44px",
47
+ right: space.xl,
48
+ boxSizing: "border-box",
49
+ display: "flex",
50
+ flexDirection: "column",
51
+ gap: space.md,
52
+ width: "min(380px, calc(100vw - 32px))",
53
+ maxHeight: "min(420px, calc(100vh - 64px))",
54
+ padding: space.lg,
55
+ borderWidth: 1,
56
+ borderStyle: "solid",
57
+ borderColor: colors.border,
58
+ borderRadius: radius.md,
59
+ backgroundColor: colors.bgSurface,
60
+ boxShadow: "0 8px 24px rgba(24, 24, 27, 0.12)",
61
+ color: colors.text,
62
+ },
63
+ head: {
64
+ display: "flex",
65
+ alignItems: "center",
66
+ justifyContent: "space-between",
67
+ gap: space.lg,
68
+ },
69
+ title: {
70
+ margin: 0,
71
+ fontSize: type.sm,
72
+ fontWeight: 600,
73
+ color: colors.danger,
74
+ },
75
+ list: {
76
+ margin: 0,
77
+ padding: 0,
78
+ overflow: "auto",
79
+ listStyle: "none",
80
+ display: "flex",
81
+ flexDirection: "column",
82
+ gap: space.md,
83
+ },
84
+ item: {
85
+ display: "flex",
86
+ flexDirection: "column",
87
+ gap: "2px",
88
+ minWidth: 0,
89
+ },
90
+ file: {
91
+ fontFamily: type.mono,
92
+ fontSize: type.xs,
93
+ color: colors.text,
94
+ },
95
+ text: {
96
+ fontSize: type.sm,
97
+ lineHeight: 1.45,
98
+ color: colors.textMuted,
99
+ },
100
+ dismiss: {
101
+ flexShrink: 0,
102
+ margin: 0,
103
+ padding: 0,
104
+ borderWidth: 0,
105
+ backgroundColor: "transparent",
106
+ color: colors.textMuted,
107
+ fontSize: type.xs,
108
+ cursor: "pointer",
109
+ [hover]: {
110
+ ":hover": {
111
+ color: colors.text,
112
+ },
113
+ },
114
+ },
115
+ });
@@ -16,10 +16,28 @@ export function FloatingUserMenu() {
16
16
  );
17
17
  }
18
18
 
19
- export function UserMenu({ quiet = false, compact = false }: { quiet?: boolean; compact?: boolean }) {
19
+ /** Small account control for full-screen pages that have no sidebar. */
20
+ export function CornerUserMenu() {
21
+ return (
22
+ <div {...stylex.props(styles.corner)}>
23
+ <UserMenu quiet placement="below-end" />
24
+ </div>
25
+ );
26
+ }
27
+
28
+ export function UserMenu({
29
+ quiet = false,
30
+ compact = false,
31
+ placement = "above-start",
32
+ }: {
33
+ quiet?: boolean;
34
+ compact?: boolean;
35
+ placement?: "above-start" | "below-end";
36
+ }) {
20
37
  const { email, onLogout } = useAppChrome();
21
38
  const location = useLocation();
22
39
  const onAccount = location.pathname.replace(/\/$/, "") === "/account";
40
+ const belowEnd = placement === "below-end";
23
41
 
24
42
  return (
25
43
  <Menu.Root>
@@ -29,6 +47,7 @@ export function UserMenu({ quiet = false, compact = false }: { quiet?: boolean;
29
47
  styles.trigger,
30
48
  quiet && styles.triggerQuiet,
31
49
  compact && styles.triggerCompact,
50
+ belowEnd && styles.triggerCorner,
32
51
  onAccount && styles.triggerActive,
33
52
  )}
34
53
  >
@@ -40,8 +59,8 @@ export function UserMenu({ quiet = false, compact = false }: { quiet?: boolean;
40
59
  </Menu.Trigger>
41
60
  <Menu.Portal>
42
61
  <Menu.Positioner
43
- side="top"
44
- align={compact ? "center" : "start"}
62
+ side={belowEnd ? "bottom" : "top"}
63
+ align={belowEnd ? "end" : compact ? "center" : "start"}
45
64
  sideOffset={6}
46
65
  className={cx(popup.positioner)}
47
66
  >
@@ -81,6 +100,13 @@ const styles = stylex.create({
81
100
  zIndex: z.dropdown,
82
101
  maxWidth: "220px",
83
102
  },
103
+ corner: {
104
+ position: "fixed",
105
+ top: space.lg,
106
+ right: space.lg,
107
+ zIndex: z.dropdown,
108
+ maxWidth: "240px",
109
+ },
84
110
  trigger: {
85
111
  display: "flex",
86
112
  width: "100%",
@@ -128,6 +154,11 @@ const styles = stylex.create({
128
154
  fontSize: type.sm,
129
155
  fontWeight: 500,
130
156
  },
157
+ triggerCorner: {
158
+ minHeight: "44px",
159
+ justifyContent: "flex-end",
160
+ textAlign: "right",
161
+ },
131
162
  triggerActive: {
132
163
  backgroundColor: colors.bgActive,
133
164
  },