@drawnagency/primitives 0.1.99 → 0.1.100

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 (117) hide show
  1. package/dist/{chunk-FAZFADJK.js → chunk-3TO4RRHY.js} +3 -1
  2. package/dist/{chunk-KP4DWTPR.js → chunk-72FMQQIA.js} +246 -3
  3. package/dist/chunk-BS5AQHZG.js +45 -0
  4. package/dist/{chunk-HKHEAH5F.js → chunk-GOCFTXR3.js} +8 -60
  5. package/dist/{chunk-GYCBWGVW.js → chunk-LBHQWIVL.js} +79 -24
  6. package/dist/components/chat/ChatPanel.d.ts.map +1 -1
  7. package/dist/components/docfile/DocFileOverlay.d.ts +12 -0
  8. package/dist/components/docfile/DocFileOverlay.d.ts.map +1 -0
  9. package/dist/components/docfile/DocFilePane.d.ts +17 -0
  10. package/dist/components/docfile/DocFilePane.d.ts.map +1 -0
  11. package/dist/components/docfile/doc-file-state.d.ts +33 -0
  12. package/dist/components/docfile/doc-file-state.d.ts.map +1 -0
  13. package/dist/components/editor/SettingsForm.d.ts.map +1 -1
  14. package/dist/components/editor/index.js +1 -1
  15. package/dist/components/sections/Document/index.d.ts +18 -0
  16. package/dist/components/sections/Document/index.d.ts.map +1 -0
  17. package/dist/components/sections/all-sections.d.ts +10 -0
  18. package/dist/components/sections/all-sections.d.ts.map +1 -1
  19. package/dist/components/sections/brand-guide.d.ts +10 -0
  20. package/dist/components/sections/brand-guide.d.ts.map +1 -1
  21. package/dist/components/sections/brand-guide.js +3 -1
  22. package/dist/components/sections/register-schemas.js +5 -3
  23. package/dist/components/sections/register.js +5 -3
  24. package/dist/components/shared/DocumentSourceField.d.ts +13 -0
  25. package/dist/components/shared/DocumentSourceField.d.ts.map +1 -0
  26. package/dist/components/shared/Navigation.d.ts.map +1 -1
  27. package/dist/components/shell/EditorModal.d.ts.map +1 -1
  28. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  29. package/dist/components/shell/MediaBreadcrumb.d.ts.map +1 -1
  30. package/dist/components/shell/MediaFolderTile.d.ts +3 -1
  31. package/dist/components/shell/MediaFolderTile.d.ts.map +1 -1
  32. package/dist/components/shell/MediaFolderTree.d.ts +3 -0
  33. package/dist/components/shell/MediaFolderTree.d.ts.map +1 -1
  34. package/dist/components/shell/MediaLibraryContext.d.ts +18 -1
  35. package/dist/components/shell/MediaLibraryContext.d.ts.map +1 -1
  36. package/dist/components/shell/MediaLibraryModal.d.ts +21 -1
  37. package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -1
  38. package/dist/hooks/useDocumentUploads.d.ts +32 -0
  39. package/dist/hooks/useDocumentUploads.d.ts.map +1 -0
  40. package/dist/hooks/useEditorPublish.d.ts +18 -1
  41. package/dist/hooks/useEditorPublish.d.ts.map +1 -1
  42. package/dist/hooks/useMediaPipeline.d.ts +14 -1
  43. package/dist/hooks/useMediaPipeline.d.ts.map +1 -1
  44. package/dist/hooks/useResolvedDocument.d.ts +12 -0
  45. package/dist/hooks/useResolvedDocument.d.ts.map +1 -0
  46. package/dist/index.js +56 -32
  47. package/dist/lib/dexie.d.ts +11 -0
  48. package/dist/lib/dexie.d.ts.map +1 -1
  49. package/dist/lib/dexie.js +32 -0
  50. package/dist/lib/document-refs.d.ts +22 -0
  51. package/dist/lib/document-refs.d.ts.map +1 -0
  52. package/dist/lib/document-uploads.d.ts +38 -0
  53. package/dist/lib/document-uploads.d.ts.map +1 -0
  54. package/dist/lib/documents.d.ts +65 -0
  55. package/dist/lib/documents.d.ts.map +1 -0
  56. package/dist/lib/index.js +4 -4
  57. package/dist/lib/media-folders.d.ts +8 -0
  58. package/dist/lib/media-folders.d.ts.map +1 -1
  59. package/dist/lib/media-folders.js +2 -0
  60. package/dist/lib/nav.d.ts +29 -1
  61. package/dist/lib/nav.d.ts.map +1 -1
  62. package/dist/lib/platform-broker.d.ts +12 -0
  63. package/dist/lib/platform-broker.d.ts.map +1 -1
  64. package/dist/lib/platform-broker.js +12 -3
  65. package/dist/lib/registry.d.ts +10 -0
  66. package/dist/lib/registry.d.ts.map +1 -1
  67. package/dist/lib/save-chunks.d.ts +15 -0
  68. package/dist/lib/save-chunks.d.ts.map +1 -1
  69. package/dist/lib/scroll-lock.d.ts +2 -0
  70. package/dist/lib/scroll-lock.d.ts.map +1 -0
  71. package/dist/schemas/documents.d.ts +80 -0
  72. package/dist/schemas/documents.d.ts.map +1 -0
  73. package/dist/schemas/documents.js +25 -0
  74. package/dist/schemas/index.d.ts +1 -0
  75. package/dist/schemas/index.d.ts.map +1 -1
  76. package/dist/schemas/index.js +23 -1
  77. package/dist/storage/index.d.ts +2 -2
  78. package/dist/storage/index.d.ts.map +1 -1
  79. package/dist/storage/index.js +3 -1
  80. package/dist/storage/types.d.ts +47 -0
  81. package/dist/storage/types.d.ts.map +1 -1
  82. package/package.json +5 -1
  83. package/src/components/chat/ChatPanel.tsx +5 -4
  84. package/src/components/docfile/DocFileOverlay.tsx +54 -0
  85. package/src/components/docfile/DocFilePane.tsx +129 -0
  86. package/src/components/docfile/doc-file-state.ts +180 -0
  87. package/src/components/editor/SettingsForm.tsx +10 -0
  88. package/src/components/sections/Document/index.tsx +163 -0
  89. package/src/components/sections/brand-guide.ts +2 -1
  90. package/src/components/shared/DocumentSourceField.tsx +51 -0
  91. package/src/components/shared/Navigation.tsx +53 -10
  92. package/src/components/shell/EditorModal.tsx +2 -5
  93. package/src/components/shell/EditorShell.tsx +111 -7
  94. package/src/components/shell/MediaBreadcrumb.tsx +14 -1
  95. package/src/components/shell/MediaFolderTile.tsx +4 -2
  96. package/src/components/shell/MediaFolderTree.tsx +20 -3
  97. package/src/components/shell/MediaLibraryContext.tsx +18 -1
  98. package/src/components/shell/MediaLibraryModal.tsx +223 -45
  99. package/src/hooks/useDocumentUploads.ts +330 -0
  100. package/src/hooks/useEditorPublish.ts +180 -8
  101. package/src/hooks/useMediaPipeline.ts +136 -5
  102. package/src/hooks/useResolvedDocument.ts +25 -0
  103. package/src/lib/dexie.ts +54 -0
  104. package/src/lib/document-refs.ts +83 -0
  105. package/src/lib/document-uploads.ts +66 -0
  106. package/src/lib/documents.ts +138 -0
  107. package/src/lib/media-folders.ts +9 -0
  108. package/src/lib/nav.ts +46 -2
  109. package/src/lib/platform-broker.ts +18 -3
  110. package/src/lib/registry.ts +9 -0
  111. package/src/lib/save-chunks.ts +42 -0
  112. package/src/lib/scroll-lock.ts +27 -0
  113. package/src/schemas/documents.ts +82 -0
  114. package/src/schemas/index.ts +1 -0
  115. package/src/storage/index.ts +2 -2
  116. package/src/storage/types.ts +46 -0
  117. /package/dist/{chunk-FOY5TF7D.js → chunk-ZDQ3TT47.js} +0 -0
package/src/lib/nav.ts CHANGED
@@ -3,6 +3,7 @@ import { getSection, type SectionRegistry } from "./registry";
3
3
  import { audiencePasses, homePage, pageDisplayTitle } from "./pages";
4
4
  import { navEntries } from "./nav-tree";
5
5
  import type { SiteIndex } from "../schemas/site-config";
6
+ import type { SiteDocument } from "./documents";
6
7
 
7
8
  export interface NavItem {
8
9
  href: string;
@@ -102,7 +103,30 @@ export interface GroupNavItem {
102
103
  pages: PageNavItem[];
103
104
  }
104
105
 
105
- export type SiteNavEntry = ({ kind: "page" } & PageNavItem) | GroupNavItem;
106
+ /** A launcher entry for one document section. NOT a PageNavItem: the editor's
107
+ * page rendering turns hrefs into pageSelectEvent buttons, which would
108
+ * dead-end here. */
109
+ export interface DocNavItem {
110
+ kind: "doc";
111
+ sectionId: string;
112
+ title: string;
113
+ url: string;
114
+ }
115
+
116
+ /** The synthetic, never-persisted "Documents" group. GroupNavItem.pages is
117
+ * PageNavItem[], so the doc group is its own entry kind rather than a reuse. */
118
+ export interface DocGroupNavItem {
119
+ kind: "doc-group";
120
+ id: string;
121
+ label: string;
122
+ docs: DocNavItem[];
123
+ }
124
+
125
+ /** Id of the synthetic group. Generated page-group ids never collide with it,
126
+ * and it is never written to nav.json. */
127
+ export const DOCUMENTS_GROUP_ID = "__documents__";
128
+
129
+ export type SiteNavEntry = ({ kind: "page" } & PageNavItem) | GroupNavItem | DocGroupNavItem;
106
130
 
107
131
  export interface SiteNav {
108
132
  /** The authored tree: loose pages and groups, in nav order. */
@@ -123,8 +147,15 @@ export function generateSiteNav(opts: {
123
147
  mode: "viewer" | "editor";
124
148
  audience: string | null;
125
149
  registry?: SectionRegistry;
150
+ /** Pre-derived and already requester-filtered (deriveSiteDocuments). This
151
+ * function cannot see SITE-WIDE section content — activeSections carries at
152
+ * most the current page's sections, and EditorShell's two history call
153
+ * sites pass activeSections: [] outright — so the list is always supplied
154
+ * by the caller. Callers without one (history views) pass nothing and
155
+ * render no group. */
156
+ documents?: SiteDocument[];
126
157
  }): SiteNav {
127
- const { index, activeSections, activePageId, mode, audience, registry } = opts;
158
+ const { index, activeSections, activePageId, mode, audience, registry, documents } = opts;
128
159
  const active = activePageId ?? homePage(index).id;
129
160
  const byId = new Map(index.pages.map((p) => [p.id, p]));
130
161
 
@@ -163,6 +194,19 @@ export function generateSiteNav(opts: {
163
194
  });
164
195
  }
165
196
 
197
+ // Derived, never persisted: the Documents group lives only in this view model,
198
+ // so nav.json is never written, reconcileNav never sees it, and the Pages
199
+ // modal's positional drag indexing is untouched.
200
+ const navDocs = (documents ?? []).filter((d) => d.showInDocumentsNav);
201
+ if (navDocs.length > 0) {
202
+ entries.push({
203
+ kind: "doc-group",
204
+ id: DOCUMENTS_GROUP_ID,
205
+ label: "Documents",
206
+ docs: navDocs.map((d) => ({ kind: "doc" as const, sectionId: d.sectionId, title: d.title, url: d.url })),
207
+ });
208
+ }
209
+
166
210
  if (mode === "editor") {
167
211
  return {
168
212
  entries,
@@ -7,6 +7,21 @@ function sleep(ms: number): Promise<void> {
7
7
  return new Promise((resolve) => setTimeout(resolve, ms));
8
8
  }
9
9
 
10
+ /**
11
+ * A broker call that reached apps/admin and got a non-2xx back. The `status` is
12
+ * carried so callers can distinguish a MISSING endpoint (404/405/501 — the
13
+ * stale-admin window, where the client site has been upgraded ahead of the
14
+ * platform) from a real auth or config failure. Without it every such case
15
+ * surfaces as an opaque 500 the editor cannot act on. `status` is null when the
16
+ * request never got a response at all.
17
+ */
18
+ export class PlatformBrokerError extends Error {
19
+ constructor(public readonly status: number | null, message: string) {
20
+ super(message);
21
+ this.name = "PlatformBrokerError";
22
+ }
23
+ }
24
+
10
25
  /** True when this runtime should broker privileged ops through the platform. */
11
26
  export function isPlatformMode(): boolean {
12
27
  return Boolean(env("PLATFORM_API_URL") && env("PLATFORM_API_KEY"));
@@ -60,15 +75,15 @@ export async function platformBroker<T = unknown>(
60
75
  }
61
76
 
62
77
  if (res.status >= 500 && !isLast) {
63
- lastError = new Error(`Platform broker error (${res.status})`);
78
+ lastError = new PlatformBrokerError(res.status, `Platform broker error (${res.status})`);
64
79
  await sleep(RETRY_BASE_MS * 2 ** attempt);
65
80
  continue;
66
81
  }
67
82
 
68
- throw new Error(`Platform broker error (${res.status}): ${await res.text()}`);
83
+ throw new PlatformBrokerError(res.status, `Platform broker error (${res.status}): ${await res.text()}`);
69
84
  }
70
85
 
71
- throw lastError instanceof Error ? lastError : new Error("Platform broker unreachable");
86
+ throw lastError instanceof Error ? lastError : new PlatformBrokerError(null, "Platform broker unreachable");
72
87
  }
73
88
 
74
89
  /**
@@ -62,6 +62,15 @@ export type SettingsFieldDef =
62
62
  label: string;
63
63
  default: VideoSource;
64
64
  target?: "content" | "options";
65
+ }
66
+ | {
67
+ /** Opens the media library filtered to documents (PDF/HTML). The renderer
68
+ * lives in SettingsForm; an unrendered kind falls through its `default`
69
+ * arm, so this type can land ahead of the field component. */
70
+ type: "document-source";
71
+ label: string;
72
+ default: { documentId: string };
73
+ target?: "content" | "options";
65
74
  };
66
75
 
67
76
  export type SettingsSchema = Record<string, SettingsFieldDef>;
@@ -51,3 +51,45 @@ export function packUploads(uploads: MediaUploadPayload[], budget = SAVE_CHUNK_B
51
51
  if (current.length > 0) chunks.push(current);
52
52
  return { chunks, oversized };
53
53
  }
54
+
55
+ export interface DocumentUploadPayload {
56
+ id: string;
57
+ path: string;
58
+ base64: string;
59
+ }
60
+
61
+ /** Approximate serialized size of one document upload — base64 dominates; the
62
+ * allowance covers JSON keys, quoting, and the manifest entry riding along. */
63
+ export function documentUploadSizeBytes(upload: DocumentUploadPayload): number {
64
+ return upload.base64.length + upload.path.length + 576;
65
+ }
66
+
67
+ /** Greedy in-order packing of document uploads. A 2.5MiB document base64s to
68
+ * ~3.4MB, so two never share a request — hence chunking rather than one blob
69
+ * request. Documents are never packed with sections/index. */
70
+ export function packDocumentUploads(
71
+ uploads: DocumentUploadPayload[],
72
+ budget = SAVE_CHUNK_BUDGET_BYTES,
73
+ ): { chunks: DocumentUploadPayload[][]; oversized: DocumentUploadPayload[] } {
74
+ const chunks: DocumentUploadPayload[][] = [];
75
+ const oversized: DocumentUploadPayload[] = [];
76
+ let current: DocumentUploadPayload[] = [];
77
+ let currentBytes = 0;
78
+
79
+ for (const upload of uploads) {
80
+ const size = documentUploadSizeBytes(upload);
81
+ if (size > budget) {
82
+ oversized.push(upload);
83
+ continue;
84
+ }
85
+ if (current.length > 0 && currentBytes + size > budget) {
86
+ chunks.push(current);
87
+ current = [];
88
+ currentBytes = 0;
89
+ }
90
+ current.push(upload);
91
+ currentBytes += size;
92
+ }
93
+ if (current.length > 0) chunks.push(current);
94
+ return { chunks, oversized };
95
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Refcounted body-scroll lock.
3
+ *
4
+ * EditorModal and ChatPanel each save/restore `body.style.overflow` themselves,
5
+ * which collides when two lockers overlap — the first to release restores
6
+ * "hidden" as if it were the resting value, or unlocks the page under a still-
7
+ * open surface. The doc overlay and the mobile chat sheet CAN be open at the
8
+ * same time, so this one counts.
9
+ */
10
+ let depth = 0;
11
+ let previousOverflow = "";
12
+
13
+ export function lockBodyScroll(): () => void {
14
+ if (typeof document === "undefined") return () => {};
15
+ if (depth === 0) {
16
+ previousOverflow = document.body.style.overflow;
17
+ document.body.style.overflow = "hidden";
18
+ }
19
+ depth++;
20
+ let released = false;
21
+ return () => {
22
+ if (released) return;
23
+ released = true;
24
+ depth--;
25
+ if (depth === 0) document.body.style.overflow = previousOverflow;
26
+ };
27
+ }
@@ -0,0 +1,82 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Document metadata lives in its OWN file, not in image-manifest.json.
5
+ *
6
+ * The image manifest is hard-`.parse`d on every page load across five routes,
7
+ * its remote-merge guard fails open on parse failure, and the authoring CLI
8
+ * parse-catch-WRITES it — so one entry a stale package version cannot
9
+ * represent means whole-site 500s or a wiped manifest. A sidecar is simply
10
+ * absent from an older writer's file list and survives untouched. Same
11
+ * structural answer as nav.json; do not "simplify" this back into either
12
+ * index.json or image-manifest.json.
13
+ */
14
+ export const DOCUMENT_MANIFEST_PATH = "src/content/document-manifest.json";
15
+
16
+ /**
17
+ * Git-tier cap. 2.5 MiB base64-encodes to ~3.4 MB, inside the 4 MB post-base64
18
+ * chunk budget with envelope headroom.
19
+ */
20
+ export const DOCUMENT_GIT_MAX_BYTES = 2_621_440;
21
+
22
+ /** Bucket-tier cap. Its lower bound is DOCUMENT_GIT_MAX_BYTES — no dead zone. */
23
+ export const DOCUMENT_BUCKET_MAX_BYTES = 209_715_200;
24
+
25
+ /**
26
+ * The only two accepted upload types, and the ONLY source of a document's file
27
+ * extension — never the media pipeline's `?? "bin"` fallback.
28
+ */
29
+ export const DOCUMENT_MIME_TO_EXT = {
30
+ "application/pdf": "pdf",
31
+ "text/html": "html",
32
+ } as const;
33
+
34
+ /** Reverse of DOCUMENT_MIME_TO_EXT, for setting Content-Type when serving. */
35
+ export const DOCUMENT_KIND_TO_MIME = {
36
+ pdf: "application/pdf",
37
+ html: "text/html",
38
+ } as const;
39
+
40
+ export const DocumentManifestEntrySchema = z.object({
41
+ filename: z.string(),
42
+ size: z.number().int().positive(),
43
+ kind: z.enum(["pdf", "html"]),
44
+ storage: z.enum(["git", "bucket"]),
45
+ /** git: repo path under assets/documents/…; bucket: site-relative object key. */
46
+ path: z.string(),
47
+ });
48
+
49
+ export const DocumentManifestSchema = z.record(z.string(), DocumentManifestEntrySchema);
50
+
51
+ export type DocumentManifest = z.infer<typeof DocumentManifestSchema>;
52
+ export type DocumentManifestEntry = z.infer<typeof DocumentManifestEntrySchema>;
53
+
54
+ /**
55
+ * The gated URL for a document, at BOTH storage tiers. Single-sourced here so
56
+ * the section row, the overlay, the nav group and the tests cannot drift — a
57
+ * bucket-tier document is still fetched through this route, which authorizes
58
+ * the requester and only then 302s to a short-lived presigned GET.
59
+ */
60
+ export function documentFileUrl(
61
+ documentId: string,
62
+ filename: string,
63
+ opts: { download?: boolean } = {},
64
+ ): string {
65
+ const base = `/api/document-file/${documentId}/${encodeURIComponent(filename)}`;
66
+ return opts.download ? `${base}?download=1` : base;
67
+ }
68
+
69
+ /** Git-tier repo path. `slugName` comes from sanitizeMediaName(file.name). */
70
+ export function documentGitPath(slugName: string, hash: string, kind: "pdf" | "html"): string {
71
+ return `assets/documents/${slugName}-${hash}/original.${kind}`;
72
+ }
73
+
74
+ /**
75
+ * Bucket-tier object key, SITE-RELATIVE — the store prepends the tenant prefix,
76
+ * so this is also the form stored in the manifest, matched by the delete
77
+ * broker's regex, and compared by the publish-time GC. The id is the 32-hex
78
+ * `documentId` minted by upload-intent, never the client's content hash.
79
+ */
80
+ export function documentBucketPath(documentId: string, kind: "pdf" | "html"): string {
81
+ return `documents/${documentId}/original.${kind}`;
82
+ }
@@ -7,3 +7,4 @@ export * from "./audience";
7
7
  export * from "./media-grid-options";
8
8
  export * from "./auth";
9
9
  export * from "./link";
10
+ export * from "./documents";
@@ -1,2 +1,2 @@
1
- export type { StorageProvider, FileWrite, AssetStore } from "./types";
2
- export { StorageConflictError, IdempotencyLookupUnavailableError, ASSET_CACHE_CONTROL, IDEMPOTENCY_TRAILER, IDEMPOTENCY_KEY_RE, withIdempotencyTrailer } from "./types";
1
+ export type { StorageProvider, FileWrite, AssetStore, DocumentStore } from "./types";
2
+ export { StorageConflictError, IdempotencyLookupUnavailableError, ASSET_CACHE_CONTROL, DOCUMENT_CACHE_CONTROL, IDEMPOTENCY_TRAILER, IDEMPOTENCY_KEY_RE, withIdempotencyTrailer } from "./types";
@@ -151,3 +151,49 @@ export interface AssetStore {
151
151
  /** Recursive listing under a site-relative prefix. */
152
152
  list(prefix: string): Promise<{ path: string; size: number; updatedAt: string }[]>;
153
153
  }
154
+
155
+ /**
156
+ * `Cache-Control` every DOCUMENT upload must set. Deliberately NOT
157
+ * ASSET_CACHE_CONTROL: documents live in a PRIVATE bucket reachable only
158
+ * through short-lived presigned GETs, and a shared cache keyed on the full
159
+ * signed URL would otherwise retain the bytes for a year after the signature
160
+ * expired. `private` keeps the copy in the requesting browser only.
161
+ */
162
+ export const DOCUMENT_CACHE_CONTROL = "private, max-age=31536000, immutable";
163
+
164
+ /**
165
+ * Optional PRIVATE document store — a second bucket with no public domain.
166
+ *
167
+ * Deliberately not an AssetStore: the assets bucket is public by configuration
168
+ * at R2_PUBLIC_BASE (videos depend on it), so a signed GET there would protect
169
+ * nothing and hosted HTML would render unsandboxed on a sibling origin. Hence
170
+ * no `getPublicUrl` here at all — every read is a short-lived signed GET,
171
+ * minted only after the gated route has authorized the requester.
172
+ *
173
+ * Paths are SITE-RELATIVE ("documents/{hash}/original.pdf"); implementations
174
+ * prepend the tenant prefix themselves so a caller can never address another
175
+ * tenant's objects.
176
+ */
177
+ export interface DocumentStore {
178
+ /** Mint a signed direct-upload URL for the browser (PUT). */
179
+ createUploadUrl(
180
+ path: string,
181
+ opts: { contentType: string },
182
+ ): Promise<{ uploadUrl: string; path: string }>;
183
+
184
+ /**
185
+ * Mint a short-lived signed GET. `downloadFilename`, when given, signs
186
+ * `response-content-disposition: attachment` into the URL — R2 sends no
187
+ * Content-Disposition of its own, so this is the only way ?download=1 can
188
+ * force a download on the bucket tier.
189
+ */
190
+ createDownloadUrl(
191
+ path: string,
192
+ opts: { expiresInSeconds: number; downloadFilename?: string },
193
+ ): Promise<string>;
194
+
195
+ delete(paths: string[]): Promise<void>;
196
+
197
+ /** Recursive listing under a site-relative prefix. */
198
+ list(prefix: string): Promise<Array<{ path: string; size: number; updatedAt: string }>>;
199
+ }
File without changes