@colixsystems/widget-sdk 0.262.0 → 0.264.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 +10 -2
- package/dist/contract.cjs +8 -3
- package/dist/contract.js +8 -3
- package/dist/index.d.ts +9 -5
- package/dist/tabs-tokens.js +3 -0
- package/dist/tabs-view.js +18 -9
- package/dist/tabs.native.js +2 -2
- package/package.json +1 -1
- package/src/contract.js +8 -3
package/README.md
CHANGED
|
@@ -86,7 +86,15 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
86
86
|
|
|
87
87
|
## Status
|
|
88
88
|
|
|
89
|
-
`v0.
|
|
89
|
+
`v0.264.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**.
|
|
90
|
+
|
|
91
|
+
### What's new in 0.264.0 (contract 1.214.0)
|
|
92
|
+
|
|
93
|
+
**A `datastoreTemplate` column can be a `UUID`.** It holds a UUID, checked on every write and stored lowercase, so an id typed in capitals still matches. `defaultValue: "@uuid"` gives every new row its own random id, and `defaultLocked: true` with it makes that id server-generated and unforgeable. Sample `rows` may seed one. `DatastoreSchemaColumn.data_type` now also lists `UUID` (and `DATETIME`, which was missing). Additive.
|
|
94
|
+
|
|
95
|
+
### What's new in 0.263.0 (contract 1.213.0)
|
|
96
|
+
|
|
97
|
+
**`<Tabs>` wraps tabs that do not fit onto another line.** The strip used to scroll sideways with its scrollbar hidden, so on a computer the tabs past the edge were out of sight and a mouse wheel could not reach them. A dialog with eight tabs showed three. Now a strip too wide for its container flows onto more lines, and every tab stays visible. A label wider than the whole strip ends in an ellipsis inside it. A strip that fits is unchanged. The same strip draws the page Tabs container, on the web and in the exported app. No prop changed. A widget that wrapped `<Tabs>` in a horizontal `ScrollView` of its own should drop it.
|
|
90
98
|
|
|
91
99
|
### What's new in 0.262.0 (contract 1.212.0)
|
|
92
100
|
|
|
@@ -1015,7 +1023,7 @@ See [Bundled images and sounds](#bundled-images-and-sounds). Additive: a widget
|
|
|
1015
1023
|
|
|
1016
1024
|
**Files can be attached to a datastore record and stay private to it.** `useFilestoreUpload({ spaceType: "record", recordId })` attaches the uploaded file to that record, and `useFilestoreFiles({ spaceType: "record", recordId, type })` lists its attachments newest first (`type: "media"` for images and videos, `"document"` for the rest). Exactly the people who may read the record may read its attachments, through the same permission check as the record itself. Uploading needs write on the record, only the uploader may delete an attachment, and removing the record removes its attachments. A record attachment's URL is issued to one viewer and lives about ten minutes, so re-resolve it (`useFilestoreFile(id).refetch()`) when an image fails to load. The new `useFilestoreDelete()` returns `{ remove, removing, error }`: `remove(fileId)` deletes a record attachment permanently (only its uploader may) and moves any other file to the trash. `useFilestoreCopy().copy(fileId, { spaceType: "record", recordId })` attaches a copy of a file to a record, with the same write check as uploading there. `useCamera().pick({ multiple: true })` lets the user choose up to 10 photos from the device library at once and resolves an array of assets; `pick()` alone is unchanged. `<Avatar seed={user.id} />` draws the initials on that person's own colour, the same one the Studio's member lists use, and `avatarColorFor(seed)` returns it.
|
|
1017
1025
|
|
|
1018
|
-
**A `datastoreTemplate` can make inherited access read-only and stamp author columns on the server.** An `inheritAcl` RELATION column may add `inheritAclReadOnly: true`. Rows then inherit only READ access from the row they point at: members of a conversation read every message in it and may post into it, but change or delete only the messages they created (pair it with `grantCreatorPermissions`). This also holds down a chain, such as a reaction on a message in a conversation. A column may declare `defaultValue`, which is written when a create omits the column. Use `"@currentUser"` on a USER column, `"@now"` on a DATE or DATETIME column, or a literal of the column's own type. FILE, RELATION and USER_GROUP columns take no default, because ids belong to the installing workspace. `defaultLocked: true` on a `"@currentUser"` or `"@now"` default makes the server always stamp it. Any value a client sends for the column is ignored on create and on update, so a `Sender` or `CreatedBy` column is always the real author. `immutable: true` makes a column write-once: a create sets it from the value sent or the default, and any value sent for it on update is ignored, so a message's `Conversation` cannot be moved after it is posted. It is not valid on a MANY_TO_MANY relation, whose links live outside the row. Additive.
|
|
1026
|
+
**A `datastoreTemplate` can make inherited access read-only and stamp author columns on the server.** An `inheritAcl` RELATION column may add `inheritAclReadOnly: true`. Rows then inherit only READ access from the row they point at: members of a conversation read every message in it and may post into it, but change or delete only the messages they created (pair it with `grantCreatorPermissions`). This also holds down a chain, such as a reaction on a message in a conversation. A column may declare `defaultValue`, which is written when a create omits the column. Use `"@currentUser"` on a USER column, `"@now"` on a DATE or DATETIME column, `"@uuid"` on a UUID column, or a literal of the column's own type. FILE, RELATION and USER_GROUP columns take no default, because ids belong to the installing workspace. `defaultLocked: true` on a `"@currentUser"` or `"@now"` default makes the server always stamp it. Any value a client sends for the column is ignored on create and on update, so a `Sender` or `CreatedBy` column is always the real author. `immutable: true` makes a column write-once: a create sets it from the value sent or the default, and any value sent for it on update is ignored, so a message's `Conversation` cannot be moved after it is posted. It is not valid on a MANY_TO_MANY relation, whose links live outside the row. Additive.
|
|
1019
1027
|
|
|
1020
1028
|
**A user can mute one record's notifications.** `useRecordMute(tableId, recordId)` returns `{ muted, mutedUntil, loading, error, mute, unmute, refetch }`. `mute(until)` takes an ISO time or a `Date`, or `null` (or nothing) to mute until `unmute()`. While a mute is active, a server action's `notifications.notifyRecordSubjects` on that record sends the user nothing: no inbox row, no push, no email. `mutedUntil` is the ISO time the mute ends, or `null` when it lasts until unmuted, and `muted` turns false by itself when that time passes. The user must be able to read the record, so a record they cannot read fails with `NotificationError { code: "RECORD_NOT_FOUND" }`, just like one that does not exist. A falsy id returns a stable not-muted result with no request. On a host without `ctx.notifications.mutes` (the Builder canvas preview) the hook reads not muted, and `mute()` / `unmute()` reject with `NOTIFICATIONS_UNAVAILABLE`. No scope is needed, because it only changes the user's own setting. To show mutes in a list, call `useRecordMutes(tableId)` once: `isMuted(recordId)` answers for every row from one request, and a mute made through `useRecordMute` on the same table shows at once. `NotificationError.code` now carries the server's code when it sends one (`INVALID_TITLE`, `RECORD_NOT_FOUND`, …), and a 401 reads `AUTH_REQUIRED`. Additive.
|
|
1021
1029
|
|
package/dist/contract.cjs
CHANGED
|
@@ -3540,7 +3540,7 @@ const PRIMITIVES = [
|
|
|
3540
3540
|
{
|
|
3541
3541
|
name: "Tabs",
|
|
3542
3542
|
description:
|
|
3543
|
-
'Themed tab strip. `<Tabs items={[{ key: "open", label: "Open" }, { key: "done", label: "Done", icon: "check" }]} value={tab} onChange={setTab} />`. The default way to switch between views inside a widget: its look (a segmented control, pills, or an underline — Theme Settings > Tabs) and its colours and corner radius come from the theme, so every tab strip in the app matches, including the page Tabs container. It draws the strip only; render the selected panel yourself below it. Use it rather than tabs of your own from `Pressable` chips; when the request needs a strip it cannot draw (a live count badge on each tab, a vertical rail down the side), build your own in the app\'s look with the tab role and its selected state. Props: `items` (`[{ key, label, icon?, disabled?, testID? }]`, icon is a lucide name), `value` (the selected key), `onChange(key)`, `accessibilityLabel`, `style`, `testID`, and the overrides `variant` ("segmented" | "pill" | "underline"), `activeColor`, `activeTextColor`, `textColor`, `radius`.
|
|
3543
|
+
'Themed tab strip. `<Tabs items={[{ key: "open", label: "Open" }, { key: "done", label: "Done", icon: "check" }]} value={tab} onChange={setTab} />`. The default way to switch between views inside a widget: its look (a segmented control, pills, or an underline — Theme Settings > Tabs) and its colours and corner radius come from the theme, so every tab strip in the app matches, including the page Tabs container. It draws the strip only; render the selected panel yourself below it. Use it rather than tabs of your own from `Pressable` chips; when the request needs a strip it cannot draw (a live count badge on each tab, a vertical rail down the side), build your own in the app\'s look with the tab role and its selected state. Props: `items` (`[{ key, label, icon?, disabled?, testID? }]`, icon is a lucide name), `value` (the selected key), `onChange(key)`, `accessibilityLabel`, `style`, `testID`, and the overrides `variant` ("segmented" | "pill" | "underline"), `activeColor`, `activeTextColor`, `textColor`, `radius`. When the tabs do not fit the width, the strip wraps them onto another line, so every tab stays visible; never wrap it in a horizontal `ScrollView` of your own. Declare `tabsStyle` / `tabsActiveColor` / `tabsActiveTextColor` / `tabsTextColor` / `tabsRadius` style fields and pass them through to let an author override one widget.',
|
|
3544
3544
|
rnComponent: null,
|
|
3545
3545
|
docsUrl: null,
|
|
3546
3546
|
},
|
|
@@ -3959,7 +3959,7 @@ const MANIFEST_SCHEMA = {
|
|
|
3959
3959
|
type: "object",
|
|
3960
3960
|
required: false,
|
|
3961
3961
|
description:
|
|
3962
|
-
"Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 100 tables, 200 columns per table, 100 sample rows per table and 20000 sample cells across the whole template (each row counts the keys it sets, and at least one, summed over every table). RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for. sc-7530: a table may also declare `roleGrants` — group-scoped access named SYMBOLICALLY, because a group id belongs to the workspace that installs the widget and could never travel in the manifest. Declare the audiences once at template level as `roles: [{ key, label, description? }]` (key is lowercase kebab, max 32 roles), then per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`. The installing workspace binds each role to one of its own user groups (or has one created) during the install consent step, so a permission model where an admin group does everything and signed-in users only read survives the trip between workspaces. Use `publicGrant` for the two id-less audiences (everyone / any signed-in user) and `roleGrants` for anything that names a group. A role nobody binds simply writes no grant — it never fails the install. sc-7557: a column may declare `encrypted: true` — its values are stored AES-256-GCM encrypted at rest under a per-workspace subkey. The widget still reads and writes PLAINTEXT (the platform encrypts on write, decrypts for end users); studio users see `🔒` instead, and the column cannot be searched, filtered or sorted on. Sample `rows` you seed for it are encrypted the same way. Not valid on `RELATION` — its value is a foreign key the backend must resolve — and publishing one is rejected. An `inheritAcl` RELATION column may add `inheritAclReadOnly: true` — rows then inherit READ access only, so members of the parent read every child and may add children, but change or delete only the rows they created (pair with `grantCreatorPermissions`). A column may declare `defaultValue`, written when a create omits it: `@currentUser` on USER, `@now` on DATE / DATETIME, or a literal of the column's own type (none on FILE, RELATION or USER_GROUP — ids belong to the installing workspace). `defaultLocked: true` on an `@currentUser` / `@now` default makes the server always stamp it and ignore any client value on create and update, so an author column cannot be forged. `immutable: true` makes a column write-once: a create sets it as usual and any value sent for it on update is ignored (not valid on a MANY_TO_MANY relation). A table may declare `presence: true` so the people who share a row see which of each other are online (useOnlineMembers); it takes effect only while the workspace records user activity, and an upgrade that declares it turns it on for the existing table. A ONE_TO_ONE / ONE_TO_MANY RELATION may declare `latestVia: \"<relation column on its target that points back>\"` (onDelete left at SET_NULL; that relation must have inheritAcl and be immutable): the server keeps it at the newest row of the target table that points back at this row. A column may declare `copyOf: { via, column }`: the server keeps it equal to `column` on the row the latest-row column `via` points at (same type, or text into text, cut to 140 characters in a STRING column; a copy of an encrypted column must be encrypted, and a copy cannot inheritAcl). Both are written only by the server, in the same write as the related row; a client value is ignored. A table may declare `constraints`, checked on every write for every client and refused as CONSTRAINT_VIOLATION: `unique` ({ columns, partitionColumns?, unlessRelated?: { relation, column } } — waived for rows whose related row has that yes/no column set), `no-overlap` ({ startColumn, endColumn, partitionColumns? }), `related-open` ({ relation, closedColumn } — no row is added, changed or removed while the related row's column is filled) and `related-index` ({ column, relation, listColumn } — the number must be a position in the related row's list). An upgrade adds a declared constraint to an existing table without judging the rows already in it.",
|
|
3962
|
+
"Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 100 tables, 200 columns per table, 100 sample rows per table and 20000 sample cells across the whole template (each row counts the keys it sets, and at least one, summed over every table). RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for. sc-7530: a table may also declare `roleGrants` — group-scoped access named SYMBOLICALLY, because a group id belongs to the workspace that installs the widget and could never travel in the manifest. Declare the audiences once at template level as `roles: [{ key, label, description? }]` (key is lowercase kebab, max 32 roles), then per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`. The installing workspace binds each role to one of its own user groups (or has one created) during the install consent step, so a permission model where an admin group does everything and signed-in users only read survives the trip between workspaces. Use `publicGrant` for the two id-less audiences (everyone / any signed-in user) and `roleGrants` for anything that names a group. A role nobody binds simply writes no grant — it never fails the install. sc-7557: a column may declare `encrypted: true` — its values are stored AES-256-GCM encrypted at rest under a per-workspace subkey. The widget still reads and writes PLAINTEXT (the platform encrypts on write, decrypts for end users); studio users see `🔒` instead, and the column cannot be searched, filtered or sorted on. Sample `rows` you seed for it are encrypted the same way. Not valid on `RELATION` — its value is a foreign key the backend must resolve — and publishing one is rejected. An `inheritAcl` RELATION column may add `inheritAclReadOnly: true` — rows then inherit READ access only, so members of the parent read every child and may add children, but change or delete only the rows they created (pair with `grantCreatorPermissions`). A column may declare `defaultValue`, written when a create omits it: `@currentUser` on USER, `@now` on DATE / DATETIME, `@uuid` on UUID (a new random id per row), or a literal of the column's own type (none on FILE, RELATION or USER_GROUP — ids belong to the installing workspace). `defaultLocked: true` on an `@currentUser` / `@now` / `@uuid` default makes the server always stamp it and ignore any client value on create and update, so an author column cannot be forged. `immutable: true` makes a column write-once: a create sets it as usual and any value sent for it on update is ignored (not valid on a MANY_TO_MANY relation). A table may declare `presence: true` so the people who share a row see which of each other are online (useOnlineMembers); it takes effect only while the workspace records user activity, and an upgrade that declares it turns it on for the existing table. A ONE_TO_ONE / ONE_TO_MANY RELATION may declare `latestVia: \"<relation column on its target that points back>\"` (onDelete left at SET_NULL; that relation must have inheritAcl and be immutable): the server keeps it at the newest row of the target table that points back at this row. A column may declare `copyOf: { via, column }`: the server keeps it equal to `column` on the row the latest-row column `via` points at (same type, or text into text, cut to 140 characters in a STRING column; a copy of an encrypted column must be encrypted, and a copy cannot inheritAcl). Both are written only by the server, in the same write as the related row; a client value is ignored. A table may declare `constraints`, checked on every write for every client and refused as CONSTRAINT_VIOLATION: `unique` ({ columns, partitionColumns?, unlessRelated?: { relation, column } } — waived for rows whose related row has that yes/no column set), `no-overlap` ({ startColumn, endColumn, partitionColumns? }), `related-open` ({ relation, closedColumn } — no row is added, changed or removed while the related row's column is filled) and `related-index` ({ column, relation, listColumn } — the number must be a position in the related row's list). An upgrade adds a declared constraint to an existing table without judging the rows already in it.",
|
|
3963
3963
|
},
|
|
3964
3964
|
translations: {
|
|
3965
3965
|
type: "object",
|
|
@@ -6430,7 +6430,12 @@ const CONTRACT = deepFreeze({
|
|
|
6430
6430
|
// 1.212.0: additive (sc-13843) — `useAction(actionRef)` + the `actions` host
|
|
6431
6431
|
// client + the `actionRef` propertySchema type + the `actions.run:*`
|
|
6432
6432
|
// scope. A widget runs one of the workspace's Actions and reads its result.
|
|
6433
|
-
|
|
6433
|
+
// 1.213.0: behavioural (sc-13933) — `<Tabs>` wraps tabs that do not fit onto
|
|
6434
|
+
// another line instead of scrolling them out of sight.
|
|
6435
|
+
// 1.214.0: additive (sc-13746) — a datastoreTemplate column may be a
|
|
6436
|
+
// `UUID` (validated, stored lowercase); `"@uuid"` defaults it to a new
|
|
6437
|
+
// random id per row and may be `defaultLocked`.
|
|
6438
|
+
version: "1.214.0",
|
|
6434
6439
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6435
6440
|
hooks: HOOKS,
|
|
6436
6441
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -3537,7 +3537,7 @@ const PRIMITIVES = [
|
|
|
3537
3537
|
{
|
|
3538
3538
|
name: "Tabs",
|
|
3539
3539
|
description:
|
|
3540
|
-
'Themed tab strip. `<Tabs items={[{ key: "open", label: "Open" }, { key: "done", label: "Done", icon: "check" }]} value={tab} onChange={setTab} />`. The default way to switch between views inside a widget: its look (a segmented control, pills, or an underline — Theme Settings > Tabs) and its colours and corner radius come from the theme, so every tab strip in the app matches, including the page Tabs container. It draws the strip only; render the selected panel yourself below it. Use it rather than tabs of your own from `Pressable` chips; when the request needs a strip it cannot draw (a live count badge on each tab, a vertical rail down the side), build your own in the app\'s look with the tab role and its selected state. Props: `items` (`[{ key, label, icon?, disabled?, testID? }]`, icon is a lucide name), `value` (the selected key), `onChange(key)`, `accessibilityLabel`, `style`, `testID`, and the overrides `variant` ("segmented" | "pill" | "underline"), `activeColor`, `activeTextColor`, `textColor`, `radius`.
|
|
3540
|
+
'Themed tab strip. `<Tabs items={[{ key: "open", label: "Open" }, { key: "done", label: "Done", icon: "check" }]} value={tab} onChange={setTab} />`. The default way to switch between views inside a widget: its look (a segmented control, pills, or an underline — Theme Settings > Tabs) and its colours and corner radius come from the theme, so every tab strip in the app matches, including the page Tabs container. It draws the strip only; render the selected panel yourself below it. Use it rather than tabs of your own from `Pressable` chips; when the request needs a strip it cannot draw (a live count badge on each tab, a vertical rail down the side), build your own in the app\'s look with the tab role and its selected state. Props: `items` (`[{ key, label, icon?, disabled?, testID? }]`, icon is a lucide name), `value` (the selected key), `onChange(key)`, `accessibilityLabel`, `style`, `testID`, and the overrides `variant` ("segmented" | "pill" | "underline"), `activeColor`, `activeTextColor`, `textColor`, `radius`. When the tabs do not fit the width, the strip wraps them onto another line, so every tab stays visible; never wrap it in a horizontal `ScrollView` of your own. Declare `tabsStyle` / `tabsActiveColor` / `tabsActiveTextColor` / `tabsTextColor` / `tabsRadius` style fields and pass them through to let an author override one widget.',
|
|
3541
3541
|
rnComponent: null,
|
|
3542
3542
|
docsUrl: null,
|
|
3543
3543
|
},
|
|
@@ -3956,7 +3956,7 @@ const MANIFEST_SCHEMA = {
|
|
|
3956
3956
|
type: "object",
|
|
3957
3957
|
required: false,
|
|
3958
3958
|
description:
|
|
3959
|
-
"Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 100 tables, 200 columns per table, 100 sample rows per table and 20000 sample cells across the whole template (each row counts the keys it sets, and at least one, summed over every table). RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for. sc-7530: a table may also declare `roleGrants` — group-scoped access named SYMBOLICALLY, because a group id belongs to the workspace that installs the widget and could never travel in the manifest. Declare the audiences once at template level as `roles: [{ key, label, description? }]` (key is lowercase kebab, max 32 roles), then per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`. The installing workspace binds each role to one of its own user groups (or has one created) during the install consent step, so a permission model where an admin group does everything and signed-in users only read survives the trip between workspaces. Use `publicGrant` for the two id-less audiences (everyone / any signed-in user) and `roleGrants` for anything that names a group. A role nobody binds simply writes no grant — it never fails the install. sc-7557: a column may declare `encrypted: true` — its values are stored AES-256-GCM encrypted at rest under a per-workspace subkey. The widget still reads and writes PLAINTEXT (the platform encrypts on write, decrypts for end users); studio users see `🔒` instead, and the column cannot be searched, filtered or sorted on. Sample `rows` you seed for it are encrypted the same way. Not valid on `RELATION` — its value is a foreign key the backend must resolve — and publishing one is rejected. An `inheritAcl` RELATION column may add `inheritAclReadOnly: true` — rows then inherit READ access only, so members of the parent read every child and may add children, but change or delete only the rows they created (pair with `grantCreatorPermissions`). A column may declare `defaultValue`, written when a create omits it: `@currentUser` on USER, `@now` on DATE / DATETIME, or a literal of the column's own type (none on FILE, RELATION or USER_GROUP — ids belong to the installing workspace). `defaultLocked: true` on an `@currentUser` / `@now` default makes the server always stamp it and ignore any client value on create and update, so an author column cannot be forged. `immutable: true` makes a column write-once: a create sets it as usual and any value sent for it on update is ignored (not valid on a MANY_TO_MANY relation). A table may declare `presence: true` so the people who share a row see which of each other are online (useOnlineMembers); it takes effect only while the workspace records user activity, and an upgrade that declares it turns it on for the existing table. A ONE_TO_ONE / ONE_TO_MANY RELATION may declare `latestVia: \"<relation column on its target that points back>\"` (onDelete left at SET_NULL; that relation must have inheritAcl and be immutable): the server keeps it at the newest row of the target table that points back at this row. A column may declare `copyOf: { via, column }`: the server keeps it equal to `column` on the row the latest-row column `via` points at (same type, or text into text, cut to 140 characters in a STRING column; a copy of an encrypted column must be encrypted, and a copy cannot inheritAcl). Both are written only by the server, in the same write as the related row; a client value is ignored. A table may declare `constraints`, checked on every write for every client and refused as CONSTRAINT_VIOLATION: `unique` ({ columns, partitionColumns?, unlessRelated?: { relation, column } } — waived for rows whose related row has that yes/no column set), `no-overlap` ({ startColumn, endColumn, partitionColumns? }), `related-open` ({ relation, closedColumn } — no row is added, changed or removed while the related row's column is filled) and `related-index` ({ column, relation, listColumn } — the number must be a position in the related row's list). An upgrade adds a declared constraint to an existing table without judging the rows already in it.",
|
|
3959
|
+
"Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 100 tables, 200 columns per table, 100 sample rows per table and 20000 sample cells across the whole template (each row counts the keys it sets, and at least one, summed over every table). RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for. sc-7530: a table may also declare `roleGrants` — group-scoped access named SYMBOLICALLY, because a group id belongs to the workspace that installs the widget and could never travel in the manifest. Declare the audiences once at template level as `roles: [{ key, label, description? }]` (key is lowercase kebab, max 32 roles), then per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`. The installing workspace binds each role to one of its own user groups (or has one created) during the install consent step, so a permission model where an admin group does everything and signed-in users only read survives the trip between workspaces. Use `publicGrant` for the two id-less audiences (everyone / any signed-in user) and `roleGrants` for anything that names a group. A role nobody binds simply writes no grant — it never fails the install. sc-7557: a column may declare `encrypted: true` — its values are stored AES-256-GCM encrypted at rest under a per-workspace subkey. The widget still reads and writes PLAINTEXT (the platform encrypts on write, decrypts for end users); studio users see `🔒` instead, and the column cannot be searched, filtered or sorted on. Sample `rows` you seed for it are encrypted the same way. Not valid on `RELATION` — its value is a foreign key the backend must resolve — and publishing one is rejected. An `inheritAcl` RELATION column may add `inheritAclReadOnly: true` — rows then inherit READ access only, so members of the parent read every child and may add children, but change or delete only the rows they created (pair with `grantCreatorPermissions`). A column may declare `defaultValue`, written when a create omits it: `@currentUser` on USER, `@now` on DATE / DATETIME, `@uuid` on UUID (a new random id per row), or a literal of the column's own type (none on FILE, RELATION or USER_GROUP — ids belong to the installing workspace). `defaultLocked: true` on an `@currentUser` / `@now` / `@uuid` default makes the server always stamp it and ignore any client value on create and update, so an author column cannot be forged. `immutable: true` makes a column write-once: a create sets it as usual and any value sent for it on update is ignored (not valid on a MANY_TO_MANY relation). A table may declare `presence: true` so the people who share a row see which of each other are online (useOnlineMembers); it takes effect only while the workspace records user activity, and an upgrade that declares it turns it on for the existing table. A ONE_TO_ONE / ONE_TO_MANY RELATION may declare `latestVia: \"<relation column on its target that points back>\"` (onDelete left at SET_NULL; that relation must have inheritAcl and be immutable): the server keeps it at the newest row of the target table that points back at this row. A column may declare `copyOf: { via, column }`: the server keeps it equal to `column` on the row the latest-row column `via` points at (same type, or text into text, cut to 140 characters in a STRING column; a copy of an encrypted column must be encrypted, and a copy cannot inheritAcl). Both are written only by the server, in the same write as the related row; a client value is ignored. A table may declare `constraints`, checked on every write for every client and refused as CONSTRAINT_VIOLATION: `unique` ({ columns, partitionColumns?, unlessRelated?: { relation, column } } — waived for rows whose related row has that yes/no column set), `no-overlap` ({ startColumn, endColumn, partitionColumns? }), `related-open` ({ relation, closedColumn } — no row is added, changed or removed while the related row's column is filled) and `related-index` ({ column, relation, listColumn } — the number must be a position in the related row's list). An upgrade adds a declared constraint to an existing table without judging the rows already in it.",
|
|
3960
3960
|
},
|
|
3961
3961
|
translations: {
|
|
3962
3962
|
type: "object",
|
|
@@ -6427,7 +6427,12 @@ const CONTRACT = deepFreeze({
|
|
|
6427
6427
|
// 1.212.0: additive (sc-13843) — `useAction(actionRef)` + the `actions` host
|
|
6428
6428
|
// client + the `actionRef` propertySchema type + the `actions.run:*`
|
|
6429
6429
|
// scope. A widget runs one of the workspace's Actions and reads its result.
|
|
6430
|
-
|
|
6430
|
+
// 1.213.0: behavioural (sc-13933) — `<Tabs>` wraps tabs that do not fit onto
|
|
6431
|
+
// another line instead of scrolling them out of sight.
|
|
6432
|
+
// 1.214.0: additive (sc-13746) — a datastoreTemplate column may be a
|
|
6433
|
+
// `UUID` (validated, stored lowercase); `"@uuid"` defaults it to a new
|
|
6434
|
+
// random id per row and may be `defaultLocked`.
|
|
6435
|
+
version: "1.214.0",
|
|
6431
6436
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6432
6437
|
hooks: HOOKS,
|
|
6433
6438
|
primitives: PRIMITIVES,
|
package/dist/index.d.ts
CHANGED
|
@@ -185,12 +185,14 @@ export interface WidgetDatastoreTemplateColumn {
|
|
|
185
185
|
| "FLOAT"
|
|
186
186
|
| "BOOL"
|
|
187
187
|
| "DATE"
|
|
188
|
+
| "DATETIME"
|
|
188
189
|
| "FILE"
|
|
189
190
|
| "STRING_ARRAY"
|
|
190
191
|
| "INT_ARRAY"
|
|
191
192
|
| "RELATION"
|
|
192
193
|
| "USER"
|
|
193
|
-
| "USER_GROUP"
|
|
194
|
+
| "USER_GROUP"
|
|
195
|
+
| "UUID";
|
|
194
196
|
required?: boolean;
|
|
195
197
|
/** For RELATION columns only — points at a sibling table's `suffix`. */
|
|
196
198
|
targetSuffix?: string;
|
|
@@ -221,14 +223,14 @@ export interface WidgetDatastoreTemplateColumn {
|
|
|
221
223
|
encrypted?: boolean;
|
|
222
224
|
/**
|
|
223
225
|
* Written when a create omits the column. `"@currentUser"` (USER) stamps
|
|
224
|
-
* the signed-in user
|
|
225
|
-
* time. Other types take a literal of their own type. FILE, RELATION and
|
|
226
|
+
* the signed-in user, `"@now"` (DATE / DATETIME) the current date or
|
|
227
|
+
* time, and `"@uuid"` (UUID) a new random id per row. Other types take a literal of their own type. FILE, RELATION and
|
|
226
228
|
* USER_GROUP columns take none, because ids belong to the installing
|
|
227
229
|
* workspace.
|
|
228
230
|
*/
|
|
229
231
|
defaultValue?: unknown;
|
|
230
232
|
/**
|
|
231
|
-
* With a `"@currentUser"` or `"@
|
|
233
|
+
* With a `"@currentUser"`, `"@now"` or `"@uuid"` default only: the server always
|
|
232
234
|
* stamps it. Any value a client sends for the column is ignored on create
|
|
233
235
|
* and on update, so an author column cannot be forged.
|
|
234
236
|
*/
|
|
@@ -1633,12 +1635,14 @@ export interface DatastoreSchemaColumn {
|
|
|
1633
1635
|
| "FLOAT"
|
|
1634
1636
|
| "BOOL"
|
|
1635
1637
|
| "DATE"
|
|
1638
|
+
| "DATETIME"
|
|
1636
1639
|
| "FILE"
|
|
1637
1640
|
| "STRING_ARRAY"
|
|
1638
1641
|
| "INT_ARRAY"
|
|
1639
1642
|
| "RELATION"
|
|
1640
1643
|
| "USER"
|
|
1641
|
-
| "USER_GROUP"
|
|
1644
|
+
| "USER_GROUP"
|
|
1645
|
+
| "UUID";
|
|
1642
1646
|
required: boolean;
|
|
1643
1647
|
/** For RELATION columns only. */
|
|
1644
1648
|
relation_type?: "ONE_TO_ONE" | "ONE_TO_MANY" | "MANY_TO_MANY" | null;
|
package/dist/tabs-tokens.js
CHANGED
|
@@ -106,6 +106,8 @@ export const TAB_ITEM_LAYOUT = Object.freeze({
|
|
|
106
106
|
justifyContent: "center",
|
|
107
107
|
gap: 6,
|
|
108
108
|
minHeight: 44,
|
|
109
|
+
// A label longer than the whole strip ends in an ellipsis inside it.
|
|
110
|
+
maxWidth: "100%",
|
|
109
111
|
paddingHorizontal: 12,
|
|
110
112
|
paddingVertical: 8,
|
|
111
113
|
});
|
|
@@ -124,5 +126,6 @@ export function tabLabelStyle(t, theme, active) {
|
|
|
124
126
|
fontSize: typography.sizes.sm,
|
|
125
127
|
fontWeight: active ? "600" : "500",
|
|
126
128
|
color: active ? t.activeTextColor : t.textColor,
|
|
129
|
+
flexShrink: 1,
|
|
127
130
|
};
|
|
128
131
|
}
|
package/dist/tabs-view.js
CHANGED
|
@@ -33,10 +33,15 @@ function stripStyles(t, theme) {
|
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
if (t.style === "underline") {
|
|
36
|
-
//
|
|
36
|
+
// A wrapped line sits close under the one above, not a tab-gap away.
|
|
37
37
|
return {
|
|
38
38
|
bar: null,
|
|
39
|
-
content: {
|
|
39
|
+
content: {
|
|
40
|
+
columnGap: spacing.lg,
|
|
41
|
+
rowGap: spacing.xs,
|
|
42
|
+
borderBottomWidth: 1,
|
|
43
|
+
borderColor: t.rule,
|
|
44
|
+
},
|
|
40
45
|
item: (active) => ({
|
|
41
46
|
paddingHorizontal: 4,
|
|
42
47
|
borderBottomWidth: 2,
|
|
@@ -50,10 +55,10 @@ function stripStyles(t, theme) {
|
|
|
50
55
|
/**
|
|
51
56
|
* Bind the shared tab strip to one platform's primitives.
|
|
52
57
|
*
|
|
53
|
-
* @param {{View: any, Text: any, Pressable: any
|
|
58
|
+
* @param {{View: any, Text: any, Pressable: any}} rn
|
|
54
59
|
*/
|
|
55
60
|
export function makeTabs(rn) {
|
|
56
|
-
const { Text, Pressable
|
|
61
|
+
const { View, Text, Pressable } = rn;
|
|
57
62
|
|
|
58
63
|
function Tabs({
|
|
59
64
|
items = [],
|
|
@@ -84,13 +89,17 @@ export function makeTabs(rn) {
|
|
|
84
89
|
const strip = stripStyles(t, theme);
|
|
85
90
|
|
|
86
91
|
return React.createElement(
|
|
87
|
-
|
|
92
|
+
View,
|
|
88
93
|
{
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
// Tabs that do not fit wrap onto another line: a sideways scroller
|
|
95
|
+
// hid them from a mouse, with nothing to say they were there.
|
|
91
96
|
// A strip never grows into, or shrinks away from, the panel below.
|
|
92
|
-
style: [
|
|
93
|
-
|
|
97
|
+
style: [
|
|
98
|
+
{ flexGrow: 0, flexShrink: 0, maxWidth: "100%", flexDirection: "row", flexWrap: "wrap" },
|
|
99
|
+
strip.bar,
|
|
100
|
+
strip.content,
|
|
101
|
+
style,
|
|
102
|
+
],
|
|
94
103
|
accessibilityRole: "tablist",
|
|
95
104
|
accessibilityLabel,
|
|
96
105
|
testID,
|
package/dist/tabs.native.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// `<Tabs>` (native). Binds ./tabs-view.js to React Native's own primitives;
|
|
2
2
|
// only the import differs from ./tabs.js.
|
|
3
3
|
|
|
4
|
-
import { View, Pressable
|
|
4
|
+
import { View, Pressable } from "react-native";
|
|
5
5
|
import { Text } from "./bounded-text.native.js";
|
|
6
6
|
import { makeTabs } from "./tabs-view.js";
|
|
7
7
|
|
|
8
|
-
export const Tabs = makeTabs({ View, Text, Pressable
|
|
8
|
+
export const Tabs = makeTabs({ View, Text, Pressable });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.264.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",
|
package/src/contract.js
CHANGED
|
@@ -3537,7 +3537,7 @@ const PRIMITIVES = [
|
|
|
3537
3537
|
{
|
|
3538
3538
|
name: "Tabs",
|
|
3539
3539
|
description:
|
|
3540
|
-
'Themed tab strip. `<Tabs items={[{ key: "open", label: "Open" }, { key: "done", label: "Done", icon: "check" }]} value={tab} onChange={setTab} />`. The default way to switch between views inside a widget: its look (a segmented control, pills, or an underline — Theme Settings > Tabs) and its colours and corner radius come from the theme, so every tab strip in the app matches, including the page Tabs container. It draws the strip only; render the selected panel yourself below it. Use it rather than tabs of your own from `Pressable` chips; when the request needs a strip it cannot draw (a live count badge on each tab, a vertical rail down the side), build your own in the app\'s look with the tab role and its selected state. Props: `items` (`[{ key, label, icon?, disabled?, testID? }]`, icon is a lucide name), `value` (the selected key), `onChange(key)`, `accessibilityLabel`, `style`, `testID`, and the overrides `variant` ("segmented" | "pill" | "underline"), `activeColor`, `activeTextColor`, `textColor`, `radius`.
|
|
3540
|
+
'Themed tab strip. `<Tabs items={[{ key: "open", label: "Open" }, { key: "done", label: "Done", icon: "check" }]} value={tab} onChange={setTab} />`. The default way to switch between views inside a widget: its look (a segmented control, pills, or an underline — Theme Settings > Tabs) and its colours and corner radius come from the theme, so every tab strip in the app matches, including the page Tabs container. It draws the strip only; render the selected panel yourself below it. Use it rather than tabs of your own from `Pressable` chips; when the request needs a strip it cannot draw (a live count badge on each tab, a vertical rail down the side), build your own in the app\'s look with the tab role and its selected state. Props: `items` (`[{ key, label, icon?, disabled?, testID? }]`, icon is a lucide name), `value` (the selected key), `onChange(key)`, `accessibilityLabel`, `style`, `testID`, and the overrides `variant` ("segmented" | "pill" | "underline"), `activeColor`, `activeTextColor`, `textColor`, `radius`. When the tabs do not fit the width, the strip wraps them onto another line, so every tab stays visible; never wrap it in a horizontal `ScrollView` of your own. Declare `tabsStyle` / `tabsActiveColor` / `tabsActiveTextColor` / `tabsTextColor` / `tabsRadius` style fields and pass them through to let an author override one widget.',
|
|
3541
3541
|
rnComponent: null,
|
|
3542
3542
|
docsUrl: null,
|
|
3543
3543
|
},
|
|
@@ -3956,7 +3956,7 @@ const MANIFEST_SCHEMA = {
|
|
|
3956
3956
|
type: "object",
|
|
3957
3957
|
required: false,
|
|
3958
3958
|
description:
|
|
3959
|
-
"Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 100 tables, 200 columns per table, 100 sample rows per table and 20000 sample cells across the whole template (each row counts the keys it sets, and at least one, summed over every table). RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for. sc-7530: a table may also declare `roleGrants` — group-scoped access named SYMBOLICALLY, because a group id belongs to the workspace that installs the widget and could never travel in the manifest. Declare the audiences once at template level as `roles: [{ key, label, description? }]` (key is lowercase kebab, max 32 roles), then per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`. The installing workspace binds each role to one of its own user groups (or has one created) during the install consent step, so a permission model where an admin group does everything and signed-in users only read survives the trip between workspaces. Use `publicGrant` for the two id-less audiences (everyone / any signed-in user) and `roleGrants` for anything that names a group. A role nobody binds simply writes no grant — it never fails the install. sc-7557: a column may declare `encrypted: true` — its values are stored AES-256-GCM encrypted at rest under a per-workspace subkey. The widget still reads and writes PLAINTEXT (the platform encrypts on write, decrypts for end users); studio users see `🔒` instead, and the column cannot be searched, filtered or sorted on. Sample `rows` you seed for it are encrypted the same way. Not valid on `RELATION` — its value is a foreign key the backend must resolve — and publishing one is rejected. An `inheritAcl` RELATION column may add `inheritAclReadOnly: true` — rows then inherit READ access only, so members of the parent read every child and may add children, but change or delete only the rows they created (pair with `grantCreatorPermissions`). A column may declare `defaultValue`, written when a create omits it: `@currentUser` on USER, `@now` on DATE / DATETIME, or a literal of the column's own type (none on FILE, RELATION or USER_GROUP — ids belong to the installing workspace). `defaultLocked: true` on an `@currentUser` / `@now` default makes the server always stamp it and ignore any client value on create and update, so an author column cannot be forged. `immutable: true` makes a column write-once: a create sets it as usual and any value sent for it on update is ignored (not valid on a MANY_TO_MANY relation). A table may declare `presence: true` so the people who share a row see which of each other are online (useOnlineMembers); it takes effect only while the workspace records user activity, and an upgrade that declares it turns it on for the existing table. A ONE_TO_ONE / ONE_TO_MANY RELATION may declare `latestVia: \"<relation column on its target that points back>\"` (onDelete left at SET_NULL; that relation must have inheritAcl and be immutable): the server keeps it at the newest row of the target table that points back at this row. A column may declare `copyOf: { via, column }`: the server keeps it equal to `column` on the row the latest-row column `via` points at (same type, or text into text, cut to 140 characters in a STRING column; a copy of an encrypted column must be encrypted, and a copy cannot inheritAcl). Both are written only by the server, in the same write as the related row; a client value is ignored. A table may declare `constraints`, checked on every write for every client and refused as CONSTRAINT_VIOLATION: `unique` ({ columns, partitionColumns?, unlessRelated?: { relation, column } } — waived for rows whose related row has that yes/no column set), `no-overlap` ({ startColumn, endColumn, partitionColumns? }), `related-open` ({ relation, closedColumn } — no row is added, changed or removed while the related row's column is filled) and `related-index` ({ column, relation, listColumn } — the number must be a position in the related row's list). An upgrade adds a declared constraint to an existing table without judging the rows already in it.",
|
|
3959
|
+
"Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 100 tables, 200 columns per table, 100 sample rows per table and 20000 sample cells across the whole template (each row counts the keys it sets, and at least one, summed over every table). RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for. sc-7530: a table may also declare `roleGrants` — group-scoped access named SYMBOLICALLY, because a group id belongs to the workspace that installs the widget and could never travel in the manifest. Declare the audiences once at template level as `roles: [{ key, label, description? }]` (key is lowercase kebab, max 32 roles), then per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`. The installing workspace binds each role to one of its own user groups (or has one created) during the install consent step, so a permission model where an admin group does everything and signed-in users only read survives the trip between workspaces. Use `publicGrant` for the two id-less audiences (everyone / any signed-in user) and `roleGrants` for anything that names a group. A role nobody binds simply writes no grant — it never fails the install. sc-7557: a column may declare `encrypted: true` — its values are stored AES-256-GCM encrypted at rest under a per-workspace subkey. The widget still reads and writes PLAINTEXT (the platform encrypts on write, decrypts for end users); studio users see `🔒` instead, and the column cannot be searched, filtered or sorted on. Sample `rows` you seed for it are encrypted the same way. Not valid on `RELATION` — its value is a foreign key the backend must resolve — and publishing one is rejected. An `inheritAcl` RELATION column may add `inheritAclReadOnly: true` — rows then inherit READ access only, so members of the parent read every child and may add children, but change or delete only the rows they created (pair with `grantCreatorPermissions`). A column may declare `defaultValue`, written when a create omits it: `@currentUser` on USER, `@now` on DATE / DATETIME, `@uuid` on UUID (a new random id per row), or a literal of the column's own type (none on FILE, RELATION or USER_GROUP — ids belong to the installing workspace). `defaultLocked: true` on an `@currentUser` / `@now` / `@uuid` default makes the server always stamp it and ignore any client value on create and update, so an author column cannot be forged. `immutable: true` makes a column write-once: a create sets it as usual and any value sent for it on update is ignored (not valid on a MANY_TO_MANY relation). A table may declare `presence: true` so the people who share a row see which of each other are online (useOnlineMembers); it takes effect only while the workspace records user activity, and an upgrade that declares it turns it on for the existing table. A ONE_TO_ONE / ONE_TO_MANY RELATION may declare `latestVia: \"<relation column on its target that points back>\"` (onDelete left at SET_NULL; that relation must have inheritAcl and be immutable): the server keeps it at the newest row of the target table that points back at this row. A column may declare `copyOf: { via, column }`: the server keeps it equal to `column` on the row the latest-row column `via` points at (same type, or text into text, cut to 140 characters in a STRING column; a copy of an encrypted column must be encrypted, and a copy cannot inheritAcl). Both are written only by the server, in the same write as the related row; a client value is ignored. A table may declare `constraints`, checked on every write for every client and refused as CONSTRAINT_VIOLATION: `unique` ({ columns, partitionColumns?, unlessRelated?: { relation, column } } — waived for rows whose related row has that yes/no column set), `no-overlap` ({ startColumn, endColumn, partitionColumns? }), `related-open` ({ relation, closedColumn } — no row is added, changed or removed while the related row's column is filled) and `related-index` ({ column, relation, listColumn } — the number must be a position in the related row's list). An upgrade adds a declared constraint to an existing table without judging the rows already in it.",
|
|
3960
3960
|
},
|
|
3961
3961
|
translations: {
|
|
3962
3962
|
type: "object",
|
|
@@ -6427,7 +6427,12 @@ const CONTRACT = deepFreeze({
|
|
|
6427
6427
|
// 1.212.0: additive (sc-13843) — `useAction(actionRef)` + the `actions` host
|
|
6428
6428
|
// client + the `actionRef` propertySchema type + the `actions.run:*`
|
|
6429
6429
|
// scope. A widget runs one of the workspace's Actions and reads its result.
|
|
6430
|
-
|
|
6430
|
+
// 1.213.0: behavioural (sc-13933) — `<Tabs>` wraps tabs that do not fit onto
|
|
6431
|
+
// another line instead of scrolling them out of sight.
|
|
6432
|
+
// 1.214.0: additive (sc-13746) — a datastoreTemplate column may be a
|
|
6433
|
+
// `UUID` (validated, stored lowercase); `"@uuid"` defaults it to a new
|
|
6434
|
+
// random id per row and may be `defaultLocked`.
|
|
6435
|
+
version: "1.214.0",
|
|
6431
6436
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6432
6437
|
hooks: HOOKS,
|
|
6433
6438
|
primitives: PRIMITIVES,
|