@colixsystems/widget-sdk 0.121.0 → 0.122.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 CHANGED
@@ -70,7 +70,18 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
70
70
 
71
71
  ## Status
72
72
 
73
- `v0.119.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
73
+ `v0.122.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
74
+
75
+ ### What's new in 0.122.0 (contract 1.94.0)
76
+
77
+ **A refused filestore upload now says it is a PERMISSION problem (sc-6977).** `useFilestoreUpload` and `usePdfExport` relay whatever the destination decides, and that decision used to arrive as a bare `404 "Space not found"` no matter who asked. sc-5395 chose that masking for one good reason — an **anonymous** prober must not be able to enumerate which workspaces have opened their public space — but it applied to every caller, so a signed-in author uploading a cover image hit a dead end with nothing to act on.
78
+
79
+ The refusal now splits along what the caller can already see:
80
+
81
+ - **403** when the caller can READ the destination (the response body carries `code: "UPLOAD_NOT_PERMITTED"`, which the client puts on `err.details.code` — `err.code` is the client's own `"FORBIDDEN"`, so branch on `err.status`). The common case is a `public` space, which is world-readable but upload-**deny-by-default**: the workspace operator has to switch upload access on (Studio → File library → the space → upload access). Naming that discloses nothing the caller could not already see.
82
+ - **404 `"Space not found"`** wherever saying more would leak: an anonymous caller, or a position hidden from this one (a restricted folder, a foreign `owner_id`, another tenant). A masked refusal carries no `code`.
83
+
84
+ No hook signature moved, so nothing has to change to keep working — but a widget that branched on `404` alone should accept **both**, which is what the built-in Files widget now does. Folder creation and a folder move report the same split (`FOLDER_CREATE_NOT_PERMITTED` / `FOLDER_MOVE_NOT_PERMITTED`).
74
85
 
75
86
  ### What's new in 0.121.0 (contract 1.93.0)
76
87
 
package/dist/contract.cjs CHANGED
@@ -896,8 +896,14 @@ const HOOKS = [
896
896
  "snake_case fields the backend reads verbatim (`space_type`, " +
897
897
  "`owner_id`, `folder_id`, plus the binary `file`), and POSTs through " +
898
898
  "ctx.filestore.files.upload. `upload(file, { folderId?, compress? })` " +
899
- "resolves to the created file row or throws the wire error; a 404 " +
900
- "means the destination folder denied a write (REQ-FSH canWrite gate). " +
899
+ "resolves to the created file row or throws the wire error. Branch " +
900
+ "on `err.status`: 403 means the destination denied the add and says " +
901
+ "so, because the caller can see that position — for a public space " +
902
+ "that means an operator has not switched upload access on yet; 404 " +
903
+ "means the position is hidden from the caller (or it is anonymous), " +
904
+ "so the refusal states no reason. Treat both as \"you can't upload " +
905
+ "here\". The server's `UPLOAD_NOT_PERMITTED` is at " +
906
+ "`err.details.code`; `err.code` is the client's own \"FORBIDDEN\". " +
901
907
  "Uploaded images are compressed to WebP by the backend; pass " +
902
908
  "`compress: false` (on the hook or per upload) to store the file " +
903
909
  "byte-for-byte as the user provided it — use that whenever the " +
@@ -927,8 +933,10 @@ const HOOKS = [
927
933
  "(`{ spaceType, folderId? }`); the hook resolves owner_id from the host " +
928
934
  "context and POSTs JSON through ctx.filestore.files.exportPdf. " +
929
935
  "`exportToPdf(html, { fileName?, folderId? })` resolves to the created " +
930
- "file row (mime_type `application/pdf`) or throws the wire error; a 404 " +
931
- "means the destination folder denied a write, a 413 that the rendered " +
936
+ "file row (mime_type `application/pdf`) or throws the wire error; " +
937
+ "`err.status` 403/404 means the destination denied the add (403 " +
938
+ "when the caller can see the position, 404 when it cannot), a 413 " +
939
+ "that the rendered " +
932
940
  "PDF exceeded the size cap. The HTML is rendered by the host's PDF " +
933
941
  "service (headless Chromium) — the SAME server-side pipeline on the web " +
934
942
  "Player and the native export, so no browser-only PDF library is used.",
@@ -3603,7 +3611,17 @@ const CONTRACT = deepFreeze({
3603
3611
  // JOINED to the page refuses it. An app that colours its rail or bar
3604
3612
  // explicitly is untouched; an app that themed NOTHING moves from
3605
3613
  // `#ffffff` to the `#f8fafc` its page already was.
3606
- version: "1.93.0",
3614
+ // 1.94.0: behavioural (sc-6977) -- a filestore destination that refuses an
3615
+ // ADD now answers 403 with `code: UPLOAD_NOT_PERMITTED` when the caller
3616
+ // can already READ that position, and keeps the bare 404 only where
3617
+ // saying more would disclose something: an anonymous caller (sc-5395's
3618
+ // guarantee that a prober cannot enumerate which workspaces opened their
3619
+ // public space) or a position hidden from the caller. Affects
3620
+ // `useFilestoreUpload` and `usePdfExport`; no signature moved, but a
3621
+ // widget that branched on 404 alone should accept both, and the actionable
3622
+ // case -- a public space whose upload access is still off -- is now
3623
+ // distinguishable instead of arriving as a bare "Space not found".
3624
+ version: "1.94.0",
3607
3625
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
3608
3626
  hooks: HOOKS,
3609
3627
  primitives: PRIMITIVES,
package/dist/contract.js CHANGED
@@ -896,8 +896,14 @@ const HOOKS = [
896
896
  "snake_case fields the backend reads verbatim (`space_type`, " +
897
897
  "`owner_id`, `folder_id`, plus the binary `file`), and POSTs through " +
898
898
  "ctx.filestore.files.upload. `upload(file, { folderId?, compress? })` " +
899
- "resolves to the created file row or throws the wire error; a 404 " +
900
- "means the destination folder denied a write (REQ-FSH canWrite gate). " +
899
+ "resolves to the created file row or throws the wire error. Branch " +
900
+ "on `err.status`: 403 means the destination denied the add and says " +
901
+ "so, because the caller can see that position — for a public space " +
902
+ "that means an operator has not switched upload access on yet; 404 " +
903
+ "means the position is hidden from the caller (or it is anonymous), " +
904
+ "so the refusal states no reason. Treat both as \"you can't upload " +
905
+ "here\". The server's `UPLOAD_NOT_PERMITTED` is at " +
906
+ "`err.details.code`; `err.code` is the client's own \"FORBIDDEN\". " +
901
907
  "Uploaded images are compressed to WebP by the backend; pass " +
902
908
  "`compress: false` (on the hook or per upload) to store the file " +
903
909
  "byte-for-byte as the user provided it — use that whenever the " +
@@ -927,8 +933,10 @@ const HOOKS = [
927
933
  "(`{ spaceType, folderId? }`); the hook resolves owner_id from the host " +
928
934
  "context and POSTs JSON through ctx.filestore.files.exportPdf. " +
929
935
  "`exportToPdf(html, { fileName?, folderId? })` resolves to the created " +
930
- "file row (mime_type `application/pdf`) or throws the wire error; a 404 " +
931
- "means the destination folder denied a write, a 413 that the rendered " +
936
+ "file row (mime_type `application/pdf`) or throws the wire error; " +
937
+ "`err.status` 403/404 means the destination denied the add (403 " +
938
+ "when the caller can see the position, 404 when it cannot), a 413 " +
939
+ "that the rendered " +
932
940
  "PDF exceeded the size cap. The HTML is rendered by the host's PDF " +
933
941
  "service (headless Chromium) — the SAME server-side pipeline on the web " +
934
942
  "Player and the native export, so no browser-only PDF library is used.",
@@ -3603,7 +3611,17 @@ const CONTRACT = deepFreeze({
3603
3611
  // JOINED to the page refuses it. An app that colours its rail or bar
3604
3612
  // explicitly is untouched; an app that themed NOTHING moves from
3605
3613
  // `#ffffff` to the `#f8fafc` its page already was.
3606
- version: "1.93.0",
3614
+ // 1.94.0: behavioural (sc-6977) -- a filestore destination that refuses an
3615
+ // ADD now answers 403 with `code: UPLOAD_NOT_PERMITTED` when the caller
3616
+ // can already READ that position, and keeps the bare 404 only where
3617
+ // saying more would disclose something: an anonymous caller (sc-5395's
3618
+ // guarantee that a prober cannot enumerate which workspaces opened their
3619
+ // public space) or a position hidden from the caller. Affects
3620
+ // `useFilestoreUpload` and `usePdfExport`; no signature moved, but a
3621
+ // widget that branched on 404 alone should accept both, and the actionable
3622
+ // case -- a public space whose upload access is still off -- is now
3623
+ // distinguishable instead of arriving as a bare "Space not found".
3624
+ version: "1.94.0",
3607
3625
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
3608
3626
  hooks: HOOKS,
3609
3627
  primitives: PRIMITIVES,
package/dist/hooks.js CHANGED
@@ -3203,9 +3203,14 @@ export function useFilestoreFile(fileId) {
3203
3203
  * on web). FormData serialises both verbatim, so the same hook code path
3204
3204
  * feeds the multipart on both platforms.
3205
3205
  *
3206
- * A 404 from the backend means the destination folder denied a write
3207
- * the widget should surface a friendly "you can't upload here" message
3208
- * rather than the raw error.
3206
+ * The destination can refuse the add two ways (sc-6977). BRANCH ON
3207
+ * `err.status`: 403 means the caller may know why most often a public space
3208
+ * whose upload access an operator has not switched on — and 404 means the
3209
+ * position is hidden from it (or it is anonymous), so the refusal states no
3210
+ * reason. Treat both as "you can't upload here" rather than showing the raw
3211
+ * error. NOT `err.status === 403 && err.code === "UPLOAD_NOT_PERMITTED"`:
3212
+ * `err.code` is the CLIENT's taxonomy ("FORBIDDEN"), and the server's
3213
+ * `UPLOAD_NOT_PERMITTED` arrives at `err.details.code`.
3209
3214
  */
3210
3215
  export function useFilestoreUpload(options) {
3211
3216
  const ctx = useWidgetContextOrThrow("useFilestoreUpload");
@@ -3291,8 +3296,10 @@ export function useFilestoreUpload(options) {
3291
3296
  * so the capability behaves identically on both platforms (no browser-only PDF
3292
3297
  * library is involved).
3293
3298
  *
3294
- * A 404 means the destination folder denied a write; a 413 means the rendered
3295
- * PDF exceeded the size cap.
3299
+ * A 403 or 404 means the destination denied the add 403 when the caller can
3300
+ * see that position, 404 when it cannot; a 413 means the rendered PDF exceeded
3301
+ * the size cap. Branch on `err.status`; the server's `UPLOAD_NOT_PERMITTED` is
3302
+ * at `err.details.code`, not `err.code` (which is the client's "FORBIDDEN").
3296
3303
  */
3297
3304
  export function usePdfExport(options) {
3298
3305
  const ctx = useWidgetContextOrThrow("usePdfExport");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.121.0",
3
+ "version": "0.122.0",
4
4
  "description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
5
5
  "homepage": "https://github.com/Colix-AB/AppStudio",
6
6
  "type": "module",