@startsimpli/integrations 0.2.33 → 0.2.35
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.
- package/package.json +1 -1
- package/src/__tests__/connection-activity.test.tsx +160 -0
- package/src/__tests__/corpus-folders.test.tsx +727 -0
- package/src/__tests__/corpus-pages.test.tsx +4 -4
- package/src/__tests__/library-failure-reason.test.tsx +178 -0
- package/src/components/FolderTree.tsx +425 -0
- package/src/components/index.ts +6 -0
- package/src/components/pages/ConnectionsPage.tsx +174 -68
- package/src/components/pages/LibraryPage.tsx +333 -35
- package/src/corpus/client.ts +123 -9
- package/src/corpus/entities.ts +75 -0
- package/src/corpus/fixtures.ts +263 -1
- package/src/corpus/index.ts +8 -1
- package/src/corpus/types.ts +121 -0
package/src/corpus/types.ts
CHANGED
|
@@ -127,6 +127,16 @@ export interface SourceDocument {
|
|
|
127
127
|
scope: CorpusScope;
|
|
128
128
|
/** When the connector last wrote this row. Drives the freshness claim. */
|
|
129
129
|
ingestedAt?: string | null;
|
|
130
|
+
/**
|
|
131
|
+
* WHERE IT LIVES — the CONTAINER, `/Contracts`, never `/Contracts/SOW.pdf`
|
|
132
|
+
* (start-simpli-api#369 ships `path` with exactly that meaning).
|
|
133
|
+
*
|
|
134
|
+
* Absent on a record whose source has no notion of a folder — a meeting note,
|
|
135
|
+
* an email — and that absence is honest rather than a gap to be filled: those
|
|
136
|
+
* records live in no folder and are reachable from the top of the Library, not
|
|
137
|
+
* from a bucket invented for them.
|
|
138
|
+
*/
|
|
139
|
+
folder?: string;
|
|
130
140
|
/**
|
|
131
141
|
* Attribute names the SERVER withheld from this reader
|
|
132
142
|
* (bd startsim-768w.38.33). The tenant's per-field read gate strips them from
|
|
@@ -222,6 +232,34 @@ export interface SourceFreshness {
|
|
|
222
232
|
status: 'ok' | 'stale' | 'error' | 'never';
|
|
223
233
|
/** Why it is not `ok`, in the words the server used. */
|
|
224
234
|
detail?: string;
|
|
235
|
+
/**
|
|
236
|
+
* WHY THE LAST SYNC FAILED, in the vendor's own words, and a DIFFERENT FACT
|
|
237
|
+
* FROM THE CREDENTIAL'S VERDICT (bd startsim-ei8gm.2).
|
|
238
|
+
*
|
|
239
|
+
* The Tandem Granola card read "Failing — 20 syncs in a row failed, synced
|
|
240
|
+
* 16d ago" for sixteen days and nothing said why. It was our own 256 KiB
|
|
241
|
+
* egress cap — the per-note detail response peaked at 351 KB — which is a
|
|
242
|
+
* minutes-long diagnosis for anyone who could see the text and sixteen days
|
|
243
|
+
* for anyone who could not.
|
|
244
|
+
*
|
|
245
|
+
* `check_error` COULD NOT HAVE ANSWERED IT, which is why this is its own
|
|
246
|
+
* field rather than a wider read of that one. `check()` and `poll()` are
|
|
247
|
+
* different requests: Granola's check asks for `page_size=1` and passes,
|
|
248
|
+
* while its poll asks for thirty notes with transcripts and aborts. The
|
|
249
|
+
* credential verified cleanly throughout, so a strip built on `check_error`
|
|
250
|
+
* alone can count twenty failures and name none of them — and it sends the
|
|
251
|
+
* reader to re-enter a key that was never the problem.
|
|
252
|
+
*
|
|
253
|
+
* Central sends it only while the connection's NEWEST run is the failed one
|
|
254
|
+
* (`SubtreeConnectionsView._last_failure_reasons`), so it is never last
|
|
255
|
+
* Tuesday's error quoted over a fix that has already landed. It is SCRUBBED
|
|
256
|
+
* server-side against that connection's own stored credential plus the
|
|
257
|
+
* platform app_secret — a vendor can quote our key back inside its own error
|
|
258
|
+
* body — and a reason that could not be scrubbed arrives as a sentence saying
|
|
259
|
+
* so rather than as the raw text. Nothing here re-checks it; there is exactly
|
|
260
|
+
* one redaction path and it is not in the browser.
|
|
261
|
+
*/
|
|
262
|
+
lastError?: string;
|
|
225
263
|
}
|
|
226
264
|
|
|
227
265
|
/** DRF PageNumberPagination, which is what the tenant data API speaks. */
|
|
@@ -261,6 +299,72 @@ export interface DocumentQuery {
|
|
|
261
299
|
/** ISO instants, filtering on `occurredAt`. */
|
|
262
300
|
occurredAfter?: string;
|
|
263
301
|
occurredBefore?: string;
|
|
302
|
+
/**
|
|
303
|
+
* THE FILES ON ONE SHELF — the documents whose container is EXACTLY this
|
|
304
|
+
* folder, which is what clicking a folder in a file browser means. Goes out as
|
|
305
|
+
* `?attr.path=<folder>`.
|
|
306
|
+
*/
|
|
307
|
+
folder?: string;
|
|
308
|
+
/**
|
|
309
|
+
* THE WHOLE SUBTREE — this folder and everything beneath it. Goes out as
|
|
310
|
+
* `?attr.path__under=<folder>`.
|
|
311
|
+
*
|
|
312
|
+
* TWO FIELDS AND NOT ONE, AND `__startswith` IS NEITHER OF THEM. The tenant's
|
|
313
|
+
* `filters.subtree_bound()` spells out why: `startswith=/Contracts` also
|
|
314
|
+
* matches the sibling `/Contracts-old`, and `startswith=/Contracts/` misses
|
|
315
|
+
* the documents sitting directly IN `/Contracts`, which is most of what a
|
|
316
|
+
* person means by the folder. The correct bound is the UNION of the two, a
|
|
317
|
+
* list endpoint ANDs its filters so a caller cannot express a union, and that
|
|
318
|
+
* is why the server ships it as ONE parameter. Reach for this, never for a
|
|
319
|
+
* prefix a client assembled.
|
|
320
|
+
*/
|
|
321
|
+
underFolder?: string;
|
|
322
|
+
page?: number;
|
|
323
|
+
pageSize?: number;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* A CONTAINER IN THE CORPUS — one node of the folder tree.
|
|
328
|
+
*
|
|
329
|
+
* NOTHING MINTS ONE (bd startsim-maios). A folder is DERIVED at read time from
|
|
330
|
+
* the `path` attribute every file-shaped source already carries, so it has no id
|
|
331
|
+
* and no row: a rename re-mints nothing and orphans nothing, which is exactly
|
|
332
|
+
* the failure a materialised folder table would have. That is also why `path`
|
|
333
|
+
* below is the address — there is no other.
|
|
334
|
+
*/
|
|
335
|
+
export interface CorpusFolder {
|
|
336
|
+
/**
|
|
337
|
+
* The FULL path, as the server returned it — `/Contracts/2026`, never `2026`.
|
|
338
|
+
*
|
|
339
|
+
* IT GOES STRAIGHT BACK OUT, unmodified, as the next `under=` and as
|
|
340
|
+
* `folder`. The client never joins segments: doing so is a small escaping bug
|
|
341
|
+
* in every client that tries, and the server returns the whole path precisely
|
|
342
|
+
* so nobody has to.
|
|
343
|
+
*/
|
|
344
|
+
path: string;
|
|
345
|
+
/** The last segment, for display only. Derived from `path`, never sent back. */
|
|
346
|
+
name: string;
|
|
347
|
+
/**
|
|
348
|
+
* DESCENDANT documents, not direct children — `/Contracts` counts the ones in
|
|
349
|
+
* `/Contracts/2026/Q1` too. That is the number a person reading a collapsed
|
|
350
|
+
* folder wants, and it is why a folder can show 12 and open onto no files of
|
|
351
|
+
* its own.
|
|
352
|
+
*/
|
|
353
|
+
count: number;
|
|
354
|
+
/**
|
|
355
|
+
* Whether anything sits BELOW this folder. The disclosure control, answered by
|
|
356
|
+
* the same response that listed the folder — so drawing a triangle costs no
|
|
357
|
+
* probe request per node.
|
|
358
|
+
*/
|
|
359
|
+
hasChildren: boolean;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* One level of the tree. `under` is a folder the server previously returned (or
|
|
364
|
+
* `/` for the root, which means the whole corpus).
|
|
365
|
+
*/
|
|
366
|
+
export interface FolderQuery {
|
|
367
|
+
under: string;
|
|
264
368
|
page?: number;
|
|
265
369
|
pageSize?: number;
|
|
266
370
|
}
|
|
@@ -275,6 +379,23 @@ export interface CorpusApi {
|
|
|
275
379
|
listParties(query?: { search?: string; page?: number; pageSize?: number }): Promise<CorpusPage<Party>>;
|
|
276
380
|
/** Per-source freshness, for the strip at the top of the Library. */
|
|
277
381
|
listSources(): Promise<SourceFreshness[]>;
|
|
382
|
+
/**
|
|
383
|
+
* THE CHILD FOLDERS of one container, paginated (bd startsim-ei8gm.5).
|
|
384
|
+
*
|
|
385
|
+
* The second of the two reads a navigable tree needs: `listDocuments({folder})`
|
|
386
|
+
* gives the files on a shelf, this gives the shelves below it. Server-side and
|
|
387
|
+
* ONE LEVEL AT A TIME (CLAUDE.md rule 8) — reading every document's path and
|
|
388
|
+
* building the tree in the browser is the thing this exists to remove; it
|
|
389
|
+
* works at 247 records and expires in the low thousands.
|
|
390
|
+
*
|
|
391
|
+
* OPTIONAL, and a deployment can also HAVE it and refuse it. The tenant route
|
|
392
|
+
* shipped in start-simpli-api #391, so a fork built before that answers 404
|
|
393
|
+
* and an older one that never learned the parameters answers 400. Both mean
|
|
394
|
+
* the same thing to a caller — this corpus cannot enumerate folders — so the
|
|
395
|
+
* composer treats a failure exactly like an absent method and renders the flat
|
|
396
|
+
* list it rendered before. Never a half-drawn tree.
|
|
397
|
+
*/
|
|
398
|
+
listFolders?(query: FolderQuery): Promise<CorpusPage<CorpusFolder>>;
|
|
278
399
|
/**
|
|
279
400
|
* Optional because a tenant with no payment connector has no money events, and
|
|
280
401
|
* a corpus surface that requires one would fail the Dropbox-only test.
|