@backmatter/studio 0.0.14 → 0.0.16

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 (67) hide show
  1. package/dist/chunks/{UiKitApp-fYeVEf4-.js → UiKitApp-_EDqWID3.js} +2 -2
  2. package/dist/chunks/{WorkspaceRoutes-Dt7JvEWT.js → WorkspaceRoutes-B2PrFgpz.js} +29019 -27476
  3. package/dist/chunks/{dashboard-BuY2aGkO.js → dashboard-BnlnMnnr.js} +55 -54
  4. package/dist/chunks/{index-NWDqEZYW.js → index-D_BHF_8q.js} +1 -1
  5. package/dist/dashboard.js +2 -2
  6. package/dist/index.js +51 -52
  7. package/dist/site-widget.css +4 -0
  8. package/dist/site-widget.js +9419 -9156
  9. package/dist/studio.css +186 -12
  10. package/package.json +2 -2
  11. package/src/WorkspaceRoutes.tsx +5 -2
  12. package/src/api.ts +64 -11
  13. package/src/chat/ChangeCard.tsx +9 -15
  14. package/src/chat/ChatMessage.tsx +172 -103
  15. package/src/chat/ChatPanel.tsx +54 -34
  16. package/src/chat/ChatProvider.tsx +120 -24
  17. package/src/chat/Composer.tsx +48 -7
  18. package/src/chat/DocListCard.tsx +2 -16
  19. package/src/chat/MentionPopover.tsx +8 -9
  20. package/src/chat/PointerChip.tsx +9 -5
  21. package/src/chat/ThreadHistory.tsx +3 -3
  22. package/src/chat/chat-context.ts +74 -10
  23. package/src/chat/chat-helpers.ts +15 -8
  24. package/src/chat/chat-state.ts +11 -0
  25. package/src/chrome/AppShell.tsx +16 -5
  26. package/src/chrome/CommandPalette.tsx +14 -10
  27. package/src/chrome/ContentExplorerSidebar.tsx +23 -3
  28. package/src/chrome/UserMenu.tsx +34 -3
  29. package/src/chrome/WorkspaceStatusBar.tsx +215 -50
  30. package/src/chrome/content-api-url.ts +0 -78
  31. package/src/chrome/content-tree-visibility.ts +13 -3
  32. package/src/chrome/explorer-icons.tsx +5 -0
  33. package/src/code-session-entry.ts +9 -0
  34. package/src/components/WorkspaceSwitcher.tsx +2 -2
  35. package/src/element-origin.ts +149 -0
  36. package/src/host-pointer-bootstrap.ts +11 -0
  37. package/src/host-pointer.ts +147 -0
  38. package/src/install-preview-pointer.ts +19 -3
  39. package/src/pages/CodeSessionFiles.tsx +221 -0
  40. package/src/pages/CodeSessionPage.tsx +1188 -0
  41. package/src/pages/DocEditorPage.tsx +33 -15
  42. package/src/pages/EmbedChatPage.tsx +3 -3
  43. package/src/pages/EmbedPublishPage.tsx +2 -3
  44. package/src/pages/PreviewSize.tsx +181 -0
  45. package/src/pages/SettingsGeneralPage.tsx +67 -2
  46. package/src/pages/WorkspaceHomePage.tsx +313 -0
  47. package/src/pages/WorkspaceLayout.tsx +10 -1
  48. package/src/pages/code-session-boot.ts +43 -0
  49. package/src/pages/code-session-context.tsx +51 -0
  50. package/src/pages/code-session-files.ts +42 -0
  51. package/src/pages/code-session-frame.ts +52 -0
  52. package/src/pages/code-session-routes.ts +42 -0
  53. package/src/pages/home-rows.ts +140 -0
  54. package/src/pages/preview-size.ts +33 -0
  55. package/src/pages/session-preview.ts +109 -0
  56. package/src/pages/type-docs.ts +3 -6
  57. package/src/pages/use-session-preview.ts +70 -0
  58. package/src/replica/store.ts +13 -10
  59. package/src/replica/types.ts +7 -2
  60. package/src/site-pointer.ts +24 -5
  61. package/src/site-widget.tsx +2 -3
  62. package/src/studio-config.ts +1 -1
  63. package/src/ui/ListRow.tsx +12 -3
  64. package/src/ui/Page.tsx +12 -1
  65. package/src/workspace-path.ts +35 -3
  66. package/src/chrome/ContentApiPopover.tsx +0 -215
  67. package/src/pages/WorkspaceHomeRedirect.tsx +0 -17
@@ -3,6 +3,14 @@ export function workspaceHomePath(slug: string) {
3
3
  return `/${slug}`;
4
4
  }
5
5
 
6
+ /** The workspace index. Content, media, and settings are deeper routes. */
7
+ export function isWorkspaceHomePath(pathname: string) {
8
+ const slug = cmsWorkspaceSlug(pathname);
9
+ if (!slug) return false;
10
+ const path = pathname.split("?")[0]?.replace(/\/$/, "") || "/";
11
+ return path === `/${slug}`;
12
+ }
13
+
6
14
  export function contentTypePath(slug: string, type: string) {
7
15
  return `/${slug}/content/${type}`;
8
16
  }
@@ -15,6 +23,10 @@ export function mediaPath(slug: string) {
15
23
  return `/${slug}/media`;
16
24
  }
17
25
 
26
+ export function codeSessionPath(slug: string) {
27
+ return `/${slug}/code`;
28
+ }
29
+
18
30
  export function settingsPath(slug: string, page: string) {
19
31
  return `/${slug}/settings/${page}`;
20
32
  }
@@ -71,19 +83,33 @@ export function isWorkspaceMediaPath(pathname: string) {
71
83
  return /\/media\/?$/.test(pathname);
72
84
  }
73
85
 
86
+ export function isWorkspaceCodePath(pathname: string) {
87
+ return /\/code\/?$/.test(pathname);
88
+ }
89
+
74
90
  /** The content file tree belongs on content routes only. */
75
91
  export function isWorkspaceContentPath(pathname: string) {
76
92
  return /\/content(?:\/|$)/.test(pathname);
77
93
  }
78
94
 
95
+ /** The content index. The file tree lives here; the workspace home does not. */
96
+ export function contentIndexPath(slug: string) {
97
+ return `/${slug}/content`;
98
+ }
99
+
100
+ export function isWorkspaceContentIndexPath(pathname: string) {
101
+ const path = pathname.split("?")[0]?.replace(/\/$/, "") || "/";
102
+ return /\/content$/.test(path);
103
+ }
104
+
79
105
  /**
80
106
  * Where the content rail icon navigates.
81
107
  * On a content route it stays put: a document toggles the file tree, and the
82
- * type index is already the file tree.
83
- * Anywhere else (settings, media, connect): the workspace home, which opens the docs.
108
+ * content index is already the file tree.
109
+ * Anywhere else (home, settings, media, connect): `/content`.
84
110
  */
85
111
  export function contentRailHref(pathname: string, slug: string): string | null {
86
- if (!isWorkspaceContentPath(pathname)) return workspaceHomePath(slug);
112
+ if (!isWorkspaceContentPath(pathname)) return contentIndexPath(slug);
87
113
  return null;
88
114
  }
89
115
 
@@ -101,6 +127,12 @@ export function isOrgPickerPath(pathname: string) {
101
127
  return path === "/invite";
102
128
  }
103
129
 
130
+ /** The workspace list and create form. The app sidebar repeats this list. */
131
+ export function isWorkspaceListPath(pathname: string) {
132
+ const path = pathname.split("?")[0]?.replace(/\/$/, "") || "/";
133
+ return path === "/workspaces" || path === "/workspaces/new";
134
+ }
135
+
104
136
  /** CLI approval is a full-screen form, not a workspace. */
105
137
  export function isCliAuthorizePath(pathname: string) {
106
138
  const path = pathname.replace(/\/$/, "") || "/";
@@ -1,215 +0,0 @@
1
- // packages/studio/src/chrome/ContentApiPopover.tsx
2
- import { useEffect, useState } from "react";
3
- import { Popover } from "@base-ui/react/popover";
4
- import * as stylex from "@stylexjs/stylex";
5
- import { publicApiUrl } from "../studio-config";
6
- import { colors, radius, space, type } from "../styles/tokens.stylex";
7
- import { cx, popup, popupClassName } from "../ui/popup";
8
- import { contentApiLinks } from "./content-api-url";
9
- import { ApiIcon } from "./icons";
10
-
11
- const hover = "@media (hover: hover)";
12
-
13
- function CopyUrl({ label, href }: { label: string; href: string }) {
14
- const [copied, setCopied] = useState(false);
15
-
16
- useEffect(() => {
17
- if (!copied) return;
18
- const id = window.setTimeout(() => setCopied(false), 1500);
19
- return () => window.clearTimeout(id);
20
- }, [copied]);
21
-
22
- async function copy() {
23
- try {
24
- await navigator.clipboard.writeText(href);
25
- setCopied(true);
26
- } catch {
27
- setCopied(false);
28
- }
29
- }
30
-
31
- return (
32
- <div {...stylex.props(styles.row)}>
33
- <p {...stylex.props(popup.groupLabel)}>{label}</p>
34
- <div {...stylex.props(styles.urlRow)}>
35
- <a
36
- href={href}
37
- target="_blank"
38
- rel="noreferrer"
39
- {...stylex.props(styles.href)}
40
- >
41
- {href}
42
- </a>
43
- <button
44
- type="button"
45
- onClick={() => void copy()}
46
- aria-label={copied ? "Copied" : `Copy ${label} URL`}
47
- {...stylex.props(styles.copy)}
48
- >
49
- {copied ? "Copied" : "Copy"}
50
- </button>
51
- </div>
52
- </div>
53
- );
54
- }
55
-
56
- export function ContentApiPopover({
57
- workspaceId,
58
- branch,
59
- defaultBranch,
60
- pathname,
61
- search,
62
- }: {
63
- workspaceId: string;
64
- branch?: string | null;
65
- defaultBranch?: string | null;
66
- pathname: string;
67
- search?: string;
68
- }) {
69
- const links = contentApiLinks({
70
- apiUrl: publicApiUrl(),
71
- workspaceId,
72
- branch,
73
- defaultBranch,
74
- pathname,
75
- search,
76
- });
77
- const locked = Boolean(branch && defaultBranch && branch !== defaultBranch);
78
-
79
- return (
80
- <Popover.Root>
81
- <Popover.Trigger aria-label="Content API" className={cx(styles.trigger)}>
82
- <ApiIcon />
83
- <span>API</span>
84
- </Popover.Trigger>
85
- <Popover.Portal>
86
- <Popover.Positioner
87
- side="bottom"
88
- align="start"
89
- sideOffset={6}
90
- className={cx(popup.positioner)}
91
- >
92
- <Popover.Popup
93
- aria-label="Content API"
94
- className={(state) => popupClassName(state, styles.popup)}
95
- >
96
- <p {...stylex.props(popup.groupLabel)}>Content API</p>
97
- <p {...stylex.props(styles.hint)}>
98
- {locked
99
- ? "This branch needs the content token from Settings → General."
100
- : "Default branch is public — no token. URLs use the workspace id."}
101
- </p>
102
- {links.map((link) => (
103
- <CopyUrl key={link.href} label={link.label} href={link.href} />
104
- ))}
105
- </Popover.Popup>
106
- </Popover.Positioner>
107
- </Popover.Portal>
108
- </Popover.Root>
109
- );
110
- }
111
-
112
- const styles = stylex.create({
113
- trigger: {
114
- display: "inline-flex",
115
- alignItems: "center",
116
- gap: space.xs,
117
- minHeight: "24px",
118
- paddingInline: space.xs,
119
- borderWidth: 0,
120
- backgroundColor: "transparent",
121
- color: colors.textMuted,
122
- fontSize: type.xs,
123
- fontFamily: type.mono,
124
- cursor: "pointer",
125
- touchAction: "manipulation",
126
- ":focus-visible": {
127
- outlineWidth: 2,
128
- outlineStyle: "solid",
129
- outlineColor: colors.focus,
130
- outlineOffset: "-2px",
131
- },
132
- [hover]: {
133
- ":hover": {
134
- color: colors.text,
135
- },
136
- },
137
- },
138
- popup: {
139
- width: "380px",
140
- paddingBottom: space.md,
141
- overflow: "hidden",
142
- },
143
- hint: {
144
- margin: 0,
145
- paddingInline: space.lg,
146
- paddingBottom: space.sm,
147
- fontFamily: type.mono,
148
- fontSize: type.xs,
149
- lineHeight: 1.45,
150
- color: colors.textMuted,
151
- },
152
- row: {
153
- display: "flex",
154
- flexDirection: "column",
155
- gap: space.xs,
156
- paddingBottom: space.sm,
157
- },
158
- urlRow: {
159
- display: "flex",
160
- alignItems: "center",
161
- gap: space.sm,
162
- minWidth: 0,
163
- marginInline: space.lg,
164
- },
165
- href: {
166
- minWidth: 0,
167
- flex: 1,
168
- overflow: "hidden",
169
- textOverflow: "ellipsis",
170
- whiteSpace: "nowrap",
171
- color: colors.text,
172
- fontFamily: type.mono,
173
- fontSize: type.xs,
174
- textDecoration: "none",
175
- [hover]: {
176
- ":hover": {
177
- textDecoration: "underline",
178
- },
179
- },
180
- ":focus-visible": {
181
- outlineWidth: 2,
182
- outlineStyle: "solid",
183
- outlineColor: colors.focus,
184
- outlineOffset: 2,
185
- },
186
- },
187
- copy: {
188
- flexShrink: 0,
189
- minWidth: "52px",
190
- minHeight: "24px",
191
- paddingInline: space.sm,
192
- borderWidth: 1,
193
- borderStyle: "solid",
194
- borderColor: colors.border,
195
- borderRadius: radius.md,
196
- backgroundColor: colors.bgSurface,
197
- color: colors.textMuted,
198
- fontSize: type.xs,
199
- fontFamily: type.sans,
200
- cursor: "pointer",
201
- touchAction: "manipulation",
202
- [hover]: {
203
- ":hover": {
204
- color: colors.text,
205
- backgroundColor: colors.bgHover,
206
- },
207
- },
208
- ":focus-visible": {
209
- outlineWidth: 2,
210
- outlineStyle: "solid",
211
- outlineColor: colors.focus,
212
- outlineOffset: 1,
213
- },
214
- },
215
- });
@@ -1,17 +0,0 @@
1
- // packages/studio/src/pages/WorkspaceHomeRedirect.tsx
2
- import { Navigate, useOutletContext } from "react-router";
3
- import type { WorkspaceOutletContext } from "../workspace/types";
4
- import { contentTypePath, settingsPath } from "../workspace-path";
5
-
6
- export function WorkspaceHomeRedirect() {
7
- const { workspace, models } = useOutletContext<WorkspaceOutletContext>();
8
- const first = models[0]?.type;
9
- if (first) {
10
- return (
11
- <Navigate to={contentTypePath(workspace.slug, first)} replace />
12
- );
13
- }
14
- return (
15
- <Navigate to={settingsPath(workspace.slug, "schema")} replace />
16
- );
17
- }