@caretcms/core 0.2.0 → 0.4.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 (256) hide show
  1. package/README.md +195 -12
  2. package/dist/index.d.ts +26 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +113 -12
  5. package/dist/index.js.map +1 -1
  6. package/dist/loader.d.ts.map +1 -1
  7. package/dist/loader.js +35 -3
  8. package/dist/loader.js.map +1 -1
  9. package/dist/markdown/paragraphs.d.ts +14 -0
  10. package/dist/markdown/paragraphs.d.ts.map +1 -0
  11. package/dist/markdown/paragraphs.js +61 -0
  12. package/dist/markdown/paragraphs.js.map +1 -0
  13. package/dist/markdown/remark.d.ts.map +1 -1
  14. package/dist/markdown/remark.js +1 -0
  15. package/dist/markdown/remark.js.map +1 -1
  16. package/dist/markdown/satteri.d.ts.map +1 -1
  17. package/dist/markdown/satteri.js +35 -9
  18. package/dist/markdown/satteri.js.map +1 -1
  19. package/dist/markdown/stamp.d.ts +0 -9
  20. package/dist/markdown/stamp.d.ts.map +1 -1
  21. package/dist/markdown/stamp.js +6 -0
  22. package/dist/markdown/stamp.js.map +1 -1
  23. package/dist/providers/deployment/github.d.ts +27 -0
  24. package/dist/providers/deployment/github.d.ts.map +1 -0
  25. package/dist/providers/deployment/github.js +180 -0
  26. package/dist/providers/deployment/github.js.map +1 -0
  27. package/dist/providers/deployment/simulated.d.ts +10 -0
  28. package/dist/providers/deployment/simulated.d.ts.map +1 -0
  29. package/dist/providers/deployment/simulated.js +29 -0
  30. package/dist/providers/deployment/simulated.js.map +1 -0
  31. package/dist/runtime/auth/session.d.ts +1 -0
  32. package/dist/runtime/auth/session.d.ts.map +1 -1
  33. package/dist/runtime/auth/session.js +16 -0
  34. package/dist/runtime/auth/session.js.map +1 -1
  35. package/dist/runtime/authorization.d.ts +11 -0
  36. package/dist/runtime/authorization.d.ts.map +1 -0
  37. package/dist/runtime/authorization.js +29 -0
  38. package/dist/runtime/authorization.js.map +1 -0
  39. package/dist/runtime/collection-policy.d.ts +11 -0
  40. package/dist/runtime/collection-policy.d.ts.map +1 -0
  41. package/dist/runtime/collection-policy.js +44 -0
  42. package/dist/runtime/collection-policy.js.map +1 -0
  43. package/dist/runtime/config.d.ts +3 -0
  44. package/dist/runtime/config.d.ts.map +1 -1
  45. package/dist/runtime/config.js +4 -1
  46. package/dist/runtime/config.js.map +1 -1
  47. package/dist/runtime/content-errors.d.ts +9 -0
  48. package/dist/runtime/content-errors.d.ts.map +1 -0
  49. package/dist/runtime/content-errors.js +17 -0
  50. package/dist/runtime/content-errors.js.map +1 -0
  51. package/dist/runtime/content.d.ts.map +1 -1
  52. package/dist/runtime/content.js +13 -4
  53. package/dist/runtime/content.js.map +1 -1
  54. package/dist/runtime/deployment-status.d.ts +14 -0
  55. package/dist/runtime/deployment-status.d.ts.map +1 -0
  56. package/dist/runtime/deployment-status.js +57 -0
  57. package/dist/runtime/deployment-status.js.map +1 -0
  58. package/dist/runtime/draft-state.d.ts +16 -0
  59. package/dist/runtime/draft-state.d.ts.map +1 -0
  60. package/dist/runtime/draft-state.js +29 -0
  61. package/dist/runtime/draft-state.js.map +1 -0
  62. package/dist/runtime/i18n.d.ts +97 -0
  63. package/dist/runtime/i18n.d.ts.map +1 -0
  64. package/dist/runtime/i18n.js +190 -0
  65. package/dist/runtime/i18n.js.map +1 -0
  66. package/dist/runtime/index.d.ts +1 -0
  67. package/dist/runtime/index.d.ts.map +1 -1
  68. package/dist/runtime/index.js +1 -0
  69. package/dist/runtime/index.js.map +1 -1
  70. package/dist/runtime/middleware.d.ts.map +1 -1
  71. package/dist/runtime/middleware.js +61 -4
  72. package/dist/runtime/middleware.js.map +1 -1
  73. package/dist/runtime/mutations/contracts.d.ts +12 -0
  74. package/dist/runtime/mutations/contracts.d.ts.map +1 -1
  75. package/dist/runtime/mutations/contracts.js +54 -4
  76. package/dist/runtime/mutations/contracts.js.map +1 -1
  77. package/dist/runtime/mutations/engine.d.ts +2 -0
  78. package/dist/runtime/mutations/engine.d.ts.map +1 -1
  79. package/dist/runtime/mutations/engine.js +220 -57
  80. package/dist/runtime/mutations/engine.js.map +1 -1
  81. package/dist/runtime/providers.d.ts +4 -1
  82. package/dist/runtime/providers.d.ts.map +1 -1
  83. package/dist/runtime/providers.js +18 -1
  84. package/dist/runtime/providers.js.map +1 -1
  85. package/dist/runtime/publish-recovery.d.ts +32 -0
  86. package/dist/runtime/publish-recovery.d.ts.map +1 -0
  87. package/dist/runtime/publish-recovery.js +98 -0
  88. package/dist/runtime/publish-recovery.js.map +1 -0
  89. package/dist/runtime/publish.d.ts +10 -4
  90. package/dist/runtime/publish.d.ts.map +1 -1
  91. package/dist/runtime/publish.js +105 -63
  92. package/dist/runtime/publish.js.map +1 -1
  93. package/dist/runtime/rebuild-webhook.d.ts +4 -0
  94. package/dist/runtime/rebuild-webhook.d.ts.map +1 -1
  95. package/dist/runtime/rebuild-webhook.js +12 -2
  96. package/dist/runtime/rebuild-webhook.js.map +1 -1
  97. package/dist/runtime/request-context.d.ts +7 -1
  98. package/dist/runtime/request-context.d.ts.map +1 -1
  99. package/dist/runtime/request-context.js +7 -1
  100. package/dist/runtime/request-context.js.map +1 -1
  101. package/dist/runtime/rewrite.d.ts.map +1 -1
  102. package/dist/runtime/rewrite.js +28 -2
  103. package/dist/runtime/rewrite.js.map +1 -1
  104. package/dist/runtime/rich-allowlist.js +1 -1
  105. package/dist/runtime/rich-allowlist.js.map +1 -1
  106. package/dist/runtime/routes/admin-entry.d.ts.map +1 -1
  107. package/dist/runtime/routes/admin-entry.js +129 -21
  108. package/dist/runtime/routes/admin-entry.js.map +1 -1
  109. package/dist/runtime/routes/admin.d.ts.map +1 -1
  110. package/dist/runtime/routes/admin.js +28 -14
  111. package/dist/runtime/routes/admin.js.map +1 -1
  112. package/dist/runtime/routes/auth-login.d.ts.map +1 -1
  113. package/dist/runtime/routes/auth-login.js +4 -0
  114. package/dist/runtime/routes/auth-login.js.map +1 -1
  115. package/dist/runtime/routes/auth-logout.d.ts.map +1 -1
  116. package/dist/runtime/routes/auth-logout.js +7 -2
  117. package/dist/runtime/routes/auth-logout.js.map +1 -1
  118. package/dist/runtime/routes/auth-session.d.ts.map +1 -1
  119. package/dist/runtime/routes/auth-session.js +10 -2
  120. package/dist/runtime/routes/auth-session.js.map +1 -1
  121. package/dist/runtime/routes/collection-list.d.ts.map +1 -1
  122. package/dist/runtime/routes/collection-list.js +213 -66
  123. package/dist/runtime/routes/collection-list.js.map +1 -1
  124. package/dist/runtime/routes/deployment.d.ts +5 -0
  125. package/dist/runtime/routes/deployment.d.ts.map +1 -0
  126. package/dist/runtime/routes/deployment.js +38 -0
  127. package/dist/runtime/routes/deployment.js.map +1 -0
  128. package/dist/runtime/routes/draft.d.ts.map +1 -1
  129. package/dist/runtime/routes/draft.js +10 -3
  130. package/dist/runtime/routes/draft.js.map +1 -1
  131. package/dist/runtime/routes/entries.d.ts.map +1 -1
  132. package/dist/runtime/routes/entries.js +87 -43
  133. package/dist/runtime/routes/entries.js.map +1 -1
  134. package/dist/runtime/routes/history.d.ts.map +1 -1
  135. package/dist/runtime/routes/history.js +40 -21
  136. package/dist/runtime/routes/history.js.map +1 -1
  137. package/dist/runtime/routes/mutate.d.ts.map +1 -1
  138. package/dist/runtime/routes/mutate.js +4 -1
  139. package/dist/runtime/routes/mutate.js.map +1 -1
  140. package/dist/runtime/routes/publish.d.ts.map +1 -1
  141. package/dist/runtime/routes/publish.js +127 -33
  142. package/dist/runtime/routes/publish.js.map +1 -1
  143. package/dist/runtime/routes/schema.d.ts.map +1 -1
  144. package/dist/runtime/routes/schema.js +11 -2
  145. package/dist/runtime/routes/schema.js.map +1 -1
  146. package/dist/runtime/routes/studio-home.d.ts.map +1 -1
  147. package/dist/runtime/routes/studio-home.js +41 -20
  148. package/dist/runtime/routes/studio-home.js.map +1 -1
  149. package/dist/runtime/routes/upload.d.ts.map +1 -1
  150. package/dist/runtime/routes/upload.js +3 -0
  151. package/dist/runtime/routes/upload.js.map +1 -1
  152. package/dist/runtime/schema-registry.d.ts +8 -1
  153. package/dist/runtime/schema-registry.d.ts.map +1 -1
  154. package/dist/runtime/schema-registry.js +49 -3
  155. package/dist/runtime/schema-registry.js.map +1 -1
  156. package/dist/runtime/storage/entry-commit.d.ts +8 -0
  157. package/dist/runtime/storage/entry-commit.d.ts.map +1 -0
  158. package/dist/runtime/storage/entry-commit.js +45 -0
  159. package/dist/runtime/storage/entry-commit.js.map +1 -0
  160. package/dist/runtime/storage/filesystem-adapter.d.ts +5 -0
  161. package/dist/runtime/storage/filesystem-adapter.d.ts.map +1 -1
  162. package/dist/runtime/storage/filesystem-adapter.js +12 -3
  163. package/dist/runtime/storage/filesystem-adapter.js.map +1 -1
  164. package/dist/runtime/storage/frontmatter-codec.d.ts +5 -23
  165. package/dist/runtime/storage/frontmatter-codec.d.ts.map +1 -1
  166. package/dist/runtime/storage/frontmatter-codec.js +156 -13
  167. package/dist/runtime/storage/frontmatter-codec.js.map +1 -1
  168. package/dist/runtime/storage/in-memory-adapter.d.ts +7 -0
  169. package/dist/runtime/storage/in-memory-adapter.d.ts.map +1 -1
  170. package/dist/runtime/storage/in-memory-adapter.js +6 -0
  171. package/dist/runtime/storage/in-memory-adapter.js.map +1 -1
  172. package/dist/runtime/storage/markdown-adapter.d.ts.map +1 -1
  173. package/dist/runtime/storage/markdown-adapter.js +18 -20
  174. package/dist/runtime/storage/markdown-adapter.js.map +1 -1
  175. package/dist/runtime/storage/session-overlay-adapter.d.ts +12 -4
  176. package/dist/runtime/storage/session-overlay-adapter.d.ts.map +1 -1
  177. package/dist/runtime/storage/session-overlay-adapter.js +100 -13
  178. package/dist/runtime/storage/session-overlay-adapter.js.map +1 -1
  179. package/dist/runtime/storage/sidecar-meta-store.d.ts +5 -0
  180. package/dist/runtime/storage/sidecar-meta-store.d.ts.map +1 -1
  181. package/dist/runtime/storage/sidecar-meta-store.js +48 -5
  182. package/dist/runtime/storage/sidecar-meta-store.js.map +1 -1
  183. package/dist/runtime/themes/preset-tokens.js +4 -4
  184. package/dist/runtime/themes/preset-tokens.js.map +1 -1
  185. package/dist/runtime/utils.d.ts.map +1 -1
  186. package/dist/runtime/utils.js +3 -4
  187. package/dist/runtime/utils.js.map +1 -1
  188. package/dist/runtime/views/studio-layout.d.ts +3 -0
  189. package/dist/runtime/views/studio-layout.d.ts.map +1 -1
  190. package/dist/runtime/views/studio-layout.js +36 -13
  191. package/dist/runtime/views/studio-layout.js.map +1 -1
  192. package/dist/schema-utils.d.ts +7 -0
  193. package/dist/schema-utils.d.ts.map +1 -1
  194. package/dist/schema-utils.js +143 -0
  195. package/dist/schema-utils.js.map +1 -1
  196. package/dist/types.d.ts +165 -1
  197. package/dist/types.d.ts.map +1 -1
  198. package/package.json +12 -3
  199. package/static/cms/admin-entry.css +144 -13
  200. package/static/cms/admin-entry.js +539 -586
  201. package/static/cms/dev-toolbar/app.js +78 -19
  202. package/static/cms/editor/config.js +35 -15
  203. package/static/cms/editor/content-map.css +8 -1
  204. package/static/cms/editor/content-map.js +62 -23
  205. package/static/cms/editor/deployment-status.js +66 -0
  206. package/static/cms/editor/draft-client.js +87 -0
  207. package/static/cms/editor/guards.js +20 -0
  208. package/static/cms/editor/helpers.js +30 -1
  209. package/static/cms/editor/highlight.js +1 -0
  210. package/static/cms/editor/image-edit.js +44 -1
  211. package/static/cms/editor/image-utils.js +23 -7
  212. package/static/cms/editor/link-follow.js +122 -41
  213. package/static/cms/editor/link-ui.css +10 -1
  214. package/static/cms/editor/linkify.js +2 -0
  215. package/static/cms/editor/md-block-edit.js +140 -37
  216. package/static/cms/editor/md-paragraphs.js +98 -0
  217. package/static/cms/editor/md-serialize.js +1 -1
  218. package/static/cms/editor/panel.js +53 -7
  219. package/static/cms/editor/preview-mode.js +44 -0
  220. package/static/cms/editor/publish-result.js +100 -0
  221. package/static/cms/editor/rich-toolbar.js +58 -15
  222. package/static/cms/editor/sanitize.js +6 -2
  223. package/static/cms/editor/save-queue.js +75 -17
  224. package/static/cms/editor/section-controls/activation.js +3 -0
  225. package/static/cms/editor/section-controls/api.js +25 -7
  226. package/static/cms/editor/section-controls/index.js +60 -8
  227. package/static/cms/editor/section-controls/model.js +39 -4
  228. package/static/cms/editor/section-controls/mutations.js +15 -0
  229. package/static/cms/editor/section-controls/page-context.js +9 -0
  230. package/static/cms/editor/section-controls/pickers.js +53 -2
  231. package/static/cms/editor/section-controls/reorder.js +23 -0
  232. package/static/cms/editor/section-controls/selection.js +6 -0
  233. package/static/cms/editor/section-controls/spacing-drag.js +16 -0
  234. package/static/cms/editor/section-controls/ui-elements.js +7 -0
  235. package/static/cms/editor/section-controls/utils.js +7 -0
  236. package/static/cms/editor/section-controls.css +18 -3
  237. package/static/cms/editor/security.js +13 -2
  238. package/static/cms/editor/stega-hydrate.js +17 -4
  239. package/static/cms/editor/sync.js +475 -56
  240. package/static/cms/editor/text-edit.js +37 -3
  241. package/static/cms/editor/text-link-interactions.js +59 -17
  242. package/static/cms/editor/toast.js +23 -9
  243. package/static/cms/editor/tokens-and-text.css +9 -0
  244. package/static/cms/editor/toolbar.css +5 -0
  245. package/static/cms/editor/toolbar.js +91 -101
  246. package/static/cms/editor.css +1 -1
  247. package/static/cms/editor.js +1 -0
  248. package/static/cms/studio/entry-loader.js +102 -0
  249. package/static/cms/studio/field-model.js +110 -0
  250. package/static/cms/studio/fields.js +319 -0
  251. package/static/cms/studio/history-client.js +74 -0
  252. package/static/cms/studio/mutation-client.js +74 -0
  253. package/static/cms/studio/sync-client.js +107 -0
  254. package/static/cms/studio/upload-client.js +109 -0
  255. package/static/cms/studio-tokens.css +4 -4
  256. package/static/cms/studio.css +24 -0
@@ -0,0 +1,107 @@
1
+ import { deepClone } from './field-model.js';
2
+
3
+ const SYNC_CHANNEL = 'caretcms:content';
4
+
5
+ /** @typedef {{ postMessage: (message: unknown) => void, addEventListener: (type: 'message', listener: (event: MessageEvent) => void) => void, close: () => void }} Channel */
6
+
7
+ /**
8
+ * @param {{
9
+ * collection: string,
10
+ * id: string,
11
+ * previewPath: string | null,
12
+ * origin: string,
13
+ * isEmbedded: boolean,
14
+ * studioPath: () => string,
15
+ * postToParent: (message: unknown) => void,
16
+ * onSelection: (message: Record<string, unknown>) => void,
17
+ * addWindowMessageListener: (listener: (event: MessageEvent) => void) => void,
18
+ * addPagehideListener: (listener: () => void) => void,
19
+ * openChannel?: (name: string) => Channel,
20
+ * schedule?: (callback: () => void, delay: number) => number,
21
+ * cancel?: (timer: number) => void,
22
+ * now?: () => number,
23
+ * }} options
24
+ */
25
+ export function createStudioSync(options) {
26
+ const {
27
+ collection, id, previewPath, origin, isEmbedded, studioPath,
28
+ postToParent, onSelection, addWindowMessageListener, addPagehideListener,
29
+ openChannel, schedule = (callback, delay) => window.setTimeout(callback, delay),
30
+ cancel = timer => window.clearTimeout(timer), now = Date.now,
31
+ } = options;
32
+ let previewTimer = 0;
33
+ /** @type {Channel | null} */
34
+ let channel = null;
35
+
36
+ if (openChannel) {
37
+ try {
38
+ channel = openChannel(SYNC_CHANNEL);
39
+ channel.addEventListener('message', event => {
40
+ const message = event.data;
41
+ if (message && typeof message === 'object' && message.type === 'cms:field-selected') {
42
+ onSelection(message);
43
+ }
44
+ });
45
+ addPagehideListener(() => channel?.close());
46
+ } catch {
47
+ channel = null;
48
+ }
49
+ }
50
+
51
+ addWindowMessageListener(event => {
52
+ const message = event.data;
53
+ if (event.origin === origin && message && typeof message === 'object'
54
+ && message.type === 'cms:field-selected') {
55
+ onSelection(message);
56
+ }
57
+ });
58
+
59
+ /** @param {unknown} data */
60
+ function queuePreview(data) {
61
+ if (!isEmbedded || !data) return;
62
+ if (previewTimer) cancel(previewTimer);
63
+ previewTimer = schedule(() => {
64
+ try {
65
+ postToParent({
66
+ type: 'cms:preview', collection, id, data: deepClone(data),
67
+ embedded: true, studioPath: studioPath(),
68
+ });
69
+ } catch {
70
+ // Visual preview is a progressive enhancement.
71
+ }
72
+ }, 80);
73
+ }
74
+
75
+ /** @param {string} type @param {unknown} data */
76
+ function announceChange(type, data) {
77
+ const message = {
78
+ type, collection, id, data: data ? deepClone(data) : null,
79
+ embedded: isEmbedded, studioPath: studioPath(), savedAt: now(),
80
+ };
81
+ if (isEmbedded) {
82
+ try { postToParent(message); } catch { /* Progressive enhancement. */ }
83
+ }
84
+ try { channel?.postMessage(message); } catch { /* Progressive enhancement. */ }
85
+ }
86
+
87
+ /** @param {string} path */
88
+ function announceFieldSelection(path) {
89
+ const message = {
90
+ type: 'cms:field-selected', collection, id, field: path,
91
+ source: 'studio', previewPath,
92
+ };
93
+ if (isEmbedded) {
94
+ try { postToParent({ ...message, embedded: true }); } catch { /* Progressive enhancement. */ }
95
+ return;
96
+ }
97
+ try { channel?.postMessage(message); } catch { /* Progressive enhancement. */ }
98
+ }
99
+
100
+ function announceReady() {
101
+ if (!isEmbedded) return;
102
+ try { postToParent({ type: 'cms:entry-ready', collection, id, embedded: true }); }
103
+ catch { /* Progressive enhancement. */ }
104
+ }
105
+
106
+ return { queuePreview, announceChange, announceFieldSelection, announceReady };
107
+ }
@@ -0,0 +1,109 @@
1
+ const IMAGE_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp', 'image/avif']);
2
+
3
+ export class StudioUploadError extends Error {
4
+ /** @param {'unauthorized' | 'failed' | 'invalid-response'} kind */
5
+ constructor(kind) {
6
+ super(kind === 'unauthorized' ? 'Unauthorized' : 'Upload failed');
7
+ this.name = 'StudioUploadError';
8
+ this.kind = kind;
9
+ }
10
+ }
11
+
12
+ /** @param {{ type?: string }} file */
13
+ export function isSupportedImage(file) {
14
+ return IMAGE_TYPES.has(file.type || '');
15
+ }
16
+
17
+ /**
18
+ * @param {{
19
+ * apiBasePath: string,
20
+ * fetchImpl?: typeof fetch,
21
+ * createBitmap?: typeof createImageBitmap,
22
+ * createCanvas?: () => HTMLCanvasElement,
23
+ * onUnauthorized?: () => void,
24
+ * }} options
25
+ */
26
+ export function createStudioUploadClient({
27
+ apiBasePath,
28
+ fetchImpl = fetch,
29
+ createBitmap = file => createImageBitmap(file),
30
+ createCanvas = () => document.createElement('canvas'),
31
+ onUnauthorized = () => {},
32
+ }) {
33
+ /** @param {File} file @param {number} [maxWidth] @param {number} [quality] */
34
+ async function compress(file, maxWidth = 1600, quality = 0.82) {
35
+ if (file.size < 200000 && file.type === 'image/webp') return file;
36
+ const bitmap = await createBitmap(file);
37
+ let width = bitmap.width;
38
+ let height = bitmap.height;
39
+ const needsResize = width > maxWidth;
40
+ if (file.type === 'image/png' && !needsResize) {
41
+ bitmap.close();
42
+ return file;
43
+ }
44
+ if (needsResize) {
45
+ height = Math.round((height * maxWidth) / width);
46
+ width = maxWidth;
47
+ }
48
+ const canvas = createCanvas();
49
+ canvas.width = width;
50
+ canvas.height = height;
51
+ const context = canvas.getContext('2d');
52
+ if (!context) {
53
+ bitmap.close();
54
+ return file;
55
+ }
56
+ context.drawImage(bitmap, 0, 0, width, height);
57
+ bitmap.close();
58
+ const isPng = file.type === 'image/png';
59
+ const outputType = isPng ? 'image/png' : 'image/webp';
60
+ return new Promise(resolve => {
61
+ canvas.toBlob(blob => {
62
+ if (!blob || blob.size >= file.size) return resolve(file);
63
+ const extension = isPng ? '.png' : '.webp';
64
+ resolve(new File([blob], file.name.replace(/\.[^.]+$/, extension), { type: outputType }));
65
+ }, outputType, isPng ? undefined : quality);
66
+ });
67
+ }
68
+
69
+ /** @param {File} file */
70
+ async function readDimensions(file) {
71
+ try {
72
+ const bitmap = await createBitmap(file);
73
+ let width = bitmap.width;
74
+ let height = bitmap.height;
75
+ bitmap.close();
76
+ if (width > 1600) {
77
+ height = Math.round((height * 1600) / width);
78
+ width = 1600;
79
+ }
80
+ return { width, height };
81
+ } catch {
82
+ return null;
83
+ }
84
+ }
85
+
86
+ /** @param {File} file */
87
+ async function upload(file) {
88
+ const form = new FormData();
89
+ form.append('file', await compress(file));
90
+ const response = await fetchImpl(`${apiBasePath}/upload`, {
91
+ method: 'POST',
92
+ body: form,
93
+ headers: { 'x-caret-request': '1' },
94
+ credentials: 'same-origin',
95
+ });
96
+ if (response.status === 401) {
97
+ onUnauthorized();
98
+ throw new StudioUploadError('unauthorized');
99
+ }
100
+ if (!response.ok) throw new StudioUploadError('failed');
101
+ const body = await response.json().catch(() => null);
102
+ if (!body || typeof body !== 'object' || !('url' in body) || typeof body.url !== 'string') {
103
+ throw new StudioUploadError('invalid-response');
104
+ }
105
+ return body.url;
106
+ }
107
+
108
+ return { compress, readDimensions, upload };
109
+ }
@@ -30,15 +30,15 @@
30
30
  --color-theme-border: rgba(255, 255, 255, 0.10);
31
31
 
32
32
  /* ─── Semantic: Accent ────────────────────────────────────────────── */
33
- --color-theme-accent: #3b82f6;
33
+ --color-theme-accent: #60a5fa;
34
34
  --color-theme-accent-soft: rgba(59, 130, 246, 0.12);
35
35
  --color-theme-accent-strong: rgba(59, 130, 246, 0.24);
36
- --color-theme-on-accent: #ffffff;
36
+ --color-theme-on-accent: #07111f;
37
37
 
38
38
  /* ─── Semantic: Text ──────────────────────────────────────────────── */
39
39
  --color-theme-text: rgba(255, 255, 255, 0.92);
40
- --color-theme-text-muted: rgba(255, 255, 255, 0.55);
41
- --color-theme-text-dim: rgba(255, 255, 255, 0.32);
40
+ --color-theme-text-muted: #b6bac3;
41
+ --color-theme-text-dim: #9298a3;
42
42
 
43
43
  /* ─── Semantic: State ─────────────────────────────────────────────── */
44
44
  --color-theme-success: #22c55e;
@@ -29,6 +29,18 @@ html, body {
29
29
  box-sizing: border-box;
30
30
  }
31
31
 
32
+ .studio-sr-only {
33
+ position: absolute;
34
+ width: 1px;
35
+ height: 1px;
36
+ padding: 0;
37
+ margin: -1px;
38
+ overflow: hidden;
39
+ clip: rect(0, 0, 0, 0);
40
+ white-space: nowrap;
41
+ border: 0;
42
+ }
43
+
32
44
  a {
33
45
  color: inherit;
34
46
  text-decoration: none;
@@ -47,9 +59,11 @@ a {
47
59
  /* ─── Layout: Shell + Header ────────────────────────────────────────── */
48
60
 
49
61
  .studio-shell {
62
+ height: 100dvh;
50
63
  min-height: 100vh;
51
64
  display: flex;
52
65
  flex-direction: column;
66
+ overflow: hidden;
53
67
  }
54
68
 
55
69
  .studio-header {
@@ -87,6 +101,15 @@ a {
87
101
  flex-shrink: 0;
88
102
  }
89
103
 
104
+ .studio-editor-identity {
105
+ max-width: 14rem;
106
+ overflow: hidden;
107
+ text-overflow: ellipsis;
108
+ white-space: nowrap;
109
+ color: var(--studio-text-muted);
110
+ font-size: 0.72rem;
111
+ }
112
+
90
113
  .studio-brand {
91
114
  display: flex;
92
115
  align-items: center;
@@ -113,6 +136,7 @@ a {
113
136
 
114
137
  .studio-main {
115
138
  flex: 1;
139
+ min-height: 0;
116
140
  padding: var(--space-theme-xl) var(--space-theme-lg);
117
141
  overflow-y: auto;
118
142
  }