@jxsuite/studio 3.0.0 → 4.1.0

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 (197) hide show
  1. package/README.md +5 -5
  2. package/dist/chunks/{2020-xp8vnwma.js → 2020-n6tfwnda.js} +2 -2
  3. package/dist/chunks/Overlay.dev-etqjwyf9.js +23 -0
  4. package/dist/chunks/cf-account-picker-en86v7x7.js +163 -0
  5. package/dist/chunks/{cf-settings-zj2cj1ar.js → cf-settings-45fgyx8y.js} +6 -5
  6. package/dist/chunks/{components-z6cnkyy1.js → components-zmx10t62.js} +6 -5
  7. package/dist/chunks/content-source-48xg86wj.js +49 -0
  8. package/dist/chunks/{convert-to-repeater-6vvx96n2.js → convert-to-repeater-43tfn7cs.js} +33 -25
  9. package/dist/chunks/entry-commands-e63f1xc0.js +46 -0
  10. package/dist/chunks/{extension-sections-kwzvpemr.js → extension-sections-txv08xxg.js} +34 -26
  11. package/dist/chunks/file-ops-wbh4hhsx.js +48 -0
  12. package/dist/chunks/files-3kfkwhk4.js +93 -0
  13. package/dist/chunks/{focus-trap.esm-ggawf1hy.js → focus-trap.esm-9t2drp0g.js} +6 -5
  14. package/dist/chunks/{format-host-8fr3545e.js → format-host-w16q81tf.js} +3 -3
  15. package/dist/chunks/fs-events-hrrg328g.js +21 -0
  16. package/dist/chunks/{git-panel-g01d93vc.js → git-panel-532whr1y.js} +22 -16
  17. package/dist/chunks/{index-nj44jpzj.js → index-wcnt3r9g.js} +2 -2
  18. package/dist/chunks/{jx-validate-jj9rnt4n.js → jx-validate-8er6gbxy.js} +6 -4
  19. package/dist/chunks/layers-panel-07hm7g5j.js +67 -0
  20. package/dist/chunks/layers-q75tj2mf.js +45 -0
  21. package/dist/chunks/lit-html-j055mkh8.js +22 -0
  22. package/dist/chunks/monaco-buffer-dqw0xxpx.js +25 -0
  23. package/dist/chunks/{monaco-setup-a5wkmbgh.js → monaco-setup-vx9bhp1d.js} +4 -4
  24. package/dist/chunks/notify-yjm79n4s.js +30 -0
  25. package/dist/chunks/parse-24d0gay3.js +19 -0
  26. package/dist/chunks/platform-ba1j1gbh.js +16 -0
  27. package/dist/chunks/publish-panel-v9st96ja.js +29 -0
  28. package/dist/chunks/quick-search-zccjp6gv.js +66 -0
  29. package/dist/chunks/references-3qx8fkyp.js +29 -0
  30. package/dist/chunks/right-panel-vm3b6kwy.js +46 -0
  31. package/dist/chunks/sp-overlay.dev-qgvhnxf4.js +18 -0
  32. package/dist/chunks/sp-popover.dev-n2etmqdp.js +9 -0
  33. package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +15 -0
  34. package/dist/chunks/sp-tooltip.dev-4sastdnw.js +15 -0
  35. package/dist/chunks/{sp-tray.dev-64v4pxw8.js → sp-tray.dev-tkbscm06.js} +5 -5
  36. package/dist/chunks/{studio-wdt9y92a.js → studio-0bq6eh7v.js} +2 -2
  37. package/dist/chunks/studio-1ngbejft.js +181 -0
  38. package/dist/chunks/studio-1wagb550.js +118 -0
  39. package/dist/chunks/{studio-j595e748.js → studio-2b946z26.js} +172 -95
  40. package/dist/chunks/{studio-pkp61mh8.js → studio-3bqpr0zh.js} +25 -17
  41. package/dist/chunks/{studio-k4jvjcfx.js → studio-4b5z1x43.js} +46 -12
  42. package/dist/chunks/{studio-ry1vz5sv.js → studio-4n2b5gzb.js} +15 -13
  43. package/dist/chunks/{studio-p5dja96s.js → studio-4r1pv6zf.js} +2 -2
  44. package/dist/chunks/{studio-kvfx9z2c.js → studio-4rzyrtjz.js} +70 -31
  45. package/dist/chunks/{studio-tzx3xmvx.js → studio-5fx3g9jg.js} +33 -52
  46. package/dist/chunks/{studio-rh1cnamv.js → studio-5myknm0a.js} +565 -135
  47. package/dist/chunks/studio-6yw4y3my.js +91 -0
  48. package/dist/chunks/{studio-0rqqhrdy.js → studio-7nte2hj4.js} +2 -2
  49. package/dist/chunks/studio-92ab2jqe.js +282 -0
  50. package/dist/chunks/{studio-6z5eyjwn.js → studio-950k0dx6.js} +72 -135
  51. package/dist/chunks/{studio-sas7mt3k.js → studio-9ab8gqsm.js} +2 -2
  52. package/dist/chunks/studio-9n4mrmnb.js +184 -0
  53. package/dist/chunks/{studio-hcxzyk3x.js → studio-ahn3pdgx.js} +6 -6
  54. package/dist/chunks/studio-b55y6ewx.js +498 -0
  55. package/dist/chunks/{studio-s519zc99.js → studio-bgxt93ga.js} +3 -3
  56. package/dist/chunks/studio-cft9r4r7.js +119 -0
  57. package/dist/chunks/{studio-dc8s8heb.js → studio-cmb03bg5.js} +6904 -8321
  58. package/dist/chunks/studio-dm19q43r.js +195 -0
  59. package/dist/chunks/{studio-2x9zbxmx.js → studio-ef49pnzx.js} +11 -9
  60. package/dist/chunks/{studio-ay8wcr2b.js → studio-eqq1w638.js} +7 -9
  61. package/dist/chunks/{studio-5efrtn1m.js → studio-ew0djmhg.js} +23 -19
  62. package/dist/chunks/{studio-tr20m8g8.js → studio-f3wqjtk5.js} +39 -3
  63. package/dist/chunks/{studio-xntfdy1x.js → studio-fbf92ckf.js} +36 -23
  64. package/dist/chunks/{studio-4q0wznzf.js → studio-gcye7qka.js} +442 -126
  65. package/dist/chunks/{studio-7p35254k.js → studio-gfgb8pe6.js} +36 -193
  66. package/dist/chunks/studio-jc994dnr.js +1066 -0
  67. package/dist/chunks/{studio-hj2se583.js → studio-jkftxyvz.js} +69 -3423
  68. package/dist/chunks/{studio-5x5774ak.js → studio-kryvsw6j.js} +3 -3
  69. package/dist/chunks/{studio-kfpek536.js → studio-kyh9avvv.js} +2 -2
  70. package/dist/chunks/{studio-twsafr1e.js → studio-m3hwn7y1.js} +3 -3
  71. package/dist/chunks/studio-msd847zn.js +1315 -0
  72. package/dist/chunks/{studio-g5gdqnyd.js → studio-mtfnaqs9.js} +19 -19
  73. package/dist/chunks/{studio-7ejemhej.js → studio-n6j9t10q.js} +6 -6
  74. package/dist/chunks/{studio-vhk5fmfv.js → studio-n8b9wqwy.js} +5 -5
  75. package/dist/chunks/{studio-cjm7fdjf.js → studio-p4ngc046.js} +8 -104
  76. package/dist/chunks/studio-q43a5y9h.js +394 -0
  77. package/dist/chunks/studio-q7v10cg2.js +106 -0
  78. package/dist/chunks/{studio-6kv3apa9.js → studio-q9jd1a60.js} +27 -11
  79. package/dist/chunks/studio-qcy05dp8.js +1743 -0
  80. package/dist/chunks/{studio-a0yp7syn.js → studio-qjy2z9bv.js} +29 -132
  81. package/dist/chunks/{studio-2w11grav.js → studio-qq6jym04.js} +3 -3
  82. package/dist/chunks/{studio-3fk1ac9z.js → studio-rdx8jcxy.js} +4 -4
  83. package/dist/chunks/{studio-4mqb9a0j.js → studio-rn9ta2mz.js} +4 -4
  84. package/dist/chunks/studio-t7qb9db8.js +665 -0
  85. package/dist/chunks/studio-tmc5emrw.js +8 -0
  86. package/dist/chunks/{studio-r5xbtctf.js → studio-trxrabqf.js} +292 -2297
  87. package/dist/chunks/studio-wcy8trfx.js +263 -0
  88. package/dist/chunks/{studio-3dkcd4c5.js → studio-x4qkdat5.js} +62 -57
  89. package/dist/chunks/studio-x813zp48.js +195 -0
  90. package/dist/chunks/{studio-qpfq7xhm.js → studio-xrapxwwc.js} +22 -285
  91. package/dist/chunks/{studio-xm3dbxvn.js → studio-zczps70q.js} +2 -2
  92. package/dist/chunks/{studio-8ezdegcx.js → studio-zqazsct2.js} +40 -36
  93. package/dist/chunks/workspace-fjx52ryv.js +89 -0
  94. package/dist/iframe-entry.js +141 -56
  95. package/dist/manifest.json +7 -0
  96. package/dist/studio.js +2605 -1950
  97. package/favicon.ico +0 -0
  98. package/index.html +1 -0
  99. package/package.json +20 -18
  100. package/src/about/about-modal.ts +4 -3
  101. package/src/browse/library-pane.ts +57 -3
  102. package/src/canvas/canvas-render.ts +23 -1
  103. package/src/canvas/iframe-patch.ts +47 -21
  104. package/src/canvas/iframe-render.ts +22 -6
  105. package/src/commands/app-commands.ts +4 -0
  106. package/src/commands/command-args.ts +29 -0
  107. package/src/commands/context.ts +15 -1
  108. package/src/commands/defaults.ts +29 -1
  109. package/src/commands/levels.ts +16 -0
  110. package/src/commands/live-context.ts +3 -0
  111. package/src/content/collection-match.ts +267 -0
  112. package/src/content/entry-commands.ts +38 -29
  113. package/src/content/entry-fields.ts +6 -0
  114. package/src/editor/shortcuts.ts +5 -0
  115. package/src/files/file-ops.ts +10 -42
  116. package/src/files/files.ts +487 -59
  117. package/src/files/fs-events.ts +9 -0
  118. package/src/files/gitignore.ts +432 -0
  119. package/src/files/media-paths.ts +13 -42
  120. package/src/files/media-usage.ts +41 -131
  121. package/src/files/serialize-document.ts +60 -0
  122. package/src/format/convert-file.ts +511 -0
  123. package/src/format/format-choices.ts +199 -0
  124. package/src/grid/grid-view.ts +16 -2
  125. package/src/hosting/document.ts +2 -0
  126. package/src/hosting/layout.ts +8 -0
  127. package/src/media/media-open.ts +58 -0
  128. package/src/media/media-pane.ts +310 -0
  129. package/src/new-project/import-tab.ts +357 -163
  130. package/src/new-project/location-fields.ts +8 -5
  131. package/src/new-project/new-project-modal.ts +27 -34
  132. package/src/packages/ensure-deps.ts +14 -7
  133. package/src/packages/jxsuite-update.ts +11 -11
  134. package/src/page-params.ts +10 -46
  135. package/src/panels/activity-bar.ts +45 -16
  136. package/src/panels/ai-chat/attached-context.ts +6 -1
  137. package/src/panels/ai-chat/chat-view.ts +255 -13
  138. package/src/panels/ai-chat/composer.ts +31 -97
  139. package/src/panels/ai-panel.ts +116 -2
  140. package/src/panels/navigator-panels.ts +7 -6
  141. package/src/panels/settings-menu.ts +799 -0
  142. package/src/panels/toolbar.ts +135 -15
  143. package/src/platforms/cloud.ts +261 -70
  144. package/src/platforms/devserver.ts +67 -33
  145. package/src/preview/preview-overlay.ts +226 -0
  146. package/src/publish/publish-panel.ts +62 -2
  147. package/src/services/ai-ask.ts +247 -0
  148. package/src/services/ai-import-tools.ts +532 -0
  149. package/src/services/ai-models.ts +108 -1
  150. package/src/services/ai-project-tools.ts +20 -26
  151. package/src/services/ai-system-prompt.ts +88 -2
  152. package/src/services/ai-turn-signal.ts +70 -0
  153. package/src/services/connection.ts +74 -0
  154. package/src/services/context-manager.ts +101 -1
  155. package/src/services/document-assistant.ts +45 -4
  156. package/src/services/import-client.ts +33 -6
  157. package/src/services/import-run.ts +158 -0
  158. package/src/services/import-seed.ts +120 -0
  159. package/src/services/jx-validate.ts +58 -6
  160. package/src/services/project-adoption.ts +71 -0
  161. package/src/services/references.ts +0 -0
  162. package/src/services/settings/definitions.ts +17 -0
  163. package/src/services/tool-executor.ts +45 -2
  164. package/src/settings/contexts-section.ts +45 -3
  165. package/src/settings/dependencies-editor.ts +43 -23
  166. package/src/settings/general-settings.ts +30 -12
  167. package/src/settings/preferences-accounts.ts +193 -12
  168. package/src/settings/preferences-dialog.ts +93 -59
  169. package/src/settings/preferences-sections.ts +57 -0
  170. package/src/settings/settings-document.ts +65 -9
  171. package/src/studio.ts +33 -6
  172. package/src/tabs/transact.ts +42 -19
  173. package/src/types.ts +129 -7
  174. package/src/ui/ai-managed-connect.ts +62 -13
  175. package/src/ui/ai-model-picker.ts +189 -0
  176. package/src/ui/cf-account-picker.ts +200 -0
  177. package/src/ui/choice-field.ts +72 -0
  178. package/src/ui/layers.ts +83 -5
  179. package/src/utils/studio-utils.ts +22 -52
  180. package/styles/forced-colors.css +7 -0
  181. package/styles/overlays.css +256 -0
  182. package/styles/panels.css +13 -1
  183. package/styles/shell.css +147 -0
  184. package/dist/chunks/Overlay.dev-d2ajy6sj.js +0 -23
  185. package/dist/chunks/content-source-7fy9qrm0.js +0 -40
  186. package/dist/chunks/files-xcybanxe.js +0 -70
  187. package/dist/chunks/layers-panel-27essgkc.js +0 -60
  188. package/dist/chunks/publish-panel-09sy2bab.js +0 -22
  189. package/dist/chunks/quick-search-sefb61v8.js +0 -55
  190. package/dist/chunks/right-panel-9r32jd5j.js +0 -39
  191. package/dist/chunks/sp-overlay.dev-s18hymn9.js +0 -18
  192. package/dist/chunks/sp-popover.dev-dj9x7e7a.js +0 -9
  193. package/dist/chunks/sp-progress-circle.dev-0zxcyz3r.js +0 -15
  194. package/dist/chunks/sp-tooltip.dev-5c6d1kjz.js +0 -15
  195. package/dist/chunks/studio-bw5dz6km.js +0 -608
  196. package/dist/chunks/studio-c31yb4t6.js +0 -55
  197. package/src/canvas/site-style-css.ts +0 -117
@@ -1,164 +1,74 @@
1
1
  /**
2
- * Media usage — "which pages use this image?", asked in the shape an author actually wrote.
2
+ * Media usage — "which pages use this image?", asked once.
3
3
  *
4
- * `services/references.ts` already answers "where is this used?" for documents, and its answer is
5
- * what every destructive confirmation says out loud. Media does not fit through it unmodified, for
6
- * one reason: **the reference index resolves refs, and a media file's authored ref usually resolves
7
- * somewhere else.** `public/hero.jpg` is written `/hero.jpg`, which the compiler — and therefore
8
- * the sweep — resolves to `hero.jpg`. Ask about the file and every reference to it is invisible;
9
- * the query succeeds, returns zero, and the delete dialog says nothing else refers to it while
10
- * seven pages break. A confident zero is the one answer a destructive dialog must never invent.
4
+ * `services/references.ts` answers "where is this used?" for every kind of file, and its answer is
5
+ * what every destructive confirmation says out loud. Media needs three things on top of it, and no
6
+ * longer needs the fourth this module was built for.
11
7
  *
12
- * So the query is keyed on the AUTHORED ref. {@link authoredRefTargets} enumerates every path a
13
- * reference to this file resolves to — the file, its served path, its asset-mount path — and this
14
- * module asks all of them and unions the answers. Everything else is deliberately inherited rather
15
- * than re-implemented: the same `loadUsages` cache (so a dialog and a panel asking at once are one
16
- * round trip), the same `invalidateUsages` on any filesystem event, the same `usageWarning`
17
- * sentence in the confirmation, and the same `usageHeadline` wording on a panel.
8
+ * **What it still does.** A media path is normalized before it becomes a query (a leading `./` is
9
+ * not part of a file's identity), an empty path is a FAILURE rather than "nothing uses it", and
10
+ * {@link mediaUsageHeadline} renders the one line a media surface shows beside a file — where a
11
+ * failed query says **unknown**, which is a different fact from "unused" and has to read like one.
18
12
  *
19
- * **A partial answer is `failed`, not a total.** If one lane of the union cannot be counted, the
20
- * union cannot either — a number assembled from the lanes that worked reads exactly like a complete
21
- * one, and would understate what a delete breaks. Unknown is the honest answer, and
22
- * {@link mediaUsageHeadline} says the word.
13
+ * **What it no longer does, and why that matters.** A media file's authored reference usually
14
+ * resolves somewhere else: `public/hero.jpg` is written `/hero.jpg`, and a content asset is written
15
+ * `./images/hero.png` and republished at its collection's mount. `findReferences` used to resolve a
16
+ * rooted reference against the project root ALONE, so asking it about the file matched none of
17
+ * those and returned a confident zero — the one answer a destructive dialog must never invent. This
18
+ * module's answer was to enumerate every authored spelling of the file and union the results.
19
+ *
20
+ * That was a workaround in the wrong place. A client-side union can make a COUNT come out right and
21
+ * can do nothing at all about the REWRITE, which happens inside the engine — so the engine learned
22
+ * to resolve every lane itself (issue 239/241, `site-architecture.md` §9.3), and the union became a
23
+ * second implementation of a rule that now has one. It is gone: one path in, one query, one answer,
24
+ * and the lanes are the engine's business.
23
25
  *
24
26
  * @docs studio/projects/media
25
27
  */
26
28
 
27
- import { authoredRefTargets } from "./media-paths";
29
+ import { normalizeProjectPath } from "./media-paths";
28
30
  import { loadUsages, peekUsages, retryUsages, usageHeadline } from "../services/references";
29
- import type { UsageQuery, UsageState } from "../services/references";
30
- import type { ReferenceFile, ReferenceHit, ReferencesResult } from "../types";
31
+ import type { UsageState } from "../services/references";
31
32
 
32
- /**
33
- * The queries whose union answers "what uses this media file?".
34
- *
35
- * One per authored form. An empty list means there is no question to ask (an empty path), which
36
- * {@link loadMediaUsages} reports as a failure rather than as "nothing uses it".
37
- */
38
- export function mediaUsageQueries(path: string): UsageQuery[] {
39
- return authoredRefTargets(path).map((target) => ({ path: target }));
40
- }
41
-
42
- /** Merge one file's hits into the accumulator, summing counts per `(refType, ref)`. */
43
- function mergeFile(into: Map<string, ReferenceFile>, file: ReferenceFile): void {
44
- const existing = into.get(file.path);
45
- if (!existing) {
46
- into.set(file.path, { count: file.count, path: file.path, refs: [...file.refs] });
47
- return;
48
- }
49
- for (const hit of file.refs) {
50
- const same = existing.refs.find(
51
- (candidate: ReferenceHit) => candidate.refType === hit.refType && candidate.ref === hit.ref,
52
- );
53
- if (same) {
54
- same.count += hit.count;
55
- } else {
56
- existing.refs.push({ ...hit });
57
- }
58
- }
59
- existing.count += file.count;
60
- }
33
+ /** The state a caller gets for a path that names no file. */
34
+ const NO_FILE: UsageState = { message: "no media file to look for", status: "failed" };
61
35
 
62
36
  /**
63
- * One result out of several, for the same file asked about under different names.
37
+ * The query for a media file, or null when the path names nothing.
64
38
  *
65
- * The lanes cannot double-count: each asks about a DIFFERENT resolved path, and one authored string
66
- * resolves to exactly one of them. Errors are unioned by path so a document that failed to parse in
67
- * every lane is reported once, and `path` reports the media file the caller asked about rather than
68
- * whichever lane happened to be first.
39
+ * Normalizing here rather than at each call site is the whole of what this wrapper adds over
40
+ * `loadUsages`: the file tree, a drop handler and a library tile do not agree on whether a path
41
+ * carries a leading `./`, and two spellings of one file must not become two cache entries.
69
42
  */
70
- function unionResults(path: string, results: readonly ReferencesResult[]): ReferencesResult {
71
- const files = new Map<string, ReferenceFile>();
72
- const errors = new Map<string, { path: string; error: string }>();
73
- for (const result of results) {
74
- for (const file of result.files) {
75
- mergeFile(files, file);
76
- }
77
- for (const error of result.errors) {
78
- if (!errors.has(error.path)) {
79
- errors.set(error.path, error);
80
- }
81
- }
82
- }
83
- const merged = [...files.values()].toSorted((a, b) => a.path.localeCompare(b.path));
84
- return {
85
- errors: [...errors.values()],
86
- files: merged,
87
- filesReferencing: merged.length,
88
- path,
89
- refsTotal: merged.reduce((sum, file) => sum + file.count, 0),
90
- tagName: null,
91
- };
92
- }
93
-
94
- /**
95
- * Reduce the lanes to one state.
96
- *
97
- * Precedence is strictly least-informative-wins, because every step down is a step away from a
98
- * number the caller could act on: unsupported (the host cannot answer at all) beats failed (it
99
- * tried and could not) beats pending beats a real union.
100
- */
101
- function combine(path: string, states: readonly UsageState[]): UsageState {
102
- if (states.some((state) => state.status === "unsupported")) {
103
- return { status: "unsupported" };
104
- }
105
- const failure = states.find((state) => state.status === "failed");
106
- if (failure) {
107
- return failure;
108
- }
109
- if (states.some((state) => state.status === "pending")) {
110
- return { status: "pending" };
111
- }
112
- return {
113
- result: unionResults(
114
- path,
115
- states.map((state) => (state as { result: ReferencesResult }).result),
116
- ),
117
- status: "ready",
118
- };
43
+ function mediaQuery(path: string): { path: string } | null {
44
+ const normalized = normalizeProjectPath(path);
45
+ return normalized === "" ? null : { path: normalized };
119
46
  }
120
47
 
121
48
  /**
122
49
  * The answer right now, WITHOUT starting a request — the synchronous read a lit template needs.
123
50
  *
124
- * `null` means at least one lane has never been asked, and the caller decides whether this paint is
125
- * the one that asks (see {@link loadMediaUsages}).
51
+ * `null` means the question has never been asked, and the caller decides whether this paint is the
52
+ * one that asks (see {@link loadMediaUsages}).
126
53
  */
127
54
  export function peekMediaUsages(path: string): UsageState | null {
128
- const queries = mediaUsageQueries(path);
129
- if (queries.length === 0) {
130
- return null;
131
- }
132
- const states: UsageState[] = [];
133
- for (const query of queries) {
134
- const state = peekUsages(query);
135
- if (state === null) {
136
- return null;
137
- }
138
- states.push(state);
139
- }
140
- return combine(path, states);
55
+ const query = mediaQuery(path);
56
+ return query === null ? null : peekUsages(query);
141
57
  }
142
58
 
143
59
  /**
144
- * Ask every lane and union the answers. Never rejects — a failure is a STATE, because the caller
60
+ * Ask, or join the ask already in flight. Never rejects — a failure is a STATE, because the caller
145
61
  * has to render something and "unknown" is the honest something.
146
62
  */
147
63
  export async function loadMediaUsages(path: string): Promise<UsageState> {
148
- const queries = mediaUsageQueries(path);
149
- if (queries.length === 0) {
150
- return { message: "no media file to look for", status: "failed" };
151
- }
152
- return combine(path, await Promise.all(queries.map((query) => loadUsages(query))));
64
+ const query = mediaQuery(path);
65
+ return query === null ? NO_FILE : loadUsages(query);
153
66
  }
154
67
 
155
- /** Forget every lane's answer and ask again — the Retry behind a failed media count. */
68
+ /** Forget the answer and ask again — the Retry behind a failed media count. */
156
69
  export async function retryMediaUsages(path: string): Promise<UsageState> {
157
- const queries = mediaUsageQueries(path);
158
- if (queries.length === 0) {
159
- return { message: "no media file to look for", status: "failed" };
160
- }
161
- return combine(path, await Promise.all(queries.map((query) => retryUsages(query))));
70
+ const query = mediaQuery(path);
71
+ return query === null ? NO_FILE : retryUsages(query);
162
72
  }
163
73
 
164
74
  /**
@@ -0,0 +1,60 @@
1
+ /**
2
+ * A tab's document, as the bytes a file would hold.
3
+ *
4
+ * Its own module because four callers need it and one of them is `file-ops` itself: the save path
5
+ * writes what this returns, the source view shows it, the collab layer publishes it, and the live
6
+ * preview overlay sends it. Leaving it in `file-ops` meant the overlay had to import the module
7
+ * that imports the overlay, and the collab layer had already worked around the same shape by
8
+ * injecting the function at boot. One home resolves both.
9
+ *
10
+ * That every one of them goes through this function is the point rather than an accident: what a
11
+ * reader sees in a preview, what a peer sees in a session and what lands on disk are the same
12
+ * bytes, so they cannot drift.
13
+ */
14
+
15
+ import { getGridController } from "../grid/grid-controller";
16
+ import {
17
+ defaultContentFormat,
18
+ formatByName,
19
+ formatSerialize,
20
+ loadFormats,
21
+ } from "../format/format-host";
22
+ import type { Tab } from "../tabs/tab";
23
+
24
+ /**
25
+ * Serialize the current document to its output format. Format tabs round-trip through the format
26
+ * class's serialize capability; everything else is native JSON.
27
+ *
28
+ * @param {import("../tabs/tab.js").Tab} tab
29
+ * @returns {Promise<string>}
30
+ */
31
+ export async function serializeDocument(tab: Tab): Promise<string> {
32
+ // Grid tabs serialize through their source (pending edits included) — e.g. the Monaco source
33
+ // View of a CSV grid tab shows the live file text.
34
+ const grid = getGridController(tab);
35
+ const gridText = grid?.serializeForSource();
36
+ if (gridText) {
37
+ return gridText;
38
+ }
39
+ await loadFormats();
40
+ const sourceFormat = formatByName(tab.doc.sourceFormat);
41
+ if (sourceFormat?.capabilities.serialize) {
42
+ const fm = tab.doc.content?.frontmatter || {};
43
+ const doc = tab.doc.document;
44
+ const fullDoc = { ...fm, ...doc, children: doc.children ?? [] };
45
+ return formatSerialize(sourceFormat.name, fullDoc, { mode: "roundtrip" });
46
+ }
47
+ if (tab.doc.mode === "content") {
48
+ const format = defaultContentFormat();
49
+ if (format) {
50
+ const fm = tab.doc.content?.frontmatter ?? {};
51
+ const hasFrontmatter = Object.keys(fm).length > 0;
52
+ const fullDoc = { ...fm, ...tab.doc.document };
53
+ return formatSerialize(format.name, fullDoc, {
54
+ frontmatter: hasFrontmatter,
55
+ mode: "roundtrip",
56
+ });
57
+ }
58
+ }
59
+ return JSON.stringify(tab.doc.document, null, 2);
60
+ }