@brftech/filex-core 0.42.1 → 0.43.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 (233) hide show
  1. package/README.md +14 -2
  2. package/dist/ArchiveViewer-BqU761Wk.js +149 -0
  3. package/dist/ArchiveViewer-BqU761Wk.js.map +1 -0
  4. package/dist/CsvViewer-ByHVK5ga.js +172 -0
  5. package/dist/CsvViewer-ByHVK5ga.js.map +1 -0
  6. package/dist/{DrawioViewer-BuhZOAx6.js → DrawioViewer-Bgrj8-zG.js} +45 -44
  7. package/dist/DrawioViewer-Bgrj8-zG.js.map +1 -0
  8. package/dist/{EpubViewer-DS3mb3k8.js → EpubViewer-DJwUKlHW.js} +50 -50
  9. package/dist/EpubViewer-DJwUKlHW.js.map +1 -0
  10. package/dist/IpynbViewer-Bz0eJ4NS.js +184 -0
  11. package/dist/IpynbViewer-Bz0eJ4NS.js.map +1 -0
  12. package/dist/MermaidViewer-CEDTD_pp.js +136 -0
  13. package/dist/MermaidViewer-CEDTD_pp.js.map +1 -0
  14. package/dist/PsdViewer-CZcxUpZO.js +121 -0
  15. package/dist/PsdViewer-CZcxUpZO.js.map +1 -0
  16. package/dist/TiffViewer-BwI62CbQ.js +141 -0
  17. package/dist/TiffViewer-BwI62CbQ.js.map +1 -0
  18. package/dist/{Viewer3D-CIrOn63I.js → Viewer3D-DoeZcuMm.js} +16 -16
  19. package/dist/Viewer3D-DoeZcuMm.js.map +1 -0
  20. package/dist/filex-core.js +475 -219
  21. package/dist/filex-core.umd.cjs +165 -108
  22. package/dist/filex-core.umd.cjs.map +1 -1
  23. package/dist/fonts/caveat-latin-ext.woff2 +0 -0
  24. package/dist/fonts/caveat-latin.woff2 +0 -0
  25. package/dist/fonts/dancing-script-latin-ext.woff2 +0 -0
  26. package/dist/fonts/dancing-script-latin.woff2 +0 -0
  27. package/dist/fonts/homemade-apple-latin.woff2 +0 -0
  28. package/dist/fonts/source-serif-4-latin-ext.woff2 +0 -0
  29. package/dist/fonts/source-serif-4-latin.woff2 +0 -0
  30. package/dist/index-DuWnGuv5.js +35901 -0
  31. package/dist/index-DuWnGuv5.js.map +1 -0
  32. package/dist/index.d.ts +5241 -553
  33. package/dist/pdf-CaPYsOUo.js +21302 -0
  34. package/dist/pdf-CaPYsOUo.js.map +1 -0
  35. package/dist/style.css +1 -1
  36. package/package.json +1 -1
  37. package/src/FileExplorer.vue +1207 -343
  38. package/src/assets/fonts/LICENSE-Apache-2.0.txt +202 -0
  39. package/src/assets/fonts/LICENSE-OFL-1.1.txt +93 -0
  40. package/src/assets/fonts/README.md +58 -0
  41. package/src/assets/fonts/caveat-latin-ext.woff2 +0 -0
  42. package/src/assets/fonts/caveat-latin.woff2 +0 -0
  43. package/src/assets/fonts/dancing-script-latin-ext.woff2 +0 -0
  44. package/src/assets/fonts/dancing-script-latin.woff2 +0 -0
  45. package/src/assets/fonts/homemade-apple-latin.woff2 +0 -0
  46. package/src/assets/fonts/source-serif-4-latin-ext.woff2 +0 -0
  47. package/src/assets/fonts/source-serif-4-latin.woff2 +0 -0
  48. package/src/components/AdvancedSearch.vue +49 -0
  49. package/src/components/Breadcrumb.vue +5 -5
  50. package/src/components/ChoiceButtons.vue +152 -0
  51. package/src/components/CommandPalette.vue +5 -5
  52. package/src/components/ConnectionGuideView.vue +1 -1
  53. package/src/components/ConnectionNotice.vue +59 -0
  54. package/src/components/ConnectionsPanel.vue +30 -568
  55. package/src/components/ContextMenu.vue +31 -5
  56. package/src/components/DataTable.vue +1331 -0
  57. package/src/components/FilePane.vue +3 -0
  58. package/src/components/FilterBar.vue +12 -5
  59. package/src/components/GalleryView.vue +50 -2
  60. package/src/components/GridView.vue +51 -3
  61. package/src/components/HomeView.vue +18 -6
  62. package/src/components/InspectorPanel.vue +134 -17
  63. package/src/components/ListView.vue +366 -900
  64. package/src/components/LogoMark.vue +57 -0
  65. package/src/components/NFSExportsPanel.vue +76 -28
  66. package/src/components/OnboardingTour.vue +19 -7
  67. package/src/components/OperationsCenter.vue +77 -1
  68. package/src/components/PendingOpsTray.vue +59 -5
  69. package/src/components/PresenceBar.vue +7 -7
  70. package/src/components/ProductVersion.vue +31 -0
  71. package/src/components/QuickLook.vue +10 -14
  72. package/src/components/RecentlyOpened.vue +47 -17
  73. package/src/components/RecoveryKeyModal.vue +1 -1
  74. package/src/components/RowActions.vue +134 -0
  75. package/src/components/S3KeysPanel.vue +98 -56
  76. package/src/components/SSHKeysPanel.vue +79 -26
  77. package/src/components/SideNav.vue +260 -96
  78. package/src/components/StarButton.vue +1 -0
  79. package/src/components/StorageFields.vue +74 -6
  80. package/src/components/StorageTags.vue +62 -0
  81. package/src/components/TabBar.vue +1 -1
  82. package/src/components/TagKindIcon.vue +56 -0
  83. package/src/components/TagPicker.vue +226 -40
  84. package/src/components/ThemePalette.vue +4 -4
  85. package/src/components/TimeZonePicker.vue +29 -10
  86. package/src/components/TokensPanel.vue +82 -27
  87. package/src/components/Toolbar.vue +28 -6
  88. package/src/components/plugin/PluginConfirmModal.vue +54 -0
  89. package/src/components/plugin/PluginInspectorSection.vue +149 -0
  90. package/src/components/plugin/PluginPageView.vue +339 -0
  91. package/src/components/plugin/PluginViewModal.vue +159 -0
  92. package/src/components/plugin/SurfaceConversation.vue +76 -0
  93. package/src/components/plugin/SurfaceFooterButtons.vue +73 -0
  94. package/src/components/plugin/SurfaceRenderer.vue +299 -0
  95. package/src/components/plugin/SurfaceSections.vue +91 -0
  96. package/src/components/plugin/nodes/PdfFieldEditor.vue +509 -0
  97. package/src/components/plugin/nodes/SurfaceFileChooser.vue +94 -0
  98. package/src/components/plugin/nodes/SurfaceForm.vue +82 -0
  99. package/src/components/plugin/nodes/SurfaceList.vue +127 -0
  100. package/src/components/plugin/nodes/SurfacePdfFields.vue +1285 -0
  101. package/src/components/plugin/nodes/SurfacePeoplePicker.vue +213 -0
  102. package/src/components/plugin/nodes/SurfacePinInput.vue +57 -0
  103. package/src/components/plugin/nodes/SurfacePreview.vue +119 -0
  104. package/src/components/plugin/nodes/SurfaceProgress.vue +48 -0
  105. package/src/components/plugin/nodes/SurfaceSignaturePad.vue +472 -0
  106. package/src/components/plugin/nodes/SurfaceSteps.vue +53 -0
  107. package/src/components/public/PublicLanguagePicker.vue +58 -0
  108. package/src/components/public/PublicLinkPage.vue +286 -0
  109. package/src/components/public/PublicLinkPreview.vue +85 -0
  110. package/src/components/public/PublicPinGate.vue +108 -0
  111. package/src/components/public/PublicRequestBody.vue +204 -0
  112. package/src/components/public/PublicShareBody.vue +188 -0
  113. package/src/components/public/PublicShell.vue +332 -0
  114. package/src/composables/useConnections.ts +8 -2
  115. package/src/composables/useFileApi.ts +183 -44
  116. package/src/composables/useLocale.ts +277 -41
  117. package/src/composables/useNFSExports.ts +8 -10
  118. package/src/composables/useOperations.ts +44 -3
  119. package/src/composables/usePendingOps.ts +59 -8
  120. package/src/composables/usePluginActions.ts +121 -0
  121. package/src/composables/usePluginSurface.ts +223 -0
  122. package/src/composables/usePublicBranding.ts +172 -0
  123. package/src/composables/usePublicLink.ts +573 -0
  124. package/src/composables/usePublicPage.ts +61 -0
  125. package/src/composables/useS3Keys.ts +17 -12
  126. package/src/composables/useSSHKeys.ts +8 -10
  127. package/src/composables/useTokens.ts +8 -12
  128. package/src/composables/useUploadChunked.ts +18 -8
  129. package/src/composables/useViewerFetch.ts +6 -3
  130. package/src/index.ts +404 -8
  131. package/src/lib/actionIcons.ts +46 -1
  132. package/src/lib/advSearch.ts +14 -7
  133. package/src/lib/appLock.ts +130 -0
  134. package/src/lib/connection.ts +110 -0
  135. package/src/lib/destinationTree.ts +19 -3
  136. package/src/lib/direction.ts +353 -0
  137. package/src/lib/dragGhost.ts +6 -0
  138. package/src/lib/dropLimits.ts +69 -0
  139. package/src/lib/errorWords.ts +394 -0
  140. package/src/lib/externalReach.ts +5 -1
  141. package/src/lib/fileFilters.ts +7 -2
  142. package/src/lib/fileIcons.ts +4 -0
  143. package/src/lib/internalPaths.ts +77 -0
  144. package/src/lib/listInput.ts +64 -0
  145. package/src/lib/listing.ts +58 -23
  146. package/src/lib/modalStack.ts +26 -0
  147. package/src/lib/nodeRow.ts +121 -0
  148. package/src/lib/pdfFieldRules.ts +126 -0
  149. package/src/lib/pdfFields.ts +589 -0
  150. package/src/lib/pdfFieldsGeom.ts +203 -0
  151. package/src/lib/pdfjsLoader.ts +51 -0
  152. package/src/lib/personName.ts +47 -0
  153. package/src/lib/pluginApplies.ts +123 -0
  154. package/src/lib/pluginLabel.ts +72 -0
  155. package/src/lib/pluginMenu.ts +253 -0
  156. package/src/lib/pluginPage.ts +58 -0
  157. package/src/lib/plural.ts +181 -0
  158. package/src/lib/popupLayer.ts +103 -0
  159. package/src/lib/prefs.ts +563 -0
  160. package/src/lib/productVersion.ts +35 -0
  161. package/src/lib/publicLayout.ts +39 -0
  162. package/src/lib/serviceGate.ts +104 -0
  163. package/src/lib/shareTtl.ts +25 -11
  164. package/src/lib/signFonts.ts +85 -0
  165. package/src/lib/signaturePad.ts +111 -0
  166. package/src/lib/sortOrder.ts +100 -38
  167. package/src/lib/star.ts +7 -1
  168. package/src/lib/surfaceCell.ts +41 -0
  169. package/src/lib/surfaceConditions.ts +156 -0
  170. package/src/lib/surfaceOpen.ts +106 -0
  171. package/src/lib/surfaceSteps.ts +68 -0
  172. package/src/lib/surfaceValues.ts +191 -0
  173. package/src/lib/symlink.ts +150 -0
  174. package/src/lib/tableColumns.ts +493 -0
  175. package/src/lib/tableEnv.ts +34 -0
  176. package/src/lib/tablePrefs.ts +84 -0
  177. package/src/lib/tags.ts +121 -12
  178. package/src/lib/themes.ts +286 -18
  179. package/src/lib/tour.ts +152 -0
  180. package/src/lib/trashEmpty.ts +111 -0
  181. package/src/lib/trashTimeLeft.ts +23 -0
  182. package/src/lib/uiLocales.ts +460 -0
  183. package/src/lib/viewPrefs.ts +696 -660
  184. package/src/lib/viewPrefsHttp.ts +78 -0
  185. package/src/locales/en.ts +522 -119
  186. package/src/locales/resolve.ts +17 -8
  187. package/src/locales/tr.ts +539 -155
  188. package/src/modals/ConvertModal.vue +62 -11
  189. package/src/modals/DestinationPickerModal.vue +59 -16
  190. package/src/modals/Modal.vue +113 -23
  191. package/src/modals/NewDocumentModal.vue +16 -6
  192. package/src/modals/NewFolderModal.vue +8 -0
  193. package/src/modals/PermissionsModal.vue +272 -163
  194. package/src/modals/PreviewModal.vue +119 -20
  195. package/src/modals/RenameModal.vue +37 -1
  196. package/src/styles/base.css +2895 -241
  197. package/src/styles/sign-fonts.css +118 -0
  198. package/src/styles/variables.css +60 -0
  199. package/src/types/Connections.ts +17 -0
  200. package/src/types/ExplorerConfig.ts +117 -1
  201. package/src/types/FileNode.ts +58 -0
  202. package/src/types/Plugins.ts +494 -0
  203. package/src/types/Public.ts +212 -0
  204. package/src/viewers/ArchiveViewer.vue +93 -48
  205. package/src/viewers/CsvViewer.vue +99 -53
  206. package/src/viewers/DrawioViewer.vue +35 -9
  207. package/src/viewers/EpubViewer.vue +7 -8
  208. package/src/viewers/IpynbViewer.vue +3 -1
  209. package/src/viewers/MermaidViewer.vue +4 -5
  210. package/src/viewers/PdfViewer.vue +8 -17
  211. package/src/viewers/PsdViewer.vue +2 -4
  212. package/src/viewers/TiffViewer.vue +4 -5
  213. package/src/viewers/Viewer3D.vue +1 -1
  214. package/LICENSE +0 -21
  215. package/dist/ArchiveViewer-CLNevfPE.js +0 -124
  216. package/dist/ArchiveViewer-CLNevfPE.js.map +0 -1
  217. package/dist/CsvViewer-K8XA3z7N.js +0 -141
  218. package/dist/CsvViewer-K8XA3z7N.js.map +0 -1
  219. package/dist/DrawioViewer-BuhZOAx6.js.map +0 -1
  220. package/dist/EpubViewer-DS3mb3k8.js.map +0 -1
  221. package/dist/IpynbViewer-BTk7p4Dn.js +0 -184
  222. package/dist/IpynbViewer-BTk7p4Dn.js.map +0 -1
  223. package/dist/MermaidViewer-DqucH_io.js +0 -137
  224. package/dist/MermaidViewer-DqucH_io.js.map +0 -1
  225. package/dist/PsdViewer-CwiVENmq.js +0 -122
  226. package/dist/PsdViewer-CwiVENmq.js.map +0 -1
  227. package/dist/TiffViewer-CpewSVmQ.js +0 -142
  228. package/dist/TiffViewer-CpewSVmQ.js.map +0 -1
  229. package/dist/Viewer3D-CIrOn63I.js.map +0 -1
  230. package/dist/index-Dx0IbBcI.js +0 -26831
  231. package/dist/index-Dx0IbBcI.js.map +0 -1
  232. package/dist/useViewerFetch-czqbd2Lj.js +0 -25
  233. package/dist/useViewerFetch-czqbd2Lj.js.map +0 -1
package/src/index.ts CHANGED
@@ -25,8 +25,22 @@ export { default as PreviewModal } from './modals/PreviewModal.vue';
25
25
 
26
26
  // ——— Phase-2 standalone components (consumers can mount these
27
27
  // independently of the FileExplorer host, e.g. a sidebar tray).
28
+ /* The filex mark — ONE copy, here, because the public shell draws it and
29
+ `packages/core` cannot import out of `web/` (dup-scan: `brand-mark`). */
30
+ export { default as LogoMark } from './components/LogoMark.vue';
28
31
  export { default as StarButton } from './components/StarButton.vue';
29
32
  export { default as TagPicker } from './components/TagPicker.vue';
33
+ export { default as TagKindIcon } from './components/TagKindIcon.vue';
34
+ export { tagItemsOf, tagKey, isTagKind, type TagItem, type TagKind } from './lib/tags';
35
+ // tablo:t3 — the row's ONE action control ("Actions" / "Aksiyon"). DataTable
36
+ // draws it for every table (`rowActions`); it stays exported for a host that
37
+ // needs the same control outside a table. A second menu built beside
38
+ // ContextMenu drifts from it the first time an action is added (lesson #67).
39
+ // ⚠ `useTableScroll`, `PIN_LEAD_ROOM` and `useScrolledX` are gone: they were
40
+ // the plumbing of the second table (ui/Table.vue) that imitated this one, and
41
+ // DataTable owns the scroll and the frozen edges itself.
42
+ export { default as RowActions } from './components/RowActions.vue';
43
+ export type { ContextAction } from './components/ContextMenu.vue';
30
44
  export { default as RecentlyOpened } from './components/RecentlyOpened.vue';
31
45
  // belge:n1 — the "New document" picker. Exported because the entry belongs
32
46
  // on every surface, not just the admin app: a host that draws its own
@@ -55,6 +69,265 @@ export {
55
69
  } from './lib/destinationTree';
56
70
  export type { DestinationRow } from './lib/destinationTree';
57
71
 
72
+ /* App plugins (docs/APP-PLUGINS-API.md) — the menu rule, the label fallback
73
+ * and the shared actions cache, so a host that draws its own menu offers the
74
+ * same rows the explorer does. */
75
+ export { usePluginActions, invalidatePluginActions, PLUGIN_ACTIONS_TTL_MS } from './composables/usePluginActions';
76
+ export type { PluginActionsStore } from './composables/usePluginActions';
77
+ export { appliesMatches, appliesToNodes, appliesItemOf } from './lib/pluginApplies';
78
+ export { labelOf as pluginLabelOf, labelIn as pluginLabelIn, appTextOr as pluginTextOr } from './lib/pluginLabel';
79
+ /* The public page's three card widths, and which kind gets which. */
80
+ export { publicLayoutFor } from './lib/publicLayout';
81
+ export type { PublicLayout } from './lib/publicLayout';
82
+ export { pluginMenuRows, pluginActionsFor, pluginActionKey, isPluginActionKey } from './lib/pluginMenu';
83
+ export { normalizeOp } from './composables/usePendingOps';
84
+ export type { PendingOpType, PendingOpOutput } from './composables/usePendingOps';
85
+ /* M2 — the surface conversation (modal + inspector share it) and the value seeding. */
86
+ export { usePluginSurface, SURFACE_CHANGE_DEBOUNCE_MS } from './composables/usePluginSurface';
87
+ export type { PluginSurfaceStore, PluginSurfaceHost, PluginSurfaceEvents } from './composables/usePluginSurface';
88
+ export { initialValues as surfaceInitialValues, storageFieldOf, pinLength, looksLikeEmail } from './lib/surfaceValues';
89
+ /* v3 §2 — the two rules the RENDERER keeps so no plugin can break them:
90
+ * a field that depends on another field, and a step with one way forward. */
91
+ export {
92
+ conditionMet,
93
+ fieldRequired,
94
+ formFields,
95
+ hasAnswer,
96
+ hiddenKeys,
97
+ missingRequired as surfaceMissingRequired,
98
+ stripHiddenValues,
99
+ visibleFields,
100
+ } from './lib/surfaceConditions';
101
+ export { hasSteps, isBackAction, stepFooter } from './lib/surfaceSteps';
102
+ /* v3 §3.0 — "go to this file, and start that screen on it". */
103
+ export { isOpenRequest, openHashFor, openTargetFor } from './lib/surfaceOpen';
104
+ export type { SurfaceOpenOptions, SurfaceOpenTarget } from './lib/surfaceOpen';
105
+ export type { StepFooter } from './lib/surfaceSteps';
106
+ /* v3 §2 — a choice you can read without clicking. The replacement for
107
+ * `<select>` in every surface, and available to a host that draws its own. */
108
+ export { default as ChoiceButtons } from './components/ChoiceButtons.vue';
109
+ export type { ChoiceOption } from './components/ChoiceButtons.vue';
110
+ // M3 — public pages for outside participants + the sign track's components.
111
+ export { usePublicPage, publicPageClient, publicPageUrl, PublicPageError } from './composables/usePublicPage';
112
+ export type { PublicPageStore, PublicPageClient, PublicPageStatus, PinFailure } from './composables/usePublicPage';
113
+ /* v3 §1 — ONE public shell for every link a stranger can follow: a share
114
+ * (`/s/`), a file request (`/d/`) and an app plugin's page (a share that
115
+ * carries one). A host binds the address and mounts `PublicLinkPage`; it does
116
+ * not own a public surface of its own. */
117
+ export {
118
+ usePublicLink,
119
+ usePublicShare,
120
+ usePublicRequest,
121
+ publicLinkClient,
122
+ shareRoot,
123
+ requestRoot,
124
+ pageRoot,
125
+ shareDownloadUrl,
126
+ shareNoJsUrl,
127
+ PublicLinkError,
128
+ } from './composables/usePublicLink';
129
+ export type {
130
+ PublicLinkOptions,
131
+ PublicLinkStore,
132
+ PublicLinkClient,
133
+ PublicRoot,
134
+ PublicStatus,
135
+ PublicUpload,
136
+ } from './composables/usePublicLink';
137
+ export { usePublicBranding, normalizeAccent, shade, inkOn, accentStyleOf, DEFAULT_BRAND_NAME } from './composables/usePublicBranding';
138
+ export { default as PublicLinkPage } from './components/public/PublicLinkPage.vue';
139
+ export { default as PublicShell } from './components/public/PublicShell.vue';
140
+ export { default as PublicLinkPreview } from './components/public/PublicLinkPreview.vue';
141
+ export { default as PublicPinGate } from './components/public/PublicPinGate.vue';
142
+ export { default as PublicShareBody } from './components/public/PublicShareBody.vue';
143
+ export { default as PublicRequestBody } from './components/public/PublicRequestBody.vue';
144
+ export { default as PublicLanguagePicker } from './components/public/PublicLanguagePicker.vue';
145
+ export type {
146
+ PublicBranding,
147
+ PublicEntry,
148
+ PublicFailure,
149
+ PublicLinkBase,
150
+ PublicApp,
151
+ PublicDropLimits,
152
+ PublicLocaleOption,
153
+ PublicNode,
154
+ PublicRequestInfo,
155
+ PublicShareInfo,
156
+ PublicShareKind,
157
+ } from './types/Public';
158
+ /* v3 §5 — the language list is not a constant: an app plugin may add one. */
159
+ export {
160
+ acceptableLocale,
161
+ availableLocales,
162
+ ensureLocaleStrings,
163
+ hasLocale,
164
+ isBuiltinLocale,
165
+ loadLocales,
166
+ localeLabel,
167
+ localeOwnTable,
168
+ localeStrings,
169
+ localeTable,
170
+ localesKnown,
171
+ localesVersion,
172
+ normalizeLocaleCode,
173
+ plausibleLocale,
174
+ registerLocale,
175
+ resetLocales,
176
+ setLocales,
177
+ setLocalesFromBranding,
178
+ unregisterPluginLocales,
179
+ BUILTIN_LOCALES,
180
+ } from './lib/uiLocales';
181
+ export type { OwnLocaleTable } from './lib/uiLocales';
182
+ /* v0.43.0 — CLDR plural categories: a language pack writes as many forms as
183
+ * its language has (Arabic six, Russian four), in both catalogues. */
184
+ export {
185
+ CLDR_ORDER,
186
+ pluralCategories,
187
+ pluralCategory,
188
+ pluralChoiceIndex,
189
+ } from './lib/plural';
190
+ export type { PluralCategory } from './lib/plural';
191
+ /* v3 §4-of-the-brief — theme, palette, density and language live on the
192
+ * ACCOUNT, not in one browser's localStorage. */
193
+ export {
194
+ configurePrefs,
195
+ currentPrefs,
196
+ flushPrefs,
197
+ hydratePrefs,
198
+ localPref,
199
+ localPrefs,
200
+ onPrefs,
201
+ onPrefsSettled,
202
+ prefsConfigured,
203
+ prefsHydrated,
204
+ prefsSettled,
205
+ resetPrefs,
206
+ savePref,
207
+ setLocalPref,
208
+ PREFS_PUT_DEBOUNCE_MS,
209
+ PREF_KEYS,
210
+ LOOK_KEYS,
211
+ PREF_LS_KEYS,
212
+ /* ⚠⚠ "Is anybody signed in?" — the synchronous, first-paint answer that
213
+ * decides whether a PERSON's palette and light/dark mode may be read at all.
214
+ * A host wires it in two places and no more: `sealSessionless()` on a public
215
+ * link before the first paint, `rememberSession()` the moment
216
+ * `/api/auth/me` answers. */
217
+ SESSION_LS_KEY,
218
+ hasSession,
219
+ rememberSession,
220
+ sealSessionless,
221
+ /* ⚠ The session ENDED: drop every per-person mirror this browser holds.
222
+ * An "and" beside `hasSession`, never an "instead of" — it covers only the
223
+ * sign-outs the app is told about. */
224
+ forgetPersonalPrefs,
225
+ registerPersonalMirror,
226
+ } from './lib/prefs';
227
+ export type { LookKey, PrefKey, PrefsConfig, UiPrefs } from './lib/prefs';
228
+ /* The first-use tour is offered to a PERSON once — the account's answer or
229
+ * this browser's, never once per mount. */
230
+ export { markTourSeen, offerTourOnce, resetTourState, tourSeen, TOUR_LS_KEY } from './lib/tour';
231
+ export { default as SurfaceRenderer } from './components/plugin/SurfaceRenderer.vue';
232
+ /* v2 — the surface BODY and the footer row, shared by every frame that draws
233
+ * one (the explorer's dialog, an inspector section, an app's full page, the
234
+ * public card). A host drawing its own frame takes these two rather than
235
+ * re-deriving what `submit` means. */
236
+ export { default as SurfaceConversation } from './components/plugin/SurfaceConversation.vue';
237
+ export { default as SurfaceFooterButtons } from './components/plugin/SurfaceFooterButtons.vue';
238
+ export { default as PluginPageView } from './components/plugin/PluginPageView.vue';
239
+ export { default as SurfaceSignaturePad } from './components/plugin/nodes/SurfaceSignaturePad.vue';
240
+ export { default as SurfacePdfFields } from './components/plugin/nodes/SurfacePdfFields.vue';
241
+ export { loadPdfjs, defaultPdfWorkerUrl } from './lib/pdfjsLoader';
242
+ export { fracToPdf, pdfToFrac, fracToPixel, pixelToFrac, clampFrac, fitPageWidth, normRotation } from './lib/pdfFieldsGeom';
243
+ export {
244
+ defineField,
245
+ fillValues,
246
+ isFieldOf,
247
+ isPlaced,
248
+ normalizeFields,
249
+ todayIso,
250
+ unplacedFields,
251
+ withFont,
252
+ PDF_FIELD_TYPES,
253
+ } from './lib/pdfFields';
254
+ export type { PdfField, PdfFieldType, PdfSigner, PdfFillEntry } from './lib/pdfFields';
255
+ export { signatureModes, isSignatureValue, SIGNATURE_UPLOAD_MAX_BYTES } from './lib/signaturePad';
256
+ export type { SignatureValue, SignatureMode } from './lib/signaturePad';
257
+ /* v2 — the five faces a signature may be written in, and what a `text` field
258
+ * on a PDF accepts. Both travel back to the plugin with the value, so a host
259
+ * that renders a stored signature reads them from here too. */
260
+ export { SIGN_FONTS, DEFAULT_SIGN_FONT, signFont, isSignFontKey } from './lib/signFonts';
261
+ export type { SignFont, SignFontKey } from './lib/signFonts';
262
+ /* ⚠ v3 §3.3 — `isRealDate` and `DATE_MASK` are gone with the `date` text
263
+ * rule. A date is asked for with the `date` FIELD TYPE; a host that was
264
+ * parsing `GG/AA/YYYY` out of a text field is reading a shape this product
265
+ * no longer produces. */
266
+ export {
267
+ applyRule,
268
+ normalizeRule,
269
+ ruleError,
270
+ ruleInputMode,
271
+ ruleMaxLength,
272
+ PDF_RULE_KINDS,
273
+ } from './lib/pdfFieldRules';
274
+ export type { PdfFieldRule, PdfRuleKind, PdfRuleError } from './lib/pdfFieldRules';
275
+ /* v2 — an app's hold on a file: the badge's words and the 423 refusal. */
276
+ export { lockOf, anyLocked, lockedRefusal, lockWords, lockUntilText, lockReasonText } from './lib/appLock';
277
+ /* How a failure is SAID — one table of words for every screen (lib/errorWords). */
278
+ export {
279
+ jobFailure,
280
+ looksTechnical,
281
+ networkFailure,
282
+ refusalCode,
283
+ refusalWords,
284
+ requestFailure,
285
+ sayFailure,
286
+ serverWords,
287
+ statusIsTelling,
288
+ statusWords,
289
+ } from './lib/errorWords';
290
+ export type { JobErrorCode, RequestFailure, SaidFailure } from './lib/errorWords';
291
+ export { legacyConvertGate, gateOnService } from './lib/serviceGate';
292
+ export type { AppLock, LockedRefusal, LockWordsHost } from './lib/appLock';
293
+ /* issue #34 — a symlink the server will NOT follow: what it is, why it will
294
+ not open, and the rule that every surface refuses it out loud. */
295
+ export { linkStateOf, linkWords, linkWordsFor, isUnopenableLink } from './lib/symlink';
296
+ export type { LinkState, LinkWords, LinkWordsHost } from './lib/symlink';
297
+ /* v2 — where a `page` view lives and how a host opens it. */
298
+ export { pluginPagePath, pluginPageUrl, isPagePlacement, PLUGIN_PAGE_SEGMENT } from './lib/pluginPage';
299
+ export type { PluginPageTarget } from './lib/pluginPage';
300
+ export type {
301
+ PluginText,
302
+ PluginApplies,
303
+ PluginActionRow,
304
+ PluginGatedRule,
305
+ PluginViewRow,
306
+ PluginActionsResponse,
307
+ PluginSurface,
308
+ SurfaceNode,
309
+ SurfaceAction,
310
+ PluginRunResult,
311
+ PluginViewEventBody,
312
+ PluginViewEventData,
313
+ SurfaceTone,
314
+ TextNodeProps,
315
+ PluginField,
316
+ FormNodeProps,
317
+ StepState,
318
+ StepsNodeProps,
319
+ ListRowAction,
320
+ ListNodeProps,
321
+ ProgressNodeProps,
322
+ PluginPerson,
323
+ PeoplePickerNodeProps,
324
+ PinInputNodeProps,
325
+ FileChooserNodeProps,
326
+ PreviewNodeProps,
327
+ PluginUsersResponse,
328
+ } from './types/Plugins';
329
+ export type { NavApp } from './components/SideNav.vue';
330
+
58
331
  /* issue #27 — the one "how far along is this queued op" rule, shared by the
59
332
  * explorer's operations center and a host's own tray (the admin app's). */
60
333
  export { opPercent } from './lib/opProgress';
@@ -159,7 +432,52 @@ export type { ResumeRecord, ResumeStorage } from './lib/uploadResume';
159
432
  export { useSelection } from './composables/useSelection';
160
433
  export { useKeyboardShortcuts } from './composables/useKeyboardShortcuts';
161
434
  export type { ShortcutHandlers } from './composables/useKeyboardShortcuts';
162
- export { useLocale, localeTag, formatByteSize, formatInstant } from './composables/useLocale';
435
+ export { useLocale, localeTag, formatByteSize, formatInstant, formatWhen, formatWhenFull, translate } from './composables/useLocale';
436
+ export type { WhenInput } from './composables/useLocale';
437
+ /* The Trash's "time left" sentence — the explorer's Trash view and the admin Trash page. */
438
+ export { trashTimeLeft } from './lib/trashTimeLeft';
439
+ /* A person, named one way on every surface (lib/personName; server twin model.PersonLabel). */
440
+ export { personName, personInitial } from './lib/personName';
441
+ export type { PersonLike } from './lib/personName';
442
+ /* RTL — the direction of a language (the server's `rtl` flag, never a second
443
+ * list) and the page-level rule that derives `<html dir>` from `<html lang>`. */
444
+ export {
445
+ clampAlongInline,
446
+ dirOfElement,
447
+ inlineEndX,
448
+ inlineKeyStep,
449
+ inlineSign,
450
+ inlineStartX,
451
+ foreignText,
452
+ isolateLtrRuns,
453
+ localeDir,
454
+ openAlongInline,
455
+ syncDocumentDir,
456
+ } from './lib/direction';
457
+ /* ONE notice for a connection that is down, however many requests fall over
458
+ while it is (lib/connection): a background call is folded into the shared
459
+ state, an action the person is waiting on still reports itself, and the
460
+ notice clears the moment anything gets an answer. */
461
+ export {
462
+ connectionDown,
463
+ connectionDownSince,
464
+ connectionFolded,
465
+ kindOfMethod,
466
+ noteRequestFailed,
467
+ noteRequestSucceeded,
468
+ resetConnectionNotice,
469
+ } from './lib/connection';
470
+ export type { FailureVerdict, RequestKind } from './lib/connection';
471
+ export { default as ConnectionNotice } from './components/ConnectionNotice.vue';
472
+ /* `filex 0.43.0` — which filex this is, in ONE spelling for every place that
473
+ says it (the account menus, user settings, the sign-in page). No catalogue
474
+ key: a name and a number need no translation (lib/productVersion). */
475
+ export { PRODUCT_NAME, productVersionLine } from './lib/productVersion';
476
+ export { default as ProductVersion } from './components/ProductVersion.vue';
477
+ /* The one splitter for list fields — "،" "," "、" are commas too. */
478
+ export { splitList, isListSeparatorKey } from './lib/listInput';
479
+ export type { SplitListOptions } from './lib/listInput';
480
+ export type { TextDirection } from './lib/direction';
163
481
  export type { ByteSizeOptions, ByteUnitKey } from './composables/useLocale';
164
482
  export { usePendingOps } from './composables/usePendingOps';
165
483
  export type { PendingOp, UsePendingOpsOptions } from './composables/usePendingOps';
@@ -182,7 +500,20 @@ export {
182
500
  DEFAULT_THEME_ID,
183
501
  themeById,
184
502
  useThemeState,
503
+ /* tema:v1 — operator-defined themes. The host fetches /api/appearance once
504
+ * at boot and publishes the result through `setCustomThemes`; everything
505
+ * else — the gallery, the palette cards, and the id resolution that makes a
506
+ * deleted theme fall back to stock — reads it from here. */
507
+ CUSTOM_THEME_PREFIX,
508
+ setCustomThemes,
509
+ useCustomThemes,
510
+ allThemes,
511
+ themeName,
185
512
  setTheme,
513
+ /* v3 — paint the palette the ACCOUNT holds without writing it back. */
514
+ applyStoredPalette,
515
+ /* tema:v1 — paint the INSTANCE's default without recording it as a choice. */
516
+ applyInstanceDefault,
186
517
  applyThemeToEl,
187
518
  syncThemeStyle,
188
519
  generateThemeCss,
@@ -194,6 +525,10 @@ export {
194
525
  THEME_MODE_LS_KEY,
195
526
  useThemeModeState,
196
527
  setThemeMode,
528
+ /* ⚠⚠ The session answer changed after the first paint (a public link, or
529
+ * an `/api/auth/me` that says nobody) — re-decide whose look this window
530
+ * wears. See `lib/prefs` → SESSION_LS_KEY. */
531
+ resolveSessionLook,
197
532
  } from './lib/themes';
198
533
  export type { ThemeDef, ThemeTokenMap, ThemeModePref } from './lib/themes';
199
534
  export { default as ThemeGallery } from './components/ThemeGallery.vue';
@@ -339,6 +674,7 @@ export { default as E2eRecoveryUnlockModal } from './components/E2eRecoveryUnloc
339
674
  component wrapper around this), the admin SPA mounts the SFC, embeds
340
675
  can do either — and none of them owns a copy of the form. */
341
676
  export { default as ConnectionsPanel } from './components/ConnectionsPanel.vue';
677
+ export { default as StorageTags } from './components/StorageTags.vue';
342
678
  export { default as StorageFields } from './components/StorageFields.vue';
343
679
  export { default as ConnectionGuideView } from './components/ConnectionGuideView.vue';
344
680
  export { default as S3KeysPanel } from './components/S3KeysPanel.vue';
@@ -391,27 +727,43 @@ export type {
391
727
  } from './types/Connections';
392
728
  /* /connections */
393
729
 
394
- /* tablo:t1 — per-folder view memory + the table configuration.
730
+ /* tablo:t1 — per-folder view memory, the defaults beneath it, and the
731
+ * explorer's table configuration.
395
732
  *
396
- * Exported because the HOST owns two things this module cannot reach: the
397
- * settings control that turns the memory on (`folderMemoryEnabled` /
398
- * `setFolderMemoryEnabled`), and the transport that reads and writes the
399
- * document (`attachViewPrefsStore` — the explorer wires its own, but a host
400
- * embedding the views directly has to). The rest is exported so the gates in
401
- * `web/tests/lib/viewPrefs.test.ts` drive the real module rather than a copy. */
733
+ * Exported because the HOST owns things this module cannot reach: the
734
+ * settings controls for the person's default folder view
735
+ * (`personFolderDefault` / `setPersonFolderDefault`) and the instance's
736
+ * (`readInstanceFolderDefault`, written by the admin settings page), and the
737
+ * transport that reads and writes the document (`attachViewPrefsHttp` — the
738
+ * explorer attaches it, and so does the admin app on sign-in, because its
739
+ * tables keep their columns in the same document). The rest is exported so
740
+ * the gates in `web/tests/lib/viewPrefs.test.ts` drive the real module rather
741
+ * than a copy.
742
+ *
743
+ * ⚠ v0.43: the "global default" (`globalViewPrefs` / `setGlobalViewPrefs`) is
744
+ * gone — it was written by every click and was the leak the owner reported
745
+ * ("tüm klasörlerde görünüm değişikliği geçerli oluyor"). A change belongs to
746
+ * the folder it was made in; the default is set on purpose. */
402
747
  export {
403
748
  COLUMNS,
749
+ EXPLORER_METRICS,
404
750
  FOLDER_CAP,
405
751
  NAME_AUTO,
406
752
  NAME_MIN,
753
+ ROOT_FOLDER_KEY,
407
754
  __flushViewPrefs,
408
755
  __resetViewPrefs,
756
+ applyToAllFolders,
409
757
  attachViewPrefsStore,
410
758
  canMoveColumn,
759
+ columnDropBand,
411
760
  columnHidden,
412
761
  columnOrder,
413
762
  columnWidth,
414
763
  columnsCustomised,
764
+ defaultFolderView,
765
+ detachViewPrefsStore,
766
+ folderColumnStore,
415
767
  folderIsRemembered,
416
768
  folderKey,
417
769
  folderMemoryEnabled,
@@ -419,16 +771,27 @@ export {
419
771
  forgetAllFolders,
420
772
  forgetFolder,
421
773
  freezeWidths,
774
+ instanceFolderDefault,
775
+ onViewPrefsApplied,
776
+ personFolderDefault,
777
+ readInstanceFolderDefault,
778
+ refreshViewPrefs,
422
779
  moveColumn,
423
780
  moveColumnBy,
424
781
  rememberFolder,
425
782
  rememberedCount,
783
+ reorderColumns,
426
784
  resetColumns,
785
+ resolveFolderView,
427
786
  setColumnHidden,
428
787
  setColumnWidth,
429
788
  setFolderMemoryEnabled,
789
+ setInstanceFolderDefault,
790
+ setPersonFolderDefault,
430
791
  tableLayout,
431
792
  touchFolder,
793
+ viewPrefsAttached,
794
+ viewPrefsReady,
432
795
  viewPrefsSlot,
433
796
  widthsAreAuto,
434
797
  } from './lib/viewPrefs';
@@ -436,10 +799,43 @@ export type {
436
799
  ColumnId,
437
800
  ColumnSpec,
438
801
  FolderPrefs,
802
+ FolderViewDefault,
803
+ InstanceFolderDefault,
439
804
  TableLayout,
440
805
  ViewPrefsSlot,
441
806
  ViewPrefsTransport,
442
807
  } from './lib/viewPrefs';
808
+ export { attachViewPrefsHttp, viewPrefsHttpTransport } from './lib/viewPrefsHttp';
809
+ export type { ViewPrefsHttpOptions } from './lib/viewPrefsHttp';
810
+
811
+ /* ═══ THE ONE TABLE ═══════════════════════════════════════════════════════
812
+ * `DataTable` is the explorer's list view with the files taken out of it, and
813
+ * it is the ONLY table in the product: the admin panel, the connection
814
+ * panels, My shares, the notifications list and an app's `list` node all
815
+ * render through it. A raw `<table>` or a second table component fails
816
+ * `web/tests/ui/tablePinnedActions.test.ts`; `docs/CONTRIBUTING.md` → "UI
817
+ * rules" says why (owner, 2026-09-21: "bir yere tablo gerekiyorsa bu tabloyu
818
+ * koymak zorundayız"). */
819
+ export { default as DataTable } from './components/DataTable.vue';
820
+ export type { DataColumn } from './components/DataTable.vue';
821
+ export {
822
+ columnDropBand as tableColumnDropBand,
823
+ createColumnStore,
824
+ emptyCols,
825
+ memoryBacking,
826
+ readCols,
827
+ } from './lib/tableColumns';
828
+ export type {
829
+ ColsState,
830
+ ColumnBacking,
831
+ ColumnStore,
832
+ LayoutMetrics,
833
+ TableColumnSpec,
834
+ TableLayout as GenericTableLayout,
835
+ } from './lib/tableColumns';
836
+ export { setTableSort, tableColumnBacking, tableSort } from './lib/tablePrefs';
837
+ export { TABLE_ENV, provideTableEnv, useTableEnv } from './lib/tableEnv';
838
+ export type { TableEnv } from './lib/tableEnv';
443
839
 
444
840
  /* gruplama — the date ladder every listing view draws its headings from.
445
841
  *
@@ -251,6 +251,16 @@ const GLYPHS: Record<string, string> = {
251
251
  link:
252
252
  '<path d="M10 13.5a4.5 4.5 0 0 0 6.8.5l2.7-2.7a4.5 4.5 0 0 0-6.36-6.36L11.6 6.5"/>' +
253
253
  '<path d="M14 10.5a4.5 4.5 0 0 0-6.8-.5l-2.7 2.7a4.5 4.5 0 0 0 6.36 6.36l1.53-1.53"/>',
254
+ /* baglan:b1 — "How to connect". A PLUG, deliberately not the chain above:
255
+ the empty screen's account menu draws both rows one under the other
256
+ ("Paylaştıklarım", then "Bağlantılar"), and one mark on two adjacent rows
257
+ is the misreading this set exists to stop. It is also the mark the admin
258
+ panel's own Connections entry already wears (Sidebar.vue, lucide `Cable`),
259
+ so the two ways into the same screen agree on what it looks like. */
260
+ connect:
261
+ '<path d="M9 8.5V3"/><path d="M15 8.5V3"/>' +
262
+ '<path d="M6 8.5h12v4.5a4.5 4.5 0 0 1-4.5 4.5h-3A4.5 4.5 0 0 1 6 13z"/>' +
263
+ '<path d="M12 17.5V21"/>',
254
264
 
255
265
  /* === ikon:emoji — the command palette's own rows =======================
256
266
  * The palette printed `{{ it.icon }}` as TEXT: `➜ 📁 ⬆ ▦ 🗑 ⟳ ↑ 🎨 ⌨ 🎓 ⧉ ◫
@@ -293,6 +303,20 @@ const GLYPHS: Record<string, string> = {
293
303
  * a fallback screen's 48px mark was the only full-colour thing on a grey
294
304
  * page. `progress` is drawn as an open ring because it is SPUN by CSS
295
305
  * (`@keyframes fe-spin`) — an hourglass cannot say "still going". */
306
+ /* An app plugin's row when its manifest names no glyph this set draws: a
307
+ puzzle piece, the mark every extension surface already wears. Rows whose
308
+ manifest icon IS a key here (`convert`, `lock`, …) get that glyph instead
309
+ — lib/pluginMenu decides, this only draws. */
310
+ plugin:
311
+ '<path d="M9.5 4.5a2 2 0 1 1 4 0h3a1.5 1.5 0 0 1 1.5 1.5v3a2 2 0 1 1 0 4v3a1.5 1.5 0 0 1-1.5 1.5h-3a2 2 0 1 1-4 0h-3A1.5 1.5 0 0 1 5 16v-3a2 2 0 1 1 0-4V6a1.5 1.5 0 0 1 1.5-1.5z"/>',
312
+ /* A pen signing on a line — the e-signature app's own icon (manifest
313
+ `icon: "sign"`). ⚠ Drawn here because a key the catalogue does not know
314
+ falls back to the generic plugin piece: the sidebar and every menu showed
315
+ the Signatures app as "some app" until it was (2026-09-21, a tester). */
316
+ sign:
317
+ '<path d="M12.5 20.5h8"/>' +
318
+ '<path d="M16.2 3.9a2.05 2.05 0 0 1 2.9 2.9L8 17.9l-3.9 1 1-3.9z"/>' +
319
+ '<path d="M14.6 5.5l2.9 2.9"/>',
296
320
  check: '<path d="M4.8 12.6l4.7 4.7L19.2 6.9"/>',
297
321
  alert:
298
322
  '<path d="M10.7 4.6L2.9 18.2a1.5 1.5 0 0 0 1.3 2.25h15.6a1.5 1.5 0 0 0 1.3-2.25L13.3 4.6a1.5 1.5 0 0 0-2.6 0z"/>' +
@@ -301,6 +325,27 @@ const GLYPHS: Record<string, string> = {
301
325
  progress: '<path d="M20.5 12a8.5 8.5 0 1 1-8.5-8.5"/>',
302
326
  };
303
327
 
328
+ /**
329
+ * ⚠ RTL — the glyphs that MEAN a direction along the line, and so are drawn
330
+ * mirrored in a right-to-left interface (`fe-aicon--dir`, one rule in
331
+ * base.css; `:dir(rtl)` on the icon itself, so an English explorer inside an
332
+ * Arabic page keeps its arrows):
333
+ *
334
+ * restore — the undo arrow ("put it back") points toward the start;
335
+ * goto — the arrow goes INTO the box, reading forward;
336
+ * sign-out — the arrow leaves through the door on the END side;
337
+ * copy-path — its `›` is a path separator, which points the way a path
338
+ * reads (the breadcrumb's does the same);
339
+ * nav, inspector — a panel on the start / end side of the window, and in RTL
340
+ * the navigation panel IS on the right.
341
+ *
342
+ * Deliberately NOT here: `open` (↗ "open in a new tab" points up and out, not
343
+ * along the line), `refresh` (a clock turns the same way in every script),
344
+ * `convert` (two opposed arrows, already symmetric), and everything vertical
345
+ * (upload, download, go-up, subfolders, density).
346
+ */
347
+ const DIRECTIONAL = new Set(['restore', 'goto', 'sign-out', 'copy-path', 'nav', 'inspector']);
348
+
304
349
  const SVG_CACHE = new Map<string, string>();
305
350
 
306
351
  /**
@@ -316,7 +361,7 @@ export function actionIconSvg(key: string): string {
316
361
  if (cached !== undefined) return cached;
317
362
  const glyph = GLYPHS[key];
318
363
  const svg = glyph
319
- ? '<svg class="fe-aicon" viewBox="0 0 24 24" fill="none" stroke="currentColor" ' +
364
+ ? `<svg class="${DIRECTIONAL.has(key) ? 'fe-aicon fe-aicon--dir' : 'fe-aicon'}" viewBox="0 0 24 24" fill="none" stroke="currentColor" ` +
320
365
  'stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" ' +
321
366
  `aria-hidden="true" focusable="false">${glyph}</svg>`
322
367
  : '';
@@ -46,6 +46,7 @@
46
46
  */
47
47
  import type { DriveFilters, PeopleOption } from './fileFilters';
48
48
  import { EMPTY_FILTERS } from './fileFilters';
49
+ import { splitList } from './listInput';
49
50
 
50
51
  /** Which fields the backend consults. Mirrors `search.ParseScope`. */
51
52
  export type AdvScope = 'name' | 'content' | 'all';
@@ -143,20 +144,26 @@ export function advSearchEmpty(req: AdvSearchRequest): boolean {
143
144
  * empty set by design, so the mistake would look like "there is nothing here".
144
145
  */
145
146
  export function parseTagList(raw: string): string[] {
146
- return raw
147
- .split(/[,\n]/)
148
- .map((s) => s.trim())
149
- .filter((s) => s !== '');
147
+ // ⚠ lib/listInput: "،" and "," are commas too (an Arabic or Chinese keyboard).
148
+ return splitList(raw, { newlines: true });
150
149
  }
151
150
 
152
151
  /**
153
- * True when the hit list came back at the cap, so the client-side filters ran
154
- * over a window rather than over everything that matches.
152
+ * True when the hit list was cut, so the client-side filters ran over a window
153
+ * rather than over everything that matches.
155
154
  *
156
155
  * The dialog and the count line say this out loud. A count that silently
157
156
  * describes "the first 50 hits, then filtered" as "N matching items" is the
158
157
  * exact shape of a number that looks measured and is not.
158
+ *
159
+ * `serverSaid` is the response's own `truncated` flag, and when the server
160
+ * sends one it is the answer. Only the server knows: without the index the
161
+ * manager reads a window of names several times the page size, so 400 rows can
162
+ * be a complete answer and 12 a cut one (the window was full and only 12 of
163
+ * its rows matched every word). A server too old to say leaves the guess this
164
+ * function always made — a full page is a cut page.
159
165
  */
160
- export function advSearchTruncated(returned: number, limit: number): boolean {
166
+ export function advSearchTruncated(returned: number, limit: number, serverSaid?: boolean): boolean {
167
+ if (typeof serverSaid === 'boolean') return serverSaid;
161
168
  return returned >= limit;
162
169
  }