@colixsystems/widget-sdk 0.100.0 → 0.101.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.
- package/README.md +24 -2
- package/dist/contract.cjs +14 -2
- package/dist/contract.js +14 -2
- package/dist/hooks.js +5 -1
- package/dist/linter.cjs +28 -0
- package/dist/linter.js +44 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -99,6 +99,28 @@ Host-integration surface only: no author-facing hook, prop, primitive, or manife
|
|
|
99
99
|
|
|
100
100
|
Host-integration surface only: no author-facing hook, prop, primitive, or manifest field changed. `CONTRACT.version` → `1.66.0`.
|
|
101
101
|
|
|
102
|
+
### What's new in 0.101.0 (contract 1.75.0)
|
|
103
|
+
|
|
104
|
+
**Render an image at the size you actually show it — `file.urls` (sc-5699).** Every Filestore file record now carries a delivery **size ladder** beside `url`:
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
const { files } = useFilestoreFiles({ spaceType: 'public' });
|
|
108
|
+
// a 128px grid downloads 128px images, not the 4096px originals
|
|
109
|
+
files.map((f) => <Image key={f.id} source={{ uri: f.urls.thumbnail }} />);
|
|
110
|
+
|
|
111
|
+
const { url, urls } = useFilestoreFile(fileId);
|
|
112
|
+
<Image source={{ uri: urls?.large }} />; // a detail view
|
|
113
|
+
<a href={url}>Download original</a>; // the full-size bytes
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The rungs are `thumbnail` (128px), `card` (512px), `large` (1024px), and `hero` (2048px), each a **longest-edge** target. Pick the next rung **up** from the size you render at, so a 2x/3x screen still has enough pixels — a 100px avatar wants `thumbnail`, a 400px card wants `card`.
|
|
117
|
+
|
|
118
|
+
`urls` is **always fully populated**, so it never needs a fallback branch: a type with no ladder — SVG, an animated GIF, a PDF, a video — points every rung at the original. A rung is also never *upscaled*: ask for `hero` on a 300px image and you get the 300px original rather than a blurry 2048px copy.
|
|
119
|
+
|
|
120
|
+
`useFilestoreFile` returns `urls` alongside `url`, and — like `url` — it is `null` until the fetch resolves, so read the top-level value rather than `file.urls`.
|
|
121
|
+
|
|
122
|
+
Requires `@colixsystems/filestore-client` ≥ 0.8.0. `CONTRACT.version` → `1.75.0`. Additive — `url` and `presigned_url` are unchanged, so a widget that ignores `urls` behaves exactly as before.
|
|
123
|
+
|
|
102
124
|
### What's new in 0.100.0 (contract 1.74.0)
|
|
103
125
|
|
|
104
126
|
**Opt out of image compression on upload — `useFilestoreUpload({ compress })` (sc-5402).** A file uploaded through `POST /api/v1/filestore/files` now has its raster images compressed to WebP again (EXIF-stripped, longest edge capped at 4096 px), which is the right default for anything the app renders. When the ORIGINAL bytes matter — a document archive, a photo the user re-downloads, anything with an exact-bytes requirement — pass `compress: false`, either on the hook or per call:
|
|
@@ -651,8 +673,8 @@ Also: `useFileSignatures(fileIds)` is now **self-scoped** (the caller's own sign
|
|
|
651
673
|
### What's new in 0.30.0
|
|
652
674
|
|
|
653
675
|
**Filestore browsing + BankID file signing for widgets (REQ-FS / REQ-SIGN).** Three new hooks read a newly-injected `ctx.filestore` (the `@colixsystems/filestore-client`, now constructed by both the web and native hosts):
|
|
654
|
-
- `useFilestoreFiles({ spaceType, folderId?, q?, type? })` → `{ files, loading, error, refetch }` — browses the end-user's Filestore space. The hook resolves `owner_id` from the host context (tenant for a project space, the app user for a personal space), so the widget only picks the space. Every row carries a ready-to-render `url` (its absolutized `presigned_url`), so a gallery renders `files.map(f => f.url)` directly — no per-row `useFilestoreFile` call.
|
|
655
|
-
- `useFilestoreFile(fileId)` → `{ file, url, loading, error, refetch }` — resolves ONE file id to a displayable `url` (its `presigned_url`, absolutized for web + native) via `ctx.filestore.files.get(id)`. **Read the top-level `url`** — the returned `file` is `null` until the fetch resolves (and for an empty id), so `file.url` throws on the first render; it is correct only after a null check. **Never compose a file URL yourself** — the bytes are served only from a server-signed token URL, so a hand-built path like `/api/files/<id>` can never resolve (the linter's `no-host-api-url` rule flags it, including the `${location.origin}/api/files/…` form). A datastore `FILE` column holds — and reads back as — that **bare file-id string**; it is NOT hydrated into an object the way a `RELATION` (`{ id, label }`) or `USER` (`{ id, name }`) column is, so pass the value straight to the hook (no `{ id }` / `{ url }` unwrapping guard). Empty / deleted / not-found ids resolve to `url: null` so a display widget shows a fallback. When you render `url` in an `<Image>` that fills its container, size it with `aspectRatio` (e.g. `{ width: "100%", aspectRatio: 1 }`) or pixels — never a percentage `height`, which React Native collapses to 0 against a content-sized parent, so the image loads but is invisible. Requires `files.read:*`.
|
|
676
|
+
- `useFilestoreFiles({ spaceType, folderId?, q?, type? })` → `{ files, loading, error, refetch }` — browses the end-user's Filestore space. The hook resolves `owner_id` from the host context (tenant for a project space, the app user for a personal space), so the widget only picks the space. Every row carries a ready-to-render `url` (its absolutized `presigned_url`), so a gallery renders `files.map(f => f.url)` directly — no per-row `useFilestoreFile` call. Every row ALSO carries `urls` — the size ladder `{ thumbnail, card, large, hero }` at 128 / 512 / 1024 / 2048px longest edge — so a thumbnail grid should render `f.urls.thumbnail` rather than pulling the full-size original for every tile.
|
|
677
|
+
- `useFilestoreFile(fileId)` → `{ file, url, urls, loading, error, refetch }` — resolves ONE file id to a displayable `url` (its `presigned_url`, absolutized for web + native) via `ctx.filestore.files.get(id)`. **Read the top-level `url`** — the returned `file` is `null` until the fetch resolves (and for an empty id), so `file.url` throws on the first render; it is correct only after a null check. **Never compose a file URL yourself** — the bytes are served only from a server-signed token URL, so a hand-built path like `/api/files/<id>` can never resolve (the linter's `no-host-api-url` rule flags it, including the `${location.origin}/api/files/…` form). A datastore `FILE` column holds — and reads back as — that **bare file-id string**; it is NOT hydrated into an object the way a `RELATION` (`{ id, label }`) or `USER` (`{ id, name }`) column is, so pass the value straight to the hook (no `{ id }` / `{ url }` unwrapping guard). Empty / deleted / not-found ids resolve to `url: null` so a display widget shows a fallback. When you render `url` in an `<Image>` that fills its container, size it with `aspectRatio` (e.g. `{ width: "100%", aspectRatio: 1 }`) or pixels — never a percentage `height`, which React Native collapses to 0 against a content-sized parent, so the image loads but is invisible. Requires `files.read:*`.
|
|
656
678
|
- `useFilestoreFolders({ spaceType, parentFolderId?, q?, enabled? })` → `{ folders, loading, error, refetch }` — the folder-navigation companion to `useFilestoreFiles`; pass `enabled:false` to suspend fetching.
|
|
657
679
|
- `useFilestoreUpload({ spaceType, folderId?, compress? })` → `{ upload, uploading, error, lastUploaded }` — POSTs a multipart upload to `ctx.filestore.files.upload`. Resolves `owner_id` from the host context (like the read hooks) so the widget only picks the space + destination folder. Uploaded images are compressed to WebP by the backend; pass `compress: false` (on the hook, or per `upload(file, { compress })`) to store the file byte-for-byte — use it whenever the original matters. Pair with the `<FilePicker>` primitive for the visible trigger. Requires the `files.write:*` scope.
|
|
658
680
|
- `useFileSignature(fileId)` → `{ status, qr, signerName, verdict, initiate, refresh, cancel, verify, … }` — drives a BankID signing flow for a file (the backend hashes the bytes server-side, binds the digest into the signature, and verifies the proof offline).
|
package/dist/contract.cjs
CHANGED
|
@@ -746,7 +746,15 @@ const HOOKS = [
|
|
|
746
746
|
"logged-out Player visitors. Reads ctx.filestore.files.list and unwraps " +
|
|
747
747
|
"{ data, meta } to the files array. Every row carries a ready-to-render " +
|
|
748
748
|
"`url` (its absolutized presigned_url, aliased by the filestore client) " +
|
|
749
|
-
"so a gallery renders `files.map(f => f.url)` directly."
|
|
749
|
+
"so a gallery renders `files.map(f => f.url)` directly. Every row ALSO " +
|
|
750
|
+
"carries `urls` — the delivery size ladder `{ thumbnail, card, large, " +
|
|
751
|
+
"hero }` at 128 / 512 / 1024 / 2048px longest edge. PREFER a rung over " +
|
|
752
|
+
"`url` whenever the image is rendered smaller than full size: a grid of " +
|
|
753
|
+
"thumbnails should read `f.urls.thumbnail`, a card list `f.urls.card`. " +
|
|
754
|
+
"Pick the next rung UP from the rendered size so a 2x/3x screen still " +
|
|
755
|
+
"has enough pixels. `urls` is always fully populated — a type with no " +
|
|
756
|
+
"ladder (SVG, GIF, a PDF) points every rung at the original — so it " +
|
|
757
|
+
"never needs a fallback branch.",
|
|
750
758
|
returnShape: {
|
|
751
759
|
files: "FilestoreFile[]",
|
|
752
760
|
loading: "boolean",
|
|
@@ -773,10 +781,14 @@ const HOOKS = [
|
|
|
773
781
|
"stays null for an empty id), so `file.url` throws on the first render. " +
|
|
774
782
|
"Every file record the client returns does carry the same `url` alias, " +
|
|
775
783
|
"so `file.url` is correct once you have null-checked `file`. " +
|
|
784
|
+
"`file.urls` carries the size ladder `{ thumbnail, card, large, hero }` " +
|
|
785
|
+
"(128 / 512 / 1024 / 2048px longest edge) — reach for a rung whenever " +
|
|
786
|
+
"the image renders smaller than full size, picking the next rung UP. " +
|
|
776
787
|
"NEVER build a file URL by hand from an id — no route serves one.",
|
|
777
788
|
returnShape: {
|
|
778
789
|
file: "FilestoreFile | null",
|
|
779
790
|
url: "string | null",
|
|
791
|
+
urls: "{ thumbnail, card, large, hero } | null",
|
|
780
792
|
loading: "boolean",
|
|
781
793
|
error: "Error | null",
|
|
782
794
|
refetch: "() => Promise<void>",
|
|
@@ -3209,7 +3221,7 @@ const CONTRACT = deepFreeze({
|
|
|
3209
3221
|
// are compressed to WebP by default; `compress: false` stores the file
|
|
3210
3222
|
// byte-for-byte. Existing callers are unaffected — the field is only sent
|
|
3211
3223
|
// when the opt-out is chosen.
|
|
3212
|
-
version: "1.
|
|
3224
|
+
version: "1.75.0",
|
|
3213
3225
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
3214
3226
|
hooks: HOOKS,
|
|
3215
3227
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -746,7 +746,15 @@ const HOOKS = [
|
|
|
746
746
|
"logged-out Player visitors. Reads ctx.filestore.files.list and unwraps " +
|
|
747
747
|
"{ data, meta } to the files array. Every row carries a ready-to-render " +
|
|
748
748
|
"`url` (its absolutized presigned_url, aliased by the filestore client) " +
|
|
749
|
-
"so a gallery renders `files.map(f => f.url)` directly."
|
|
749
|
+
"so a gallery renders `files.map(f => f.url)` directly. Every row ALSO " +
|
|
750
|
+
"carries `urls` — the delivery size ladder `{ thumbnail, card, large, " +
|
|
751
|
+
"hero }` at 128 / 512 / 1024 / 2048px longest edge. PREFER a rung over " +
|
|
752
|
+
"`url` whenever the image is rendered smaller than full size: a grid of " +
|
|
753
|
+
"thumbnails should read `f.urls.thumbnail`, a card list `f.urls.card`. " +
|
|
754
|
+
"Pick the next rung UP from the rendered size so a 2x/3x screen still " +
|
|
755
|
+
"has enough pixels. `urls` is always fully populated — a type with no " +
|
|
756
|
+
"ladder (SVG, GIF, a PDF) points every rung at the original — so it " +
|
|
757
|
+
"never needs a fallback branch.",
|
|
750
758
|
returnShape: {
|
|
751
759
|
files: "FilestoreFile[]",
|
|
752
760
|
loading: "boolean",
|
|
@@ -773,10 +781,14 @@ const HOOKS = [
|
|
|
773
781
|
"stays null for an empty id), so `file.url` throws on the first render. " +
|
|
774
782
|
"Every file record the client returns does carry the same `url` alias, " +
|
|
775
783
|
"so `file.url` is correct once you have null-checked `file`. " +
|
|
784
|
+
"`file.urls` carries the size ladder `{ thumbnail, card, large, hero }` " +
|
|
785
|
+
"(128 / 512 / 1024 / 2048px longest edge) — reach for a rung whenever " +
|
|
786
|
+
"the image renders smaller than full size, picking the next rung UP. " +
|
|
776
787
|
"NEVER build a file URL by hand from an id — no route serves one.",
|
|
777
788
|
returnShape: {
|
|
778
789
|
file: "FilestoreFile | null",
|
|
779
790
|
url: "string | null",
|
|
791
|
+
urls: "{ thumbnail, card, large, hero } | null",
|
|
780
792
|
loading: "boolean",
|
|
781
793
|
error: "Error | null",
|
|
782
794
|
refetch: "() => Promise<void>",
|
|
@@ -3209,7 +3221,7 @@ const CONTRACT = deepFreeze({
|
|
|
3209
3221
|
// are compressed to WebP by default; `compress: false` stores the file
|
|
3210
3222
|
// byte-for-byte. Existing callers are unaffected — the field is only sent
|
|
3211
3223
|
// when the opt-out is chosen.
|
|
3212
|
-
version: "1.
|
|
3224
|
+
version: "1.75.0",
|
|
3213
3225
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
3214
3226
|
hooks: HOOKS,
|
|
3215
3227
|
primitives: PRIMITIVES,
|
package/dist/hooks.js
CHANGED
|
@@ -2768,7 +2768,11 @@ export function useFilestoreFile(fileId) {
|
|
|
2768
2768
|
|
|
2769
2769
|
const url =
|
|
2770
2770
|
file && typeof file.presigned_url === "string" ? file.presigned_url : null;
|
|
2771
|
-
|
|
2771
|
+
// REQ-FS-15: the size ladder, surfaced beside `url` for the same reason —
|
|
2772
|
+
// `file` is null until the fetch resolves, so `file.urls` throws on first
|
|
2773
|
+
// render. Null until then, exactly like `url`.
|
|
2774
|
+
const urls = file && file.urls && typeof file.urls === "object" ? file.urls : null;
|
|
2775
|
+
return { file, url, urls, loading, error, refetch };
|
|
2772
2776
|
}
|
|
2773
2777
|
|
|
2774
2778
|
/**
|
package/dist/linter.cjs
CHANGED
|
@@ -392,6 +392,33 @@ function _hostApiUrlRules(source) {
|
|
|
392
392
|
return findings;
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
+
// sc-5619 — see linter.js for the rationale comment. The two files must stay
|
|
396
|
+
// in lockstep (the contract test asserts behaviour-equivalence).
|
|
397
|
+
const PAGE_URL_RE = /["'`][^"'`]*\/play\/|\/play\/\$\{/;
|
|
398
|
+
|
|
399
|
+
function _handBuiltPageUrlRules(source) {
|
|
400
|
+
const findings = [];
|
|
401
|
+
const lines = _stripNonCode(source, { keepStrings: true }).split(/\r?\n/);
|
|
402
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
403
|
+
if (!PAGE_URL_RE.test(lines[i])) continue;
|
|
404
|
+
findings.push({
|
|
405
|
+
rule: "no-hand-built-page-url",
|
|
406
|
+
severity: "error",
|
|
407
|
+
label:
|
|
408
|
+
`source builds a "/play/…" app URL by hand — the URL shape belongs ` +
|
|
409
|
+
`to the host, not the widget, so a hardcoded one breaks on a ` +
|
|
410
|
+
`workspace's own domain (where a page is "/page/<slug>", with no ` +
|
|
411
|
+
`workspace id) and leaks the workspace id into the address bar. ` +
|
|
412
|
+
`Navigate with useNavigation().goTo(pageId) — the page id comes from ` +
|
|
413
|
+
`a pageRef prop — and follow a link you did not build with ` +
|
|
414
|
+
`useNavigation().openLink(link).`,
|
|
415
|
+
line: i + 1,
|
|
416
|
+
snippet: lines[i].trim().slice(0, 200),
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
return findings;
|
|
420
|
+
}
|
|
421
|
+
|
|
395
422
|
// sc-4085 — see linter.js for the rationale comment. The two files must stay
|
|
396
423
|
// in lockstep (the contract test asserts behaviour-equivalence).
|
|
397
424
|
function _translationApiRules(source) {
|
|
@@ -1166,6 +1193,7 @@ function lintSource(source, options) {
|
|
|
1166
1193
|
);
|
|
1167
1194
|
findings.push(..._hostApiUrlRules(source));
|
|
1168
1195
|
findings.push(..._translationApiRules(source));
|
|
1196
|
+
findings.push(..._handBuiltPageUrlRules(source));
|
|
1169
1197
|
findings.push(..._lucideIconRules(source));
|
|
1170
1198
|
findings.push(..._reactInScopeRules(source));
|
|
1171
1199
|
findings.push(..._imagePercentHeightRules(source));
|
package/dist/linter.js
CHANGED
|
@@ -437,6 +437,49 @@ function _hostApiUrlRules(source) {
|
|
|
437
437
|
return findings;
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
+
// sc-5619 — no-hand-built-page-url.
|
|
441
|
+
//
|
|
442
|
+
// A published app answers on two URL shapes: `/play/<tenantId>/page/<slug>` on
|
|
443
|
+
// the platform host, and `/page/<slug>` at the workspace's own domain. The host
|
|
444
|
+
// owns that choice (`playerPath.js` on web, react-navigation natively), so a
|
|
445
|
+
// widget that builds the path itself — almost always from the `workspace.id` the
|
|
446
|
+
// SDK hands it — hardcodes ONE shape and breaks on the other: on a custom domain
|
|
447
|
+
// it rewrites the customer's clean domain back to the platform form and
|
|
448
|
+
// republishes their workspace id in the address bar.
|
|
449
|
+
//
|
|
450
|
+
// An `error` with no opt-out directive, like `no-external-translation-api`: the
|
|
451
|
+
// SDK covers every legitimate case, so there is no correct code to rescue.
|
|
452
|
+
// Navigate with `useNavigation().goTo(pageId)`, and follow a link whose shape
|
|
453
|
+
// the widget does NOT control (a datastore value, a notification link) with
|
|
454
|
+
// `useNavigation().openLink(link)` — that routes through the shared resolver,
|
|
455
|
+
// which already accepts either shape and rebuilds the one this host uses.
|
|
456
|
+
const PAGE_URL_RE = /["'`][^"'`]*\/play\/|\/play\/\$\{/;
|
|
457
|
+
|
|
458
|
+
function _handBuiltPageUrlRules(source) {
|
|
459
|
+
const findings = [];
|
|
460
|
+
// Strings kept, comments blanked: the literal lives IN a string, but a comment
|
|
461
|
+
// or doc line quoting `/play/<id>` must not block a publish.
|
|
462
|
+
const lines = _stripNonCode(source, { keepStrings: true }).split(/\r?\n/);
|
|
463
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
464
|
+
if (!PAGE_URL_RE.test(lines[i])) continue;
|
|
465
|
+
findings.push({
|
|
466
|
+
rule: "no-hand-built-page-url",
|
|
467
|
+
severity: "error",
|
|
468
|
+
label:
|
|
469
|
+
`source builds a "/play/…" app URL by hand — the URL shape belongs ` +
|
|
470
|
+
`to the host, not the widget, so a hardcoded one breaks on a ` +
|
|
471
|
+
`workspace's own domain (where a page is "/page/<slug>", with no ` +
|
|
472
|
+
`workspace id) and leaks the workspace id into the address bar. ` +
|
|
473
|
+
`Navigate with useNavigation().goTo(pageId) — the page id comes from ` +
|
|
474
|
+
`a pageRef prop — and follow a link you did not build with ` +
|
|
475
|
+
`useNavigation().openLink(link).`,
|
|
476
|
+
line: i + 1,
|
|
477
|
+
snippet: lines[i].trim().slice(0, 200),
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
return findings;
|
|
481
|
+
}
|
|
482
|
+
|
|
440
483
|
// sc-4085 — no-external-translation-api.
|
|
441
484
|
//
|
|
442
485
|
// Translation is a platform capability, not a third-party API: `useTranslate()`
|
|
@@ -1327,6 +1370,7 @@ export function lintSource(source, options) {
|
|
|
1327
1370
|
// REQ-WSDK-PLATFORM §3.5: soft host-API URL warning (does not block).
|
|
1328
1371
|
findings.push(..._hostApiUrlRules(source));
|
|
1329
1372
|
findings.push(..._translationApiRules(source));
|
|
1373
|
+
findings.push(..._handBuiltPageUrlRules(source));
|
|
1330
1374
|
findings.push(..._lucideIconRules(source));
|
|
1331
1375
|
// sc-2353 — widget source must be self-contained (reference React ⇒ import it).
|
|
1332
1376
|
findings.push(..._reactInScopeRules(source));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.101.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
],
|
|
49
49
|
"scripts": {
|
|
50
50
|
"build": "node scripts/build.js",
|
|
51
|
-
"test": "node --test src/__tests__/contract.test.js src/__tests__/hooks-users.test.js src/__tests__/hooks-groups.test.js src/__tests__/hooks-invites.test.js src/__tests__/hooks-schema.test.js src/__tests__/hooks-assets-by-tag.test.js src/__tests__/hooks-filestore-upload.test.js src/__tests__/hooks-filestore-file.test.js src/__tests__/hooks-mutation.test.js src/__tests__/hooks-payments.test.js src/__tests__/hooks-record-permissions.test.js src/__tests__/hooks-geolocation.test.js src/__tests__/hooks-section-empty.test.js src/__tests__/hooks-widget-event.test.js src/__tests__/hooks-widget-input.test.js src/__tests__/hooks-identification.test.js src/__tests__/hooks-subscription.test.js src/__tests__/hooks-volatile-query-key.test.js src/__tests__/linter-users-scope.test.js src/__tests__/linter-comments.test.js src/__tests__/linter-translation-api.test.js src/__tests__/linter-image-height.test.js src/__tests__/linter-measured-padding.test.js src/__tests__/linter-payment-error.test.js src/__tests__/linter-platform.test.js src/__tests__/linter-react-import.test.js src/__tests__/lucide-icon-names.test.js src/__tests__/lucideIconName.test.js src/__tests__/manifest-actions.test.js src/__tests__/widget-translations.test.js src/__tests__/hooks-translate.test.js src/__tests__/devserver.test.js src/__tests__/host-externals.test.js src/__tests__/datetimepicker.test.js src/__tests__/property-schema-resolve.test.js src/__tests__/theme-components-parity.test.js src/__tests__/navigation-parity.test.js src/__tests__/theme-depth-tokens.test.js src/__tests__/toast-host.test.js src/__tests__/hooks-domain-error-mapping.test.js src/__tests__/linter-datastore-error.test.js src/__tests__/linter-write-gating.test.js src/__tests__/hooks-speech-to-text.test.js src/__tests__/hooks-bound-columns.test.js src/__tests__/hooks-stable-query.test.js src/__tests__/hooks-can-write.test.js"
|
|
51
|
+
"test": "node --test src/__tests__/contract.test.js src/__tests__/hooks-users.test.js src/__tests__/hooks-groups.test.js src/__tests__/hooks-invites.test.js src/__tests__/hooks-schema.test.js src/__tests__/hooks-assets-by-tag.test.js src/__tests__/hooks-filestore-upload.test.js src/__tests__/hooks-filestore-file.test.js src/__tests__/hooks-mutation.test.js src/__tests__/hooks-payments.test.js src/__tests__/hooks-record-permissions.test.js src/__tests__/hooks-geolocation.test.js src/__tests__/hooks-section-empty.test.js src/__tests__/hooks-widget-event.test.js src/__tests__/hooks-widget-input.test.js src/__tests__/hooks-identification.test.js src/__tests__/hooks-subscription.test.js src/__tests__/hooks-volatile-query-key.test.js src/__tests__/linter-users-scope.test.js src/__tests__/linter-comments.test.js src/__tests__/linter-translation-api.test.js src/__tests__/linter-page-url.test.js src/__tests__/linter-image-height.test.js src/__tests__/linter-measured-padding.test.js src/__tests__/linter-payment-error.test.js src/__tests__/linter-platform.test.js src/__tests__/linter-react-import.test.js src/__tests__/lucide-icon-names.test.js src/__tests__/lucideIconName.test.js src/__tests__/manifest-actions.test.js src/__tests__/widget-translations.test.js src/__tests__/hooks-translate.test.js src/__tests__/devserver.test.js src/__tests__/host-externals.test.js src/__tests__/datetimepicker.test.js src/__tests__/property-schema-resolve.test.js src/__tests__/theme-components-parity.test.js src/__tests__/navigation-parity.test.js src/__tests__/theme-depth-tokens.test.js src/__tests__/toast-host.test.js src/__tests__/hooks-domain-error-mapping.test.js src/__tests__/linter-datastore-error.test.js src/__tests__/linter-write-gating.test.js src/__tests__/hooks-speech-to-text.test.js src/__tests__/hooks-bound-columns.test.js src/__tests__/hooks-stable-query.test.js src/__tests__/hooks-can-write.test.js"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
54
54
|
"node": ">=18"
|