@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
package/dist/studio.css CHANGED
@@ -27,6 +27,10 @@
27
27
  padding: 0;
28
28
  }
29
29
 
30
+ .x1i3ajwb {
31
+ padding: 2px;
32
+ }
33
+
30
34
  .xk7greg {
31
35
  padding: var(--x1ft7ks7);
32
36
  }
@@ -35,6 +39,10 @@
35
39
  padding: var(--x1lzhbes);
36
40
  }
37
41
 
42
+ .x12u58kn {
43
+ padding: var(--x1o7ezzp);
44
+ }
45
+
38
46
  .xue2yr4 {
39
47
  padding: var(--x1uiwg42);
40
48
  }
@@ -149,10 +157,18 @@
149
157
  border-width: 0;
150
158
  }
151
159
 
160
+ .xvndefy {
161
+ border-width: 1.5px;
162
+ }
163
+
152
164
  .xmkeg23 {
153
165
  border-width: 1px;
154
166
  }
155
167
 
168
+ .xdh2fpr {
169
+ border-width: 2px;
170
+ }
171
+
156
172
  .x9r2w2z {
157
173
  border-width: 3px;
158
174
  }
@@ -209,6 +225,10 @@
209
225
  gap: var(--xvc7yhn);
210
226
  }
211
227
 
228
+ .x1osaytk {
229
+ grid-column: 1 / -1;
230
+ }
231
+
212
232
  .x1xohano {
213
233
  inset-block: -5px;
214
234
  }
@@ -245,10 +265,6 @@
245
265
  margin-inline: auto;
246
266
  }
247
267
 
248
- .x1nmszcn {
249
- margin-inline: var(--x1ft7ks7);
250
- }
251
-
252
268
  .x1agxhcx {
253
269
  margin-inline: var(--x1o7ezzp);
254
270
  }
@@ -289,10 +305,18 @@
289
305
  padding-block: 2px;
290
306
  }
291
307
 
308
+ .xf4egzd {
309
+ padding-block: 3px;
310
+ }
311
+
292
312
  .x62rkhy {
293
313
  padding-block: 48px;
294
314
  }
295
315
 
316
+ .x12ulsxz {
317
+ padding-block: 4px;
318
+ }
319
+
296
320
  .x1t0vds8 {
297
321
  padding-block: 6px;
298
322
  }
@@ -481,6 +505,10 @@
481
505
  align-items: stretch;
482
506
  }
483
507
 
508
+ .xpvyfi4 {
509
+ align-self: flex-end;
510
+ }
511
+
484
512
  .xqcrz7y {
485
513
  align-self: flex-start;
486
514
  }
@@ -501,6 +529,10 @@
501
529
  animation-duration: .7s;
502
530
  }
503
531
 
532
+ .xemfg65 {
533
+ animation-duration: .8s;
534
+ }
535
+
504
536
  .x1m9vv7p {
505
537
  animation-duration: 1.2s;
506
538
  }
@@ -525,6 +557,10 @@
525
557
  animation-name: x1tbvpe3-B;
526
558
  }
527
559
 
560
+ .x1aerksh {
561
+ animation-name: x1wc8ddo-B;
562
+ }
563
+
528
564
  .xt3aoqv {
529
565
  animation-name: x1ymhlvw-B;
530
566
  }
@@ -617,6 +653,10 @@
617
653
  background-color: #fafafa;
618
654
  }
619
655
 
656
+ .x83z2og {
657
+ background-color: #fff;
658
+ }
659
+
620
660
  .xtwfq29 {
621
661
  background-color: currentColor;
622
662
  }
@@ -773,6 +813,10 @@
773
813
  box-shadow: 0 16px 48px #18181b33;
774
814
  }
775
815
 
816
+ .x1dej7w9 {
817
+ box-shadow: 0 1px 2px #00000014;
818
+ }
819
+
776
820
  .xtpupu6 {
777
821
  box-shadow: 0 1px 2px #18181b33;
778
822
  }
@@ -921,6 +965,14 @@
921
965
  display: inline-flex;
922
966
  }
923
967
 
968
+ .x1t1x2f9 {
969
+ flex-basis: 0%;
970
+ }
971
+
972
+ .xdl72j9 {
973
+ flex-basis: auto;
974
+ }
975
+
924
976
  .xdt5ytf {
925
977
  flex-direction: column;
926
978
  }
@@ -933,6 +985,10 @@
933
985
  flex-grow: 0;
934
986
  }
935
987
 
988
+ .x1iyjqo2 {
989
+ flex-grow: 1;
990
+ }
991
+
936
992
  .x2lah0s {
937
993
  flex-shrink: 0;
938
994
  }
@@ -965,10 +1021,22 @@
965
1021
  font-size: .92em;
966
1022
  }
967
1023
 
1024
+ .x1j6dyjg {
1025
+ font-size: 11px;
1026
+ }
1027
+
1028
+ .xfifm61 {
1029
+ font-size: 12px;
1030
+ }
1031
+
968
1032
  .xneq65o {
969
1033
  font-size: 28px;
970
1034
  }
971
1035
 
1036
+ .x1s3cmhv {
1037
+ font-size: 32px;
1038
+ }
1039
+
972
1040
  .xdzlmae {
973
1041
  font-size: 8px;
974
1042
  }
@@ -1029,6 +1097,10 @@
1029
1097
  grid-template-columns: 1fr 1fr;
1030
1098
  }
1031
1099
 
1100
+ .x19ohwaf {
1101
+ grid-template-columns: 1fr minmax(0, 520px) 1fr;
1102
+ }
1103
+
1032
1104
  .x1d5a2s6 {
1033
1105
  grid-template-columns: 7rem 1fr;
1034
1106
  }
@@ -1061,6 +1133,14 @@
1061
1133
  justify-content: space-between;
1062
1134
  }
1063
1135
 
1136
+ .x1qab1bc {
1137
+ justify-self: end;
1138
+ }
1139
+
1140
+ .x1lqcxt8 {
1141
+ justify-self: start;
1142
+ }
1143
+
1064
1144
  .xjat59b {
1065
1145
  letter-spacing: -.01em;
1066
1146
  }
@@ -1117,6 +1197,10 @@
1117
1197
  line-height: 1.55;
1118
1198
  }
1119
1199
 
1200
+ .x1dbl2gt {
1201
+ line-height: 1.6;
1202
+ }
1203
+
1120
1204
  .xo5v014 {
1121
1205
  line-height: 1;
1122
1206
  }
@@ -1195,6 +1279,10 @@
1195
1279
  outline-color: var(--x1nzi8h4);
1196
1280
  }
1197
1281
 
1282
+ .x1e6zrab {
1283
+ outline-color: var(--xz3qvk5);
1284
+ }
1285
+
1198
1286
  .x1ewu8gn {
1199
1287
  outline-offset: 3px;
1200
1288
  }
@@ -1203,6 +1291,10 @@
1203
1291
  outline-style: solid;
1204
1292
  }
1205
1293
 
1294
+ .x1qgsegg {
1295
+ outline-width: 1px;
1296
+ }
1297
+
1206
1298
  .x1de99jn {
1207
1299
  outline-width: 2px;
1208
1300
  }
@@ -1255,6 +1347,10 @@
1255
1347
  row-gap: 40px;
1256
1348
  }
1257
1349
 
1350
+ .xeq4nuv {
1351
+ tab-size: 2;
1352
+ }
1353
+
1258
1354
  .x2b8uid {
1259
1355
  text-align: center;
1260
1356
  }
@@ -1542,6 +1638,14 @@
1542
1638
  outline-width: 2px;
1543
1639
  }
1544
1640
 
1641
+ .x1ke7wyk:disabled {
1642
+ color: var(--xw4dauw);
1643
+ }
1644
+
1645
+ .x14xq8pj:disabled {
1646
+ color: var(--xz3qvk5);
1647
+ }
1648
+
1545
1649
  .xtgyt42:disabled {
1546
1650
  cursor: default;
1547
1651
  }
@@ -1550,6 +1654,10 @@
1550
1654
  cursor: not-allowed;
1551
1655
  }
1552
1656
 
1657
+ .x1bq4at4:disabled {
1658
+ opacity: .3;
1659
+ }
1660
+
1553
1661
  .x2wzlov:disabled {
1554
1662
  opacity: .35;
1555
1663
  }
@@ -1605,6 +1713,10 @@
1605
1713
  animation-duration: 0s;
1606
1714
  }
1607
1715
 
1716
+ .xy1mfq8.xy1mfq8 {
1717
+ animation-duration: 2.4s;
1718
+ }
1719
+
1608
1720
  .x1aquc0h.x1aquc0h {
1609
1721
  animation-name: none;
1610
1722
  }
@@ -1657,6 +1769,14 @@
1657
1769
  }
1658
1770
 
1659
1771
  @media (hover: hover) {
1772
+ .x1avvw9k.x1avvw9k:hover:not(:disabled) {
1773
+ background-color: var(--xeg52a);
1774
+ }
1775
+
1776
+ .x1caf6yr.x1caf6yr:hover:not(:disabled) {
1777
+ color: var(--x5y0tk9);
1778
+ }
1779
+
1660
1780
  .x1e7vmwq.x1e7vmwq:hover [data-folder-action] {
1661
1781
  opacity: 1;
1662
1782
  }
@@ -1762,6 +1882,10 @@
1762
1882
  border-right-width: 1px;
1763
1883
  }
1764
1884
 
1885
+ .x17ycamp {
1886
+ border-top-color: var(--x11n7hvk);
1887
+ }
1888
+
1765
1889
  .xmzxa0n {
1766
1890
  border-top-color: var(--xz3qvk5);
1767
1891
  }
@@ -2030,6 +2154,10 @@
2030
2154
  max-height: 180px;
2031
2155
  }
2032
2156
 
2157
+ .x1hkcv85 {
2158
+ max-height: 200px;
2159
+ }
2160
+
2033
2161
  .x1983irj {
2034
2162
  max-height: 220px;
2035
2163
  }
@@ -2042,6 +2170,10 @@
2042
2170
  max-height: 280px;
2043
2171
  }
2044
2172
 
2173
+ .x1h9acoj {
2174
+ max-height: min(320px,var(--available-height));
2175
+ }
2176
+
2045
2177
  .x10todyo {
2046
2178
  max-height: min(52vh, 440px);
2047
2179
  }
@@ -2078,6 +2210,10 @@
2078
2210
  max-width: 220px;
2079
2211
  }
2080
2212
 
2213
+ .x16em4ew {
2214
+ max-width: 240px;
2215
+ }
2216
+
2081
2217
  .x14lruf5 {
2082
2218
  max-width: 260px;
2083
2219
  }
@@ -2098,6 +2234,10 @@
2098
2234
  max-width: 440px;
2099
2235
  }
2100
2236
 
2237
+ .x1pfbilc {
2238
+ max-width: 460px;
2239
+ }
2240
+
2101
2241
  .xh9uqaf {
2102
2242
  max-width: 60%;
2103
2243
  }
@@ -2122,6 +2262,10 @@
2122
2262
  max-width: 920px;
2123
2263
  }
2124
2264
 
2265
+ .xyzno7u {
2266
+ max-width: 960px;
2267
+ }
2268
+
2125
2269
  .x2lwn1j {
2126
2270
  min-height: 0;
2127
2271
  }
@@ -2174,6 +2318,10 @@
2174
2318
  min-height: 44px;
2175
2319
  }
2176
2320
 
2321
+ .x1ujl9rh {
2322
+ min-height: 84px;
2323
+ }
2324
+
2177
2325
  .x562ti3 {
2178
2326
  min-height: 8rem;
2179
2327
  }
@@ -2210,6 +2358,10 @@
2210
2358
  min-width: 200px;
2211
2359
  }
2212
2360
 
2361
+ .x12rczxh {
2362
+ min-width: 220px;
2363
+ }
2364
+
2213
2365
  .x1f4i7yj {
2214
2366
  min-width: 252px;
2215
2367
  }
@@ -2222,10 +2374,6 @@
2222
2374
  min-width: 48px;
2223
2375
  }
2224
2376
 
2225
- .x9hgts1 {
2226
- min-width: 52px;
2227
- }
2228
-
2229
2377
  .xzhy1st {
2230
2378
  min-width: 8rem;
2231
2379
  }
@@ -2246,6 +2394,10 @@
2246
2394
  overflow-y: auto;
2247
2395
  }
2248
2396
 
2397
+ .x10wlt62 {
2398
+ overflow-y: hidden;
2399
+ }
2400
+
2249
2401
  .x18d9i69 {
2250
2402
  padding-bottom: 0;
2251
2403
  }
@@ -2254,6 +2406,10 @@
2254
2406
  padding-bottom: 2px;
2255
2407
  }
2256
2408
 
2409
+ .xq1608w {
2410
+ padding-bottom: 36px;
2411
+ }
2412
+
2257
2413
  .x2f2w3t {
2258
2414
  padding-bottom: calc(var(--x1uiwg42) + 12px);
2259
2415
  }
@@ -2374,6 +2530,10 @@
2374
2530
  right: var(--x1lzhbes);
2375
2531
  }
2376
2532
 
2533
+ .x1gki5z1 {
2534
+ right: var(--x1o7ezzp);
2535
+ }
2536
+
2377
2537
  .x13vifvy {
2378
2538
  top: 0;
2379
2539
  }
@@ -2458,6 +2618,10 @@
2458
2618
  width: 24px;
2459
2619
  }
2460
2620
 
2621
+ .x1hfn5x7 {
2622
+ width: 260px;
2623
+ }
2624
+
2461
2625
  .x23j0i4 {
2462
2626
  width: 26px;
2463
2627
  }
@@ -2482,12 +2646,12 @@
2482
2646
  width: 32px;
2483
2647
  }
2484
2648
 
2485
- .x14qfxbe {
2486
- width: 36px;
2649
+ .x1cvmir6 {
2650
+ width: 360px;
2487
2651
  }
2488
2652
 
2489
- .xwl9fdd {
2490
- width: 380px;
2653
+ .x14qfxbe {
2654
+ width: 36px;
2491
2655
  }
2492
2656
 
2493
2657
  .x1q22l7i {
@@ -2772,6 +2936,16 @@
2772
2936
  }
2773
2937
  }
2774
2938
 
2939
+ @keyframes x1wc8ddo-B {
2940
+ from {
2941
+ transform: rotate(0);
2942
+ }
2943
+
2944
+ to {
2945
+ transform: rotate(360deg);
2946
+ }
2947
+ }
2948
+
2775
2949
  @keyframes x1n620qs-B {
2776
2950
  0%, 100% {
2777
2951
  transform: translateX(.001%);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backmatter/studio",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "description": "The Backmatter editing surface: document editor, AI chat, publish. Mounted on your site or hosted on the dashboard.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "dependencies": {
58
58
  "@ai-sdk/react": "^4.0.100",
59
- "@backmatter/core": "^0.0.28",
59
+ "@backmatter/core": "^0.0.33",
60
60
  "@base-ui/react": "^1.6.0",
61
61
  "@central-icons-react/round-outlined-radius-2-stroke-1.5": "1.2.15",
62
62
  "@dnd-kit/abstract": "^0.5.0",
@@ -3,7 +3,7 @@
3
3
  // a site Studio mounts the same tree under its base path.
4
4
  import { Route } from "react-router";
5
5
  import { WorkspaceLayout } from "./pages/WorkspaceLayout";
6
- import { WorkspaceHomeRedirect } from "./pages/WorkspaceHomeRedirect";
6
+ import { WorkspaceHomePage } from "./pages/WorkspaceHomePage";
7
7
  import { TypeDocsPage } from "./pages/TypeDocsPage";
8
8
  import { SettingsGeneralPage } from "./pages/SettingsGeneralPage";
9
9
  import { SettingsUsagePage } from "./pages/SettingsUsagePage";
@@ -15,6 +15,7 @@ import { SettingsUsersPage } from "./pages/SettingsUsersPage";
15
15
  import { GithubConnectPage } from "./pages/GithubConnectPage";
16
16
  import { DocEditorPage } from "./pages/DocEditorPage";
17
17
  import { MediaGalleryPage } from "./pages/MediaGalleryPage";
18
+ import CodeSessionPage from "./pages/CodeSessionPage";
18
19
 
19
20
  /**
20
21
  * Returns `<Route>` elements. Spread the result inside a `<Routes>`; react-router
@@ -23,8 +24,10 @@ import { MediaGalleryPage } from "./pages/MediaGalleryPage";
23
24
  export function workspaceRoutes() {
24
25
  return (
25
26
  <Route path="/:workspaceSlug" element={<WorkspaceLayout />}>
26
- <Route index element={<WorkspaceHomeRedirect />} />
27
+ <Route index element={<WorkspaceHomePage />} />
28
+ <Route path="content" element={<TypeDocsPage />} />
27
29
  <Route path="content/:type" element={<TypeDocsPage />} />
30
+ <Route path="code" element={<CodeSessionPage />} />
28
31
  <Route element={<SettingsLayout />}>
29
32
  <Route path="settings" element={<SettingsIndexRedirect />} />
30
33
  <Route path="settings/general" element={<SettingsGeneralPage />} />
package/src/api.ts CHANGED
@@ -23,6 +23,7 @@ async function api<T>(path: string, init?: RequestInit): Promise<T> {
23
23
  const body = (await res.json().catch(() => ({}))) as {
24
24
  error?: string;
25
25
  details?: string[];
26
+ paths?: string[];
26
27
  };
27
28
  if (res.status === 401 || res.status === 426) reportUnauthorized(res.status, body.error);
28
29
  const code = body.error ?? `request failed: ${res.status}`;
@@ -33,7 +34,7 @@ async function api<T>(path: string, init?: RequestInit): Promise<T> {
33
34
  const err = new Error(`${code}${detailSuffix}`) as ApiError;
34
35
  err.status = res.status;
35
36
  err.code = body.error;
36
- err.details = body.details;
37
+ err.details = body.details ?? body.paths;
37
38
  throw err;
38
39
  }
39
40
  return res.json() as Promise<T>;
@@ -371,6 +372,8 @@ export type SchemaPayload = {
371
372
  singleton?: boolean;
372
373
  indexSlug?: string;
373
374
  format?: "mdx" | "page";
375
+ external?: boolean;
376
+ template?: string;
374
377
  fields: Record<string, SchemaField>;
375
378
  }>;
376
379
  blocks?: BlockSnapshot[];
@@ -400,10 +403,21 @@ export type GitChange = {
400
403
  type: string;
401
404
  slug: string;
402
405
  status: ChangeStatus;
406
+ /** Working-tree edit time. Present on git status; absent when derived from the document list. */
407
+ updatedAt?: string;
403
408
  };
404
409
 
405
410
  export type GitHead = { sha: string; updatedAt: string };
406
411
 
412
+ /** A commit on the branch, newest first. `cms` means Studio wrote it. */
413
+ export type RecentCommit = {
414
+ sha: string;
415
+ message: string;
416
+ author: string;
417
+ committedAt: string;
418
+ cms: boolean;
419
+ };
420
+
407
421
  /** `git status` for the current user on their checked-out branch. */
408
422
  export type GitState = {
409
423
  defaultBranch: string;
@@ -413,6 +427,10 @@ export type GitState = {
413
427
  githubError?: "installation_missing" | "github_list_failed" | null;
414
428
  head: GitHead | null;
415
429
  changes: GitChange[];
430
+ /** Last git status list, including deletions and edit times. */
431
+ fetchedChanges?: GitChange[];
432
+ /** Latest commits on the branch, once git status has been fetched. */
433
+ recentCommits?: RecentCommit[];
416
434
  };
417
435
 
418
436
  export function countConflicts(state: Pick<GitState, "changes"> | null | undefined) {
@@ -451,7 +469,6 @@ export type CommitResult = {
451
469
  message: string;
452
470
  branch: string;
453
471
  files: Array<{
454
- locale: string;
455
472
  type: string;
456
473
  slug: string;
457
474
  githubPath: string;
@@ -467,6 +484,32 @@ export function commitWorkspace(workspaceId: string, message?: string) {
467
484
  });
468
485
  }
469
486
 
487
+ export type CodeSessionEditList = {
488
+ files: Array<{ path: string; deleted: boolean }>;
489
+ skipped: Array<{ path: string; reason: string }>;
490
+ foldPending: boolean;
491
+ };
492
+
493
+ export function fetchCodeSessionEdits(workspaceId: string, sessionId: string) {
494
+ return api<CodeSessionEditList>(
495
+ `/workspaces/${workspaceId}/code-sessions/${sessionId}/edits`,
496
+ );
497
+ }
498
+
499
+ export function commitCodeSessionEdits(
500
+ workspaceId: string,
501
+ sessionId: string,
502
+ message?: string,
503
+ ) {
504
+ return api<{ ok: true; commitSha: string; foldErrors: string[] }>(
505
+ `/workspaces/${workspaceId}/code-sessions/${sessionId}/commit`,
506
+ {
507
+ method: "POST",
508
+ body: JSON.stringify(message?.trim() ? { message: message.trim() } : {}),
509
+ },
510
+ );
511
+ }
512
+
470
513
  /** Default commit message the server uses when none is given (mirrored for the placeholder). */
471
514
  export function defaultCommitMessage(changes: GitChange[]) {
472
515
  if (changes.length === 1) {
@@ -588,7 +631,7 @@ export function fetchDocNav(
588
631
  q?: string;
589
632
  limit?: number;
590
633
  offset?: number;
591
- current?: { locale: string; type: string; slug: string };
634
+ current?: { type: string; slug: string };
592
635
  } = {},
593
636
  ) {
594
637
  const qs = new URLSearchParams({ nav: "1" });
@@ -702,6 +745,8 @@ export type ChatThread = {
702
745
  workspaceId: string;
703
746
  userId: string;
704
747
  branch: string;
748
+ /** Null for an editor thread; a code session id for a session thread. */
749
+ codeSessionId?: string | null;
705
750
  title: string;
706
751
  createdAt: string;
707
752
  updatedAt: string;
@@ -713,7 +758,6 @@ export type ChatChangeDiff = {
713
758
  added: number;
714
759
  removed: number;
715
760
  renamedFrom?: string;
716
- renamedFromLocale?: string;
717
761
  hunks: Array<{
718
762
  oldStart: number;
719
763
  oldLines: number;
@@ -730,11 +774,11 @@ export type ChatChangeSummary = {
730
774
  workspaceId: string;
731
775
  userId: string;
732
776
  branch: string;
733
- resourceKind: "content";
734
- locale: string;
777
+ resourceKind: "content" | "code";
778
+ path: string | null;
735
779
  type: string;
736
780
  slug: string;
737
- op: "write" | "create";
781
+ op: "write" | "create" | "delete";
738
782
  beforeSource: ContentSource | null;
739
783
  diff: ChatChangeDiff | null;
740
784
  status: ChatChangeStatus;
@@ -743,14 +787,23 @@ export type ChatChangeSummary = {
743
787
  summary?: string;
744
788
  };
745
789
 
746
- export function fetchThreads(workspaceId: string) {
747
- return api<{ threads: ChatThread[] }>(`/workspaces/${workspaceId}/ai/threads`);
790
+ export function fetchThreads(workspaceId: string, codeSessionId?: string) {
791
+ const query = codeSessionId
792
+ ? `?codeSessionId=${encodeURIComponent(codeSessionId)}`
793
+ : "";
794
+ return api<{ threads: ChatThread[] }>(`/workspaces/${workspaceId}/ai/threads${query}`);
748
795
  }
749
796
 
750
- export function createThread(workspaceId: string, title?: string) {
797
+ export function createThread(
798
+ workspaceId: string,
799
+ opts?: { title?: string; codeSessionId?: string },
800
+ ) {
801
+ const body: { title?: string; codeSessionId?: string } = {};
802
+ if (opts?.title) body.title = opts.title;
803
+ if (opts?.codeSessionId) body.codeSessionId = opts.codeSessionId;
751
804
  return api<{ thread: ChatThread }>(`/workspaces/${workspaceId}/ai/threads`, {
752
805
  method: "POST",
753
- body: JSON.stringify(title ? { title } : {}),
806
+ body: JSON.stringify(body),
754
807
  });
755
808
  }
756
809
 
@@ -28,18 +28,14 @@ export function ChangeCard({
28
28
  }) {
29
29
  const [expanded, setExpanded] = useState(false);
30
30
  const nav = useWorkspaceNav();
31
- const label = change.locale
32
- ? `${change.locale}/${change.type}/${change.slug}`
33
- : `${change.type}/${change.slug}`;
34
- const to = nav
35
- ? contentDocPath(
36
- nav.workspaceSlug,
37
- change.type,
38
- change.slug,
39
- change.locale,
40
- nav.defaultLocale,
41
- )
42
- : "#";
31
+ const label =
32
+ change.resourceKind === "code"
33
+ ? (change.path ?? "")
34
+ : `${change.type}/${change.slug}`;
35
+ const to =
36
+ change.resourceKind === "code" || !nav
37
+ ? "#"
38
+ : contentDocPath(nav.workspaceSlug, change.type, change.slug);
43
39
  const status = change.status;
44
40
 
45
41
  return (
@@ -71,9 +67,7 @@ export function ChangeCard({
71
67
  )}
72
68
  {change.diff?.renamedFrom ? (
73
69
  <span {...stylex.props(styles.badge)}>
74
- {change.diff.renamedFromLocale
75
- ? `${change.diff.renamedFromLocale}/${change.diff.renamedFrom} → ${change.locale}/${change.slug}`
76
- : `${change.diff.renamedFrom} → ${change.slug}`}
70
+ {`${change.diff.renamedFrom} → ${change.slug}`}
77
71
  </span>
78
72
  ) : change.op === "create" ? (
79
73
  <span {...stylex.props(styles.badge)}>new</span>