@brftech/filex-core 0.31.0 → 0.33.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 (62) hide show
  1. package/README.md +26 -2
  2. package/dist/{CsvViewer-DxujFEM3.js → CsvViewer-CqWeV8VO.js} +22 -22
  3. package/dist/CsvViewer-CqWeV8VO.js.map +1 -0
  4. package/dist/{DrawioViewer-DCvWnX2t.js → DrawioViewer-BNALOB04.js} +16 -16
  5. package/dist/DrawioViewer-BNALOB04.js.map +1 -0
  6. package/dist/filex-core.js +9346 -8069
  7. package/dist/filex-core.js.map +1 -1
  8. package/dist/filex-core.umd.cjs +49 -49
  9. package/dist/filex-core.umd.cjs.map +1 -1
  10. package/dist/index.d.ts +235 -16
  11. package/dist/style.css +1 -1
  12. package/package.json +1 -1
  13. package/src/FileExplorer.vue +776 -246
  14. package/src/components/CommandPalette.vue +21 -5
  15. package/src/components/ConnectionsPanel.vue +2 -1
  16. package/src/components/E2eRecoveryUnlockModal.vue +29 -4
  17. package/src/components/FilterBar.vue +244 -0
  18. package/src/components/GalleryView.vue +12 -1
  19. package/src/components/GridView.vue +48 -5
  20. package/src/components/InspectorPanel.vue +231 -8
  21. package/src/components/ListView.vue +12 -3
  22. package/src/components/NFSExportsPanel.vue +2 -1
  23. package/src/components/PendingOpsTray.vue +1 -1
  24. package/src/components/PresenceBar.vue +3 -2
  25. package/src/components/S3KeysPanel.vue +2 -1
  26. package/src/components/SSHKeysPanel.vue +2 -1
  27. package/src/components/SecondaryPane.vue +28 -13
  28. package/src/components/SideNav.vue +161 -1
  29. package/src/components/StarButton.vue +2 -1
  30. package/src/components/TabBar.vue +2 -2
  31. package/src/components/ThemeGallery.vue +1 -1
  32. package/src/components/TokensPanel.vue +2 -1
  33. package/src/components/Toolbar.vue +244 -58
  34. package/src/components/ViewSwitcher.vue +81 -0
  35. package/src/composables/useFileApi.ts +49 -2
  36. package/src/composables/useLocale.ts +1 -1
  37. package/src/composables/useOperations.ts +3 -3
  38. package/src/composables/usePendingOps.ts +1 -1
  39. package/src/composables/useTabs.ts +1 -1
  40. package/src/composables/useUploadChunked.ts +39 -7
  41. package/src/index.ts +8 -2
  42. package/src/lib/e2ecrypto.ts +191 -3
  43. package/src/lib/fileFilters.ts +143 -0
  44. package/src/lib/listing.ts +47 -0
  45. package/src/lib/themes.ts +7 -7
  46. package/src/lib/transfer.ts +6 -6
  47. package/src/locales/en.ts +115 -0
  48. package/src/locales/index.ts +1 -0
  49. package/src/locales/resolve.ts +66 -0
  50. package/src/locales/tr.ts +116 -0
  51. package/src/modals/ConvertModal.vue +14 -12
  52. package/src/modals/NewFolderModal.vue +1 -1
  53. package/src/modals/PermissionsModal.vue +30 -13
  54. package/src/modals/PreviewModal.vue +9 -9
  55. package/src/styles/base.css +520 -26
  56. package/src/styles/variables.css +1 -1
  57. package/src/types/ExplorerConfig.ts +18 -2
  58. package/src/types/FileNode.ts +1 -1
  59. package/src/viewers/CsvViewer.vue +4 -4
  60. package/src/viewers/DrawioViewer.vue +2 -2
  61. package/dist/CsvViewer-DxujFEM3.js.map +0 -1
  62. package/dist/DrawioViewer-DCvWnX2t.js.map +0 -1
package/dist/index.d.ts CHANGED
@@ -54,6 +54,13 @@ declare type __VLS_Props_11 = {
54
54
  /** ui-fix — mirror the main panel's virtual `.trash` row at storage root
55
55
  * so both split panes list identical rows (no row-offset). Defaults on. */
56
56
  trashVisible?: boolean;
57
+ /**
58
+ * The navigation panel is already offering Trash, so neither pane draws the
59
+ * virtual row. Passed down rather than worked out here: the panel belongs to
60
+ * the host explorer, and a pane that guessed at it is how the two halves of a
61
+ * split end up listing different rows.
62
+ */
63
+ navOffersTrash?: boolean;
57
64
  };
58
65
 
59
66
  declare type __VLS_Props_12 = {
@@ -85,9 +92,15 @@ declare type __VLS_Props_14 = {
85
92
  locale: LocaleCode;
86
93
  /** The folder has a user recovery key slot (v2 markers created since 0.31). */
87
94
  hasRecovery: boolean;
88
- /** The folder has an escrow slot AND this installation has escrow enabled. */
89
- hasEscrow: boolean;
90
- /** Short id of the escrow key the folder was sealed to. */
95
+ /**
96
+ * Whether the escrow door applies to THIS folder — see escrowAvailability.
97
+ * 'predates' and 'other-key' are not "no escrow tab"; they are two
98
+ * different facts the dialog has to state, because an admin who knows the
99
+ * installation has escrow reads a missing tab as a bug and tries the key
100
+ * anyway.
101
+ */
102
+ escrowState: EscrowAvailability;
103
+ /** Short id of the escrow key THIS FOLDER was sealed to, when it has one. */
91
104
  escrowKid?: string | null;
92
105
  busy?: boolean;
93
106
  /** Set by the parent after a failed attempt. */
@@ -287,6 +300,40 @@ declare type __VLS_Props_9 = {
287
300
  narrow?: boolean;
288
301
  };
289
302
 
303
+ /**
304
+ * Give an EXISTING v2 folder an escrow slot, in place, using the folder
305
+ * password its owner has just typed.
306
+ *
307
+ * ── Why this exists ─────────────────────────────────────────────────
308
+ *
309
+ * Escrow used to be all-or-nothing at install time, and then adoptable but
310
+ * never retroactive: on any installation that had been running for a while,
311
+ * escrow covered only folders nobody had created yet. On a real deployment
312
+ * the folders that matter already exist, so "new folders only" means escrow
313
+ * covers nothing anyone cares about.
314
+ *
315
+ * The server still cannot do this, and that has not changed: adding a slot
316
+ * needs the folder master key, which needs a credential the server has never
317
+ * held. What CAN do it is the browser, at the one moment the password is in
318
+ * memory — exactly where `upgradeMarkerV1` already lives. Same moment, same
319
+ * shape, different slot.
320
+ *
321
+ * ⚠⚠ Accepting hands the operator of this installation a second, permanent
322
+ * way into this folder. It is the folder's owner who decides, from inside,
323
+ * with the password; no configuration change and no admin action can do it
324
+ * for them. The caller MUST say that in those words before calling this —
325
+ * see `e2e.escrowoffer.*` in the locales.
326
+ *
327
+ * ⚠ v2 only. A v1 marker has no slots at all; the path for those is
328
+ * `upgradeMarkerV1`, which already seals an escrow slot when the
329
+ * installation has a key and already discloses it. Two doors into the same
330
+ * room would be two chances to get the disclosure wrong.
331
+ *
332
+ * ⚠ No file is re-encrypted, moved or rewritten. Only `.filex-e2e.json`
333
+ * changes, and only by gaining `esc` (and losing `esc_declined`).
334
+ */
335
+ export declare function addEscrowSlot(marker: E2eMarker, password: string, escrowPublicKey: string): Promise<E2eMarker>;
336
+
290
337
  /**
291
338
  * The self-service API-token surface (`/api/tokens`).
292
339
  *
@@ -589,6 +636,19 @@ export declare function createMarker(password: string, iterations?: number): Pro
589
636
  kek: CryptoKey;
590
637
  }>;
591
638
 
639
+ /**
640
+ * Record that this folder's owner was offered an escrow slot and declined.
641
+ *
642
+ * A refusal is a decision, not a delay: without this the offer would come
643
+ * back on every single unlock, which is how people learn to click past
644
+ * security dialogs without reading them. Nothing about the folder's keys
645
+ * changes — the only effect is that filex stops asking.
646
+ *
647
+ * Reversible by `addEscrowSlot`, which is the way back for somebody who
648
+ * says no today and changes their mind next month.
649
+ */
650
+ export declare function declineEscrowSlot(marker: E2eMarker, when: string): E2eMarker;
651
+
592
652
  /**
593
653
  * Decrypt a 'filexe2e' blob with the folder master key. Throws
594
654
  * E2eDecryptError on a wrong key / tampered data, and a plain Error when the
@@ -612,6 +672,15 @@ declare type Density = 'comfortable' | 'compact';
612
672
  */
613
673
  export declare function deriveKek(password: string, salt: Uint8Array, iterations: number): Promise<CryptoKey>;
614
674
 
675
+ /**
676
+ * The browser's preference, or `en`.
677
+ *
678
+ * ⚠ Guarded for a non-browser runtime: this package is imported by SSR and by
679
+ * unit tests under Node, where `navigator` is absent — and an exception here
680
+ * would take down the whole explorer over a language choice.
681
+ */
682
+ export declare function detectLocale(): LocaleCode;
683
+
615
684
  export declare const E2E_DEFAULT_ITERATIONS = 600000;
616
685
 
617
686
  /** The only escrow algorithm this version understands. */
@@ -661,10 +730,19 @@ export declare const E2E_MAGIC = "filexe2e";
661
730
  * - No key, password or recovery key is ever stored, logged or sent to a
662
731
  * server. The FMK lives in an in-memory key ring and dies with the tab.
663
732
  * - `deriveKek` imports non-extractable. Raw KEK bytes are produced ONLY
664
- * by `deriveKekBits`, only while upgrading a v1 marker, and only long
665
- * enough to wrap them into the new slots.
733
+ * by `deriveKekBits`, only for a marker whose FMK *is* the KEK
734
+ * (`upgradeMarkerV1`, and `addEscrowSlot` on a folder it produced), and
735
+ * only long enough to wrap them into a slot.
666
736
  * - A folder created while escrow was off carries no escrow slot, so the
667
- * escrow key cannot open it. That is arithmetic, not policy.
737
+ * escrow key cannot open it, and nothing the OPERATOR does changes
738
+ * that — not enabling escrow, not adopting it, not any admin action or
739
+ * future version. That is arithmetic, not policy: adding a slot needs
740
+ * the folder master key, and the server has never held a credential
741
+ * that produces one.
742
+ * - The folder's OWNER can, from inside, with the password:
743
+ * `addEscrowSlot`. That is the only door, it opens from one side only,
744
+ * and it is the reason `escrowAvailability` says "not as things stand"
745
+ * rather than "never".
668
746
  *
669
747
  * File layout ('filexe2e' magic, fixed 97-byte header) — UNCHANGED in v2:
670
748
  * [0..8) magic "filexe2e"
@@ -722,6 +800,23 @@ export declare interface E2eMarker {
722
800
  rk?: E2eRecoverySlot;
723
801
  /** v2 only, optional: the operator escrow slot. */
724
802
  esc?: E2eEscrowSlot;
803
+ /**
804
+ * v2 only, optional: an ISO timestamp recording that this folder's owner
805
+ * was OFFERED an escrow slot and said no.
806
+ *
807
+ * It lives in the marker rather than in browser storage because the unit
808
+ * of the decision is the FOLDER, not the device: the same person opening
809
+ * the folder from their phone must not be asked again, and a decision
810
+ * that vanished when someone cleared their site data would be no decision
811
+ * at all. It travels with the folder through a move, a backup and a
812
+ * restore, for the same reason the key slots do.
813
+ *
814
+ * It holds no key material and hides nothing from the operator — it is a
815
+ * record of an answer, and its only effect is that filex stops asking.
816
+ * `addEscrowSlot` clears it, so a decline is reversible by the one person
817
+ * who can reverse it.
818
+ */
819
+ esc_declined?: string;
725
820
  }
726
821
 
727
822
  /** User-recovery-key slot: HKDF salt + the FMK wrapped under the derived key. */
@@ -823,6 +918,41 @@ export declare function ensureHighlight(): Promise<unknown | null>;
823
918
  */
824
919
  export declare function ensureMonaco(): Promise<unknown | null>;
825
920
 
921
+ /**
922
+ * Why the escrow door is, or is not, on offer for this folder.
923
+ *
924
+ * 'off' this installation has no escrow key at all.
925
+ * 'available' the folder is sealed to THIS installation's escrow key.
926
+ * 'predates' the installation has an escrow key, and this folder has no
927
+ * escrow slot: it was created before escrow existed here.
928
+ * 'other-key' the folder carries an escrow slot sealed to a DIFFERENT
929
+ * key id — it came from another installation, via a restore
930
+ * or a copied data directory.
931
+ *
932
+ * ⚠ 'predates' exists because escrow can be ADOPTED by an installation
933
+ * that already has folders (FILEX_INSTALLATION_E2E_ESCROW_ADOPT), and
934
+ * adoption is not retroactive: the folder's master key was wrapped to its
935
+ * recovery paths when the folder was created. Before this distinction
936
+ * existed the dialog simply showed no Escrow tab, which is true but says
937
+ * nothing — an admin who knows escrow is on reads a missing tab as a bug,
938
+ * tries the key anyway, and learns the real answer from a failure. The UI
939
+ * has to say it instead.
940
+ *
941
+ * ⚠⚠ 'predates' means "not as things stand", NOT "never". The folder's
942
+ * owner can add a slot from inside with the password (`addEscrowSlot`,
943
+ * offered at unlock). Any wording built on this state has to leave that
944
+ * door visible, or it tells an operator their escrow key can never reach a
945
+ * folder whose owner could hand it over this afternoon.
946
+ *
947
+ * ⚠ 'other-key' was a quieter lie: the dialog labelled the escrow field
948
+ * with the INSTALLATION's key id whatever the folder's slot said, so a
949
+ * folder restored from another install looked openable by the key the
950
+ * operator has, and was not.
951
+ */
952
+ export declare type EscrowAvailability = 'off' | 'available' | 'predates' | 'other-key';
953
+
954
+ export declare function escrowAvailability(m: E2eMarker | null, installationKid: string | null | undefined): EscrowAvailability;
955
+
826
956
  /**
827
957
  * Stable short name for an escrow key: first 8 bytes of SHA-256(SPKI), hex.
828
958
  * Written into every escrow slot so a marker says WHICH key opens it, and so
@@ -830,6 +960,27 @@ export declare function ensureMonaco(): Promise<unknown | null>;
830
960
  */
831
961
  export declare function escrowKeyId(spkiB64: string): Promise<string>;
832
962
 
963
+ /**
964
+ * Whether this folder's owner should be offered an escrow slot, and whether
965
+ * they have already answered.
966
+ *
967
+ * 'n/a' nothing to offer: the installation has no escrow key, the
968
+ * folder already has a slot, or the marker is v1 (whose path
969
+ * is `upgradeMarkerV1`).
970
+ * 'offer' the offer applies and no answer has been recorded.
971
+ * 'declined' the offer applies and the owner said no. Do not ask again;
972
+ * leave a way back.
973
+ *
974
+ * ⚠ This deliberately does NOT look at whether the folder is unlocked. That
975
+ * is the caller's business, and it matters: the offer may only be shown
976
+ * after an unlock actually succeeded, because accepting needs the password
977
+ * and because asking someone who cannot open the folder to give away a key
978
+ * to it is asking the wrong person.
979
+ */
980
+ export declare type EscrowOfferState = 'n/a' | 'offer' | 'declined';
981
+
982
+ export declare function escrowOfferState(m: E2eMarker | null, installationKid: string | null | undefined): EscrowOfferState;
983
+
833
984
  export declare interface ExplorerConfig {
834
985
  /**
835
986
  * Modern shorthand: URL prefix for the standard /api/files/* layout.
@@ -872,7 +1023,7 @@ export declare interface ExplorerConfig {
872
1023
  */
873
1024
  onlyOfficeConfig?: string;
874
1025
  /**
875
- * Standalone editor page base — when set, "" on an office file
1026
+ * Standalone editor page base — when set, "Open" on an office file
876
1027
  * opens `${openPageBase}?path=...&mode=edit` in a new tab instead
877
1028
  * of the modal preview.
878
1029
  */
@@ -988,6 +1139,22 @@ export declare interface ExplorerConfig {
988
1139
  * navigation panel starts expanded, the tab strip
989
1140
  * and split pane are off, the gallery view mode and
990
1141
  * the host's "How to connect" surface are hidden.
1142
+ * 'drive' — everything `simple` does, plus the shell an end
1143
+ * user already knows: one primary "New" menu, one
1144
+ * search field in the header (with its ⌘K/Ctrl+K
1145
+ * escalation into the command palette), a filter row
1146
+ * under the breadcrumb, Folders and Files as
1147
+ * labelled sections in grid view, the details panel
1148
+ * split into Details / Activity, and a storage line
1149
+ * under the navigation.
1150
+ *
1151
+ * ⚠ `drive` is a SUPERSET of `simple`, not a sibling: everything `simple`
1152
+ * turns off stays off, and the code asks `simpleUi` for those questions so a
1153
+ * later change to `simple` cannot silently miss `drive`. It is a third value
1154
+ * rather than a second boolean because "which chrome" is ONE question with
1155
+ * three answers — a `driveShell: true` next to `uiProfile: 'standard'` would
1156
+ * be a combination nobody can describe, and keeping that question single is
1157
+ * why `uiProfile` was a preset to begin with.
991
1158
  *
992
1159
  * Why it exists (GitHub #14): the reporter's users are not in IT and read
993
1160
  * split panes, tabs and mount instructions as a file manager they would have
@@ -999,7 +1166,7 @@ export declare interface ExplorerConfig {
999
1166
  * rest of the chrome differ. A viewer's own collapse choice, once made,
1000
1167
  * outranks the profile — it is a per-viewer preference, not a policy.
1001
1168
  */
1002
- uiProfile?: 'standard' | 'simple';
1169
+ uiProfile?: 'standard' | 'simple' | 'drive';
1003
1170
  /**
1004
1171
  * Render the navigation panel (Upload · Recent / Starred / Shared with me /
1005
1172
  * Trash · the storage list). Collapsible to an icon rail by the viewer, whose
@@ -1324,6 +1491,8 @@ inspectorOpen?: boolean;
1324
1491
  navOpen?: boolean;
1325
1492
  navEnabled?: boolean;
1326
1493
  viewModes?: ViewMode[];
1494
+ shell?: "classic" | "drive";
1495
+ scopeLabel?: string;
1327
1496
  }> & Readonly<{
1328
1497
  onAction?: ((key: string) => any) | undefined;
1329
1498
  onUpload?: (() => any) | undefined;
@@ -1338,6 +1507,7 @@ onRefresh?: (() => any) | undefined;
1338
1507
  "onToggle-nav"?: (() => any) | undefined;
1339
1508
  "onOpen-theme"?: (() => any) | undefined;
1340
1509
  "onOpen-shortcut-settings"?: (() => any) | undefined;
1510
+ "onOpen-palette"?: ((query: string) => any) | undefined;
1341
1511
  }>, {
1342
1512
  focusSearch: () => Promise<void>;
1343
1513
  }, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
@@ -1354,6 +1524,7 @@ refresh: () => any;
1354
1524
  "toggle-nav": () => any;
1355
1525
  "open-theme": () => any;
1356
1526
  "open-shortcut-settings": () => any;
1527
+ "open-palette": (query: string) => any;
1357
1528
  }, PublicProps, {}, false, {}, {}, GlobalComponents, GlobalDirectives, string, {
1358
1529
  primaryEl: HTMLDivElement;
1359
1530
  wideMoreBtnEl: HTMLButtonElement;
@@ -1463,6 +1634,8 @@ inspectorOpen?: boolean;
1463
1634
  navOpen?: boolean;
1464
1635
  navEnabled?: boolean;
1465
1636
  viewModes?: ViewMode[];
1637
+ shell?: "classic" | "drive";
1638
+ scopeLabel?: string;
1466
1639
  }> & Readonly<{
1467
1640
  onAction?: ((key: string) => any) | undefined;
1468
1641
  onUpload?: (() => any) | undefined;
@@ -1477,6 +1650,7 @@ onRefresh?: (() => any) | undefined;
1477
1650
  "onToggle-nav"?: (() => any) | undefined;
1478
1651
  "onOpen-theme"?: (() => any) | undefined;
1479
1652
  "onOpen-shortcut-settings"?: (() => any) | undefined;
1653
+ "onOpen-palette"?: ((query: string) => any) | undefined;
1480
1654
  }>, {
1481
1655
  focusSearch: () => Promise<void>;
1482
1656
  }, {}, {}, {}, {}> | null;
@@ -1496,6 +1670,7 @@ viewMode?: ViewMode;
1496
1670
  thumbSrc?: (n: FileNode) => string | null;
1497
1671
  keepBadgeFor?: (n: FileNode) => "kept" | "syncing" | "cloud" | "partial" | null;
1498
1672
  trashVisible?: boolean;
1673
+ navOffersTrash?: boolean;
1499
1674
  }> & Readonly<{
1500
1675
  onTransfer?: ((p: {
1501
1676
  sources: string[];
@@ -1550,6 +1725,7 @@ viewMode?: ViewMode;
1550
1725
  thumbSrc?: (n: FileNode) => string | null;
1551
1726
  keepBadgeFor?: (n: FileNode) => "kept" | "syncing" | "cloud" | "partial" | null;
1552
1727
  trashVisible?: boolean;
1728
+ navOffersTrash?: boolean;
1553
1729
  }> & Readonly<{
1554
1730
  onTransfer?: ((p: {
1555
1731
  sources: string[];
@@ -2054,7 +2230,7 @@ export declare interface OperationInput {
2054
2230
  percent: number | null;
2055
2231
  status: OperationStatus;
2056
2232
  error?: string | null;
2057
- /** Queue op accepted but not started yet ("Sırada"). */
2233
+ /** Queue op accepted but not started yet ("Queued"). */
2058
2234
  queued?: boolean;
2059
2235
  /** Progress counters for queue ops (3/5 items). */
2060
2236
  doneCount?: number;
@@ -2082,9 +2258,9 @@ export declare interface OperationInput {
2082
2258
  * are registered per row so the panel can route user intent back to the
2083
2259
  * owning surface without the store knowing any transport details.
2084
2260
  *
2085
- * Lifecycle rules (per the İşlem Merkezi contract):
2261
+ * Lifecycle rules (per the Operations Center contract):
2086
2262
  * - running rows live in `active`;
2087
- * - done/aborted rows linger in `active` for DONE_LINGER_MS ("bitti"
2263
+ * - done/aborted rows linger in `active` for DONE_LINGER_MS ("done"
2088
2264
  * flash) then move to `history` (session-only, capped);
2089
2265
  * - error rows are STICKY: they stay in `active` until the user
2090
2266
  * dismisses or retries them — even after the source list swept them
@@ -2213,6 +2389,14 @@ onNav?: ((delta: number) => any) | undefined;
2213
2389
  "onOpen-full"?: (() => any) | undefined;
2214
2390
  }>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, any>;
2215
2391
 
2392
+ /** surucu:d1 — `GET /api/files/quota/me` (quota.Snapshot). */
2393
+ declare interface QuotaSnapshot {
2394
+ used_bytes: number;
2395
+ quota_bytes: number;
2396
+ percent_used: number;
2397
+ unlimited: boolean;
2398
+ }
2399
+
2216
2400
  export declare const RecentlyOpened: DefineComponent<__VLS_Props_5, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
2217
2401
  error: (message: string) => any;
2218
2402
  open: (node: RecentNode) => any;
@@ -2260,6 +2444,31 @@ declare interface ResolveEmailResponse {
2260
2444
  */
2261
2445
  export declare function resolveEndpoints(config: ExplorerConfig): EndpointMap;
2262
2446
 
2447
+ /**
2448
+ * Resolve the locale to render in.
2449
+ *
2450
+ * Order, and the reasoning for it:
2451
+ *
2452
+ * 1. **What the host asked for.** An explicit `locale` always wins. A host
2453
+ * that names a language has made a decision, and guessing over it would
2454
+ * make the prop advisory.
2455
+ * 2. **What the browser asks for**, via `navigator.languages` /
2456
+ * `navigator.language` — the same signal `Accept-Language` carries, and
2457
+ * the reason a Turkish reader gets Turkish without the embedder doing
2458
+ * anything.
2459
+ * 3. **`en`.**
2460
+ *
2461
+ * ⚠ Step 2 makes the same embed render differently for two people. That is
2462
+ * deliberate — it is what "follow the browser" means — but it also means a
2463
+ * screenshot, a test or a bug report about this component must state its
2464
+ * locale, because the component alone no longer determines it. Pass an
2465
+ * explicit `locale` anywhere the answer has to be stable.
2466
+ *
2467
+ * Matching is on the primary subtag, so `tr-TR` and `en-GB` resolve; anything
2468
+ * with no catalogue falls through rather than half-rendering.
2469
+ */
2470
+ export declare function resolveLocale(explicit?: LocaleCode | '' | null): LocaleCode;
2471
+
2263
2472
  /** How long an unfinished record is worth keeping. The server sweeps its own
2264
2473
  * staging after FILEX_UPLOAD_STAGING_TTL (24 h by default); a note that
2265
2474
  * outlives the bytes it describes only produces a confusing "resuming…" that
@@ -2945,10 +3154,17 @@ export declare interface UploadOptions {
2945
3154
  onDone?: (job: UploadJob, result: UploadResult) => void;
2946
3155
  onError?: (job: UploadJob, err: Error) => void;
2947
3156
  /**
2948
- * Wait for the backend transfer before resolving. Default false: the node is
2949
- * listed the moment the commit is accepted, which is the whole point of
2950
- * staging. `true` is for a caller that must not report success until the
2951
- * bytes are on the driver.
3157
+ * Wait for the backend transfer before resolving. Defaults to TRUE, because
3158
+ * "the server has the bytes" and "the storage has the bytes" are different
3159
+ * claims and only the second one is an upload.
3160
+ *
3161
+ * ⚠ It used to default to false and no caller ever set it, so the job went
3162
+ * straight to `done` on the 202 — a transfer that failed afterwards in the
3163
+ * ops worker left the user looking at a finished upload for a file that was
3164
+ * never stored (issue #16: every file over the chunk threshold, on every S3
3165
+ * backend). The `transferring` phase is short for a healthy storage and is
3166
+ * the only place a failure can still be shown, so it is the default; pass
3167
+ * false to opt out where the wait is genuinely unwanted.
2952
3168
  */
2953
3169
  waitForTransfer?: boolean;
2954
3170
  }
@@ -3008,6 +3224,7 @@ export declare function useFileApi(config: ExplorerConfig): {
3008
3224
  limit?: number;
3009
3225
  scope?: GlobalSearchScope;
3010
3226
  }) => Promise<GlobalSearchHit[]>;
3227
+ quotaMe: () => Promise<QuotaSnapshot | null>;
3011
3228
  subfolders: (path: string) => Promise<{
3012
3229
  folders: FileNode[];
3013
3230
  }>;
@@ -3038,7 +3255,9 @@ export declare function useFileApi(config: ExplorerConfig): {
3038
3255
  uploadMultipart: (path: string, files: File[], onProgress?: (p: number) => void) => Promise<ManagerResponse>;
3039
3256
  downloadUrl: (path: string) => string;
3040
3257
  previewUrl: (path: string) => string;
3041
- fetchBlob: (path: string) => Promise<{
3258
+ fetchBlob: (path: string, opts?: {
3259
+ fresh?: boolean;
3260
+ }) => Promise<{
3042
3261
  url: string;
3043
3262
  blob: Blob;
3044
3263
  mime: string;