@colixsystems/widget-sdk 0.243.0 → 0.245.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
@@ -24,7 +24,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
24
24
  | **CORE** | `useUser()` | `{ id, email, displayName, photoUrl, roles, groupIds }` | `ctx.user` (host-built context, **camelCase** — not a wire payload; `id` null when anonymous) — no scope. `photoUrl` is the user's own profile photo as an absolute URL, or `null`. |
25
25
  | **CORE** | `useUserPhoto()` | `{ url, canEdit, upload, remove, uploading, error }` | `ctx.user` + the optional `ctx.account` — no scope. Changes the signed-in user's OWN profile photo. `canEdit` is false when nobody is signed in or the host cannot change it; `upload` / `remove` then reject. |
26
26
  | **CORE** | `useAnonymousAccount()` | `{ supported, canCreate, creating, error, create }` | The optional `ctx.account.anonymous` — no scope. Offers an account with a generated login code and password and no email. `create()` opens the app's own dialog, which confirms, creates the account, shows the pair once and signs the visitor in; the widget never receives the pair and `create()` resolves `{ created: true }`. `supported` is false where the workspace has not turned anonymous accounts on, on the Builder canvas, in a preview and for a badge counter; `canCreate` is false while someone is signed in. |
27
- | **CORE** | `useNavigation()` | `{ goTo, goToStartPage, scrollToSection, goBack, push, replace, back, currentRoute, openLink }` | `ctx.navigation` — no scope. `goTo(pageIdOrSlug, params?)` accepts a page UUID (a `pageRef` prop) **or its slug** (a row's page-key field) — both hosts resolve either; `goToStartPage()` opens the signed-in user's own start page; `scrollToSection(id)` scrolls this page to a section picked with a `sectionRef` prop (`openLink` for a link of unknown shape; a known external URL can also use the `Linking` primitive) |
27
+ | **CORE** | `useNavigation()` | `{ goTo, goToStartPage, scrollToSection, scrollIntoView, goBack, push, replace, back, currentRoute, openLink }` | `ctx.navigation` — no scope. `goTo(pageIdOrSlug, params?)` accepts a page UUID (a `pageRef` prop) **or its slug** (a row's page-key field) — both hosts resolve either; `goToStartPage()` opens the signed-in user's own start page; `scrollToSection(id)` scrolls this page to a section picked with a `sectionRef` prop; `scrollIntoView(ref)` scrolls it to an element inside your widget (a heading your table of contents jumps to) (`openLink` for a link of unknown shape; a known external URL can also use the `Linking` primitive) |
28
28
  | **CORE** | `useFilePreview(file)` | `{ uri }` | No scope. A temporary address an `<Image>` can show for a file the person picked but has not uploaded yet (a photo in a composer). Native picks use their own uri; on the web the SDK creates and releases an object URL, which widgets may not do themselves. `uri` is null for no file. |
29
29
  | **CORE** | `usePageBadge(count)` | `{ supported }` | `ctx.navigation.setBadge` — no scope. Puts `count` on the menu entry or tab of the page the widget is on (a chat's unread messages). The host sums the page's widgets and keeps the last count after the widget leaves the screen, until it reports again; `0` clears this widget's share. `supported` is false where the host draws no page badges (the Builder canvas). |
30
30
  | **CORE** | `useRouteParams()` | `{ [paramKey]: value }` | `ctx.navigation.currentRoute.params` — no scope. The nav params the previous page passed via `goTo(pageId, params)`; the flat accessor for master→detail (read `recordId` on a detail page). Empty object when none. |
@@ -55,7 +55,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
55
55
  | **DATASTORE** | `useDatastoreSchema(tableId)` | `{ schema, loading, error, refetch }` | `schema(tableId)` — `datastore.read:<table>` |
56
56
  | **DATASTORE** | `useBoundColumns(tableId, shape, props)` | `{ columns, resolved, missing, loading, error }` | `schema(tableId)` (built on `useDatastoreSchema`) — `datastore.read:<table>`. Resolves author-bound column NAMES from `props` by exact name → case-insensitive name → first unclaimed column matching `shape[key].dataType`, so a column an author renamed after install still resolves instead of `record[props.titleField]` reading `undefined`. `columns` holds the resolved NAME (`record[columns.titleField]`); `resolved` holds the full `Column`; `missing` lists non-`optional` keys that never resolved. Falsy `tableId` collapses to `{ columns: {}, resolved: {}, missing: Object.keys(shape), loading: false, error: null }`. |
57
57
  | **DATASTORE** | `useInterpretDraft(tableId)` | `{ interpret, interpreting, error, result, available }` | `interpret(tableId, body)` — `datastore.read:<table>`. Turns ONE sentence a user typed ("walk at 11 am tomorrow") into DRAFT column values so a form can prefill itself. IMPERATIVE: call `interpret(text, { fields?, timeZone? })` from an event handler, never on mount. It DRAFTS and writes nothing — show the values for review, then submit through `useDatastoreMutation().create`. Resolves to `{ values, unresolved }`; `values` is keyed by column NAME (the shape `create()` takes) and `unresolved` names the fields the sentence did not state. Only text / number / boolean / date / datetime / array columns are drafted — `FILE`, `RELATION`, `USER` and `USER_GROUP` carry ids and are never guessed. Fails closed to an empty draft. **Every call spends the workspace's AI credits** and is rate-limited per actor, so call it once per user action (never on mount or in a render loop); once the workspace runs out the call is refused with a generic 429 — an app user is deliberately **not** told the workspace's billing state, since they have never heard of an AI credit and cannot buy one. Never surface a raw error to the person filling the form: say drafting is unavailable and keep every field editable by hand. `available` is false where the host brokers no interpreter. |
58
- | **AGENTS** | `useAgent(agentRef)` | `{ send, cancel, reset, messages, sending, resuming, error, available }` | `ctx.agents` — `ai.invoke:*`. Hold a conversation with one of the WORKSPACE's own AI agents (an author-written name + system prompt). Sending is IMPERATIVE: call `send(text)` from an event handler, never on mount — the first send opens the conversation, so a widget nobody types into costs nothing. On mount the hook DOES reopen the caller's most recent thread with this agent, so a refresh or a navigation keeps the conversation instead of starting a blank one; that resume only READS and spends no credit. `resuming` is true while it runs — render a loading state, not an empty thread. `messages` is the running transcript, oldest first. You **cannot** set or read the system prompt, replay an edited transcript, or pick a model: the server assembles every turn from the stored prompt plus the stored history, and `content` is the only thing that reaches the wire. `reset()` drops the thread (a "new chat" affordance), and the new thread is the one a later mount resumes. **Every send spends the workspace's AI credits** and is rate-limited per caller AND per workspace. Rejects with `AgentError`; branch on `.code` — `AI_QUOTA_EXCEEDED` (429) is the workspace's credit ceiling and will NOT clear on a retry, unlike `RATE_LIMITED`, so never offer "try again" for it, and `AGENT_DISABLED` (409) means the author turned it off, so hide the affordance. Never surface a raw error or the workspace's billing state to an app user. `available` is false when no agent is bound or the host brokers none — render a visible unbound state, never a blank box. An assistant message that used a tool also carries **`activity`** — what the agent DID with the user's data: the table it read, or the table and columns it CHANGED (never the values, and never a row the permissions refused). **Render a write.** A reply saying "done!" is not evidence a row was written, and if the agent got it wrong the user has no other way to notice. A talk-only turn omits the key entirely. |
58
+ | **AGENTS** | `useAgent(agentRef)` | `{ send, cancel, reset, messages, sending, resuming, error, available }` | `ctx.agents` — `ai.invoke:*`. Hold a conversation with one of the WORKSPACE's own AI agents (an author-written name + system prompt). Sending is IMPERATIVE: call `send(text)` from an event handler, never on mount — the first send opens the conversation, so a widget nobody types into costs nothing. On mount the hook DOES reopen the caller's most recent thread with this agent, so a refresh or a navigation keeps the conversation instead of starting a blank one; that resume only READS and spends no credit. `resuming` is true while it runs — render a loading state, not an empty thread. `messages` is the running transcript, oldest first. You **cannot** set or read the system prompt, replay an edited transcript, or pick a model: the server assembles every turn from the stored prompt plus the stored history, and `content` is the only thing that reaches the wire. `reset()` drops the thread (a "new chat" affordance), and the new thread is the one a later mount resumes. **Every send spends the workspace's AI credits** and is rate-limited per caller AND per workspace. Rejects with `AgentError`; branch on `.code` — `AI_QUOTA_EXCEEDED` (429) is the workspace's credit ceiling and will NOT clear on a retry, unlike `RATE_LIMITED`, so never offer "try again" for it, and `AGENT_DISABLED` (409) means the author turned it off, so hide the affordance. Never surface a raw error or the workspace's billing state to an app user. `available` is false when no agent is bound or the host brokers none — render a visible unbound state, never a blank box. An assistant message that used a tool also carries **`activity`** — what the agent DID with the user's data: the table it read (with the `search` text and `filters` it read with), or the table and columns it CHANGED (never the values, and never a row the permissions refused). **Render a write.** A reply saying "done!" is not evidence a row was written, and if the agent got it wrong the user has no other way to notice. A talk-only turn omits the key entirely. |
59
59
  | **DATASTORE** | `useDatastoreMutation(table)` | `{ create, update, delete }` | `records(table).{ create, update (PATCH), delete }` — `datastore.write:*`. **To CLEAR a cell, pass it empty** — `update(id, { when: null })` (also `""` or `[]`) empties that cell and it reads back as `null` (`[]` on an array column). There is no separate clear call, so never write a sentinel such as `0` or a placeholder date to mean "no value". `false` and `0` are values, not clears, and a REQUIRED column cannot be cleared. |
60
60
  | **DATASTORE** | `useDatastoreSubscription(table, handlers, options)` (optional: options) | `{ status }` — `"connecting" \| "live" \| "reconnecting" \| "fallback"` | `records(table).subscribe` — `datastore.read:<table>`. Live `onCreated` / `onUpdated` / `onDeleted` off the REQ-RT-07 socket; never throws, resolving to `{ status: "fallback" }` so the widget polls instead. A whole-table subscribe is gated on read-EVERY-row, because one envelope reaches every subscriber of the table — so for a table governed by per-record grants pass `options.scope`: `{ kind: "record", record_id }` for one row, or `{ kind: "parent", relation_column, record_id }` for the rows whose RELATION column points at that parent (the column must carry `inheritAcl`, else the subscribe reports `"fallback"`). For a live list of the signed-in user's own rows pass `{ kind: "granted" }`: every row they read through a grant to them or one of their groups, plus `onAccessGained(record)` / `onAccessLost({ id })` as rows enter and leave that set. Re-subscribes on the scope's VALUES, so a fresh object literal each render is fine. |
61
61
  | **DATASTORE** | `useTyping(tableId, recordId)` | `{ typingUserIds, setTyping, status }` — status `"connecting" \| "live" \| "unavailable"` | `records(table).typing` — `datastore.read:<table>`. Who else is typing in one record, over the same socket as `useDatastoreSubscription`. Call `setTyping(true)` on every keystroke (throttled to one signal per 3 s; `false` is sent after 5 s idle) and `setTyping(false)` on send, clear or blur. `typingUserIds` lists the OTHER users' ids; each drops out 6 s after their last signal. Needs a signed-in user who can read the record. Never throws: an anonymous visitor, a refused subscription or a host whose client lacks `typing` reads `{ typingUserIds: [], status: "unavailable" }`. Re-subscribes when either id changes. |
@@ -84,7 +84,42 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
84
84
 
85
85
  ## Status
86
86
 
87
- `v0.243.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**.
87
+ `v0.245.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**.
88
+
89
+ ### What's new in 0.245.0 (contract 1.199.0)
90
+
91
+ **An agent's activity says what it searched for.** A read entry in an assistant `AgentMessage`'s `activity` may now carry `search` (the text the agent searched every text column for) and `filters` (`{ column, operator, value }` conditions it read with). These are the agent's own arguments, never the rows that came back. When an agent misses an answer, this shows whether it searched for the wrong words or in the wrong column. Additive: a read with neither omits both keys, so existing renderers are unchanged.
92
+
93
+ ### What's new in 0.244.0 (contract 1.198.0)
94
+
95
+ **A widget can scroll its page to an element inside itself — `useNavigation().scrollIntoView(ref)`.** A long article, FAQ or handbook widget can show a table of contents whose entries jump to its own headings. The page itself scrolls, so the widget keeps its full height and never needs a scroll box of its own. Attach a ref to the element and pass the ref:
96
+
97
+ ```jsx
98
+ import { useRef } from "react";
99
+ import { useNavigation, Pressable, Text, View } from "@colixsystems/widget-sdk";
100
+
101
+ export default function Handbook({ sections }) {
102
+ const { scrollIntoView } = useNavigation();
103
+ const headings = useRef({});
104
+ return (
105
+ <View>
106
+ {sections.map((s) => (
107
+ <Pressable key={s.id} onPress={() => scrollIntoView({ current: headings.current[s.id] })}>
108
+ <Text>{s.title}</Text>
109
+ </Pressable>
110
+ ))}
111
+ {sections.map((s) => (
112
+ <View key={s.id} ref={(el) => { headings.current[s.id] = el; }}>
113
+ <Text>{s.title}</Text>
114
+ <Text>{s.body}</Text>
115
+ </View>
116
+ ))}
117
+ </View>
118
+ );
119
+ }
120
+ ```
121
+
122
+ Both hosts use the same scroll as `scrollToSection`: smooth (instant under reduced motion), stopping with the element just below a pinned site header, on a normal page and on a Fit to screen page. A ref that is not mounted does nothing, and so does the Studio canvas. To jump to another part of the PAGE, keep using `scrollToSection` with a `sectionRef` prop. Additive.
88
123
 
89
124
  ### What's new in 0.243.0 (contract 1.197.0)
90
125
 
@@ -2985,7 +3020,7 @@ import { defineWidget, validateManifest, useDatastoreQuery, Text, View } from "@
2985
3020
 
2986
3021
  - `defineWidget({ manifest, component })` — validates the manifest and produces a widget module the host can register.
2987
3022
  - `validateManifest(m)` / `validatePropertySchema(s)` / `validateProps(schema, props)` — shape validation; no third-party deps.
2988
- - `useDatastoreQuery`, `useDatastoreRecord`, `useDatastoreSchema`, `useDatastoreMutation`, `useDirectory`, `useUsers`, `useGroups`, `useRecordPermissions`, `useAsset`, `useWidgetEvent`, `useWidgetInput`, `usePayments`, `useSendNotification`, `useTheme`, `useI18n`, `useUser`, `useNavigation`, `useRouteParams`, `usePageContext`, `useWidgetRoute`, `useChildRenderer`, `useClipboard`, `useToast` — hooks that read from the host-provided `WidgetContext` (or, for `useClipboard`, the platform clipboard API directly). `useDirectory(query?)` returns `{ users, loading, error, refetch }` (each user `{ id, name, role }`) and requires the `directory.read:users` scope. `useUsers(query?)` returns `{ users, loading, error, refetch, invite, deactivate, reactivate, remove }` and requires `users.read:*` (mutations also need `users.write:*`); rejections are a `DirectoryError`. `useGroups(query?)` returns `{ groups, loading, error, refetch, create, remove, addMember, removeMember }` and requires `groups.read:*` (mutations also need `groups.write:*`). `usePayments()` returns `{ requestPayment, getPayment }` and requires the `payments.charge:appUser` scope; `requestPayment(...)` rejects with a `PaymentError` carrying `code`, the server's user-safe `message`, and `retryable` (`false` = this charge cannot succeed until the workspace, manifest, or amount changes — show the message, not a retry). `useSendNotification()` returns `{ send, sending, error }` and requires the `notifications.send:appUser` scope; `send({ recipient_user_id, title, body, link?, payload? })` notifies one app user in the same workspace (cross-workspace `recipient_user_id` is rejected), must be called from an event handler rather than render, and rejects with a `NotificationError`. `useUser()` returns the active end-user identity `{ id, email, displayName, roles, groupIds }` (camelCase — the host-built context object, not a wire payload; `id` is `null` for anonymous / preview). `useNavigation()` returns `{ goTo, goToStartPage, scrollToSection, goBack, push, replace, back, currentRoute, openLink }` for internal page navigation; `goToStartPage()` opens the signed-in user's own start page (the page the app opens on for them); `scrollToSection(sectionId)` scrolls the current page to one of its sections (bind the id with a `sectionRef` prop); `openLink(link)` follows an author- or data-supplied link of unknown shape through the host's shared resolver (in-app page → internal route, off-app http(s) → opened outside, unsafe → refused) and is the safe choice for any value your code did not construct, while `Linking.openURL(url)` is for an external URL you built yourself. `useRouteParams()` returns the current route's params object (`currentRoute.params`) — the flat master→detail accessor; read a param off it (e.g. `recordId`), never call it. `useDatastoreRecord(tableId, recordId)` returns `{ data, loading, error, refetch }` for a single record (data is one row or null). `useDatastoreSchema(tableId)` returns `{ schema, loading, error, refetch }` where `schema` is `{ id, name, columns: [{ id, name, data_type, required, relation_type, target_table_id, is_identification }] }` (structure only, no row data; snake_case verbatim) — use it to resolve a stored `columnId` to its column type at runtime; requires the `datastore.read:<table>` scope. `useAsset(fileId)` returns `{ url, file, loading, error, refetch }` — the `url` is an absolute URL composed against the host's API base. `useChildRenderer()` returns `{ renderNode(node) }` — container widgets call it to render arbitrary child page-tree nodes (prefer the `WidgetTree` component for the common case). `useWidgetInput(inputName)` returns the latest payload a sibling widget published on the event the page author wired to this widget's declared `inputs` entry (`undefined` when unwired or not yet published).
3023
+ - `useDatastoreQuery`, `useDatastoreRecord`, `useDatastoreSchema`, `useDatastoreMutation`, `useDirectory`, `useUsers`, `useGroups`, `useRecordPermissions`, `useAsset`, `useWidgetEvent`, `useWidgetInput`, `usePayments`, `useSendNotification`, `useTheme`, `useI18n`, `useUser`, `useNavigation`, `useRouteParams`, `usePageContext`, `useWidgetRoute`, `useChildRenderer`, `useClipboard`, `useToast` — hooks that read from the host-provided `WidgetContext` (or, for `useClipboard`, the platform clipboard API directly). `useDirectory(query?)` returns `{ users, loading, error, refetch }` (each user `{ id, name, role }`) and requires the `directory.read:users` scope. `useUsers(query?)` returns `{ users, loading, error, refetch, invite, deactivate, reactivate, remove }` and requires `users.read:*` (mutations also need `users.write:*`); rejections are a `DirectoryError`. `useGroups(query?)` returns `{ groups, loading, error, refetch, create, remove, addMember, removeMember }` and requires `groups.read:*` (mutations also need `groups.write:*`). `usePayments()` returns `{ requestPayment, getPayment }` and requires the `payments.charge:appUser` scope; `requestPayment(...)` rejects with a `PaymentError` carrying `code`, the server's user-safe `message`, and `retryable` (`false` = this charge cannot succeed until the workspace, manifest, or amount changes — show the message, not a retry). `useSendNotification()` returns `{ send, sending, error }` and requires the `notifications.send:appUser` scope; `send({ recipient_user_id, title, body, link?, payload? })` notifies one app user in the same workspace (cross-workspace `recipient_user_id` is rejected), must be called from an event handler rather than render, and rejects with a `NotificationError`. `useUser()` returns the active end-user identity `{ id, email, displayName, roles, groupIds }` (camelCase — the host-built context object, not a wire payload; `id` is `null` for anonymous / preview). `useNavigation()` returns `{ goTo, goToStartPage, scrollToSection, scrollIntoView, goBack, push, replace, back, currentRoute, openLink }` for internal page navigation; `goToStartPage()` opens the signed-in user's own start page (the page the app opens on for them); `scrollToSection(sectionId)` scrolls the current page to one of its sections (bind the id with a `sectionRef` prop); `scrollIntoView(ref)` scrolls it to an element inside the calling widget; `openLink(link)` follows an author- or data-supplied link of unknown shape through the host's shared resolver (in-app page → internal route, off-app http(s) → opened outside, unsafe → refused) and is the safe choice for any value your code did not construct, while `Linking.openURL(url)` is for an external URL you built yourself. `useRouteParams()` returns the current route's params object (`currentRoute.params`) — the flat master→detail accessor; read a param off it (e.g. `recordId`), never call it. `useDatastoreRecord(tableId, recordId)` returns `{ data, loading, error, refetch }` for a single record (data is one row or null). `useDatastoreSchema(tableId)` returns `{ schema, loading, error, refetch }` where `schema` is `{ id, name, columns: [{ id, name, data_type, required, relation_type, target_table_id, is_identification }] }` (structure only, no row data; snake_case verbatim) — use it to resolve a stored `columnId` to its column type at runtime; requires the `datastore.read:<table>` scope. `useAsset(fileId)` returns `{ url, file, loading, error, refetch }` — the `url` is an absolute URL composed against the host's API base. `useChildRenderer()` returns `{ renderNode(node) }` — container widgets call it to render arbitrary child page-tree nodes (prefer the `WidgetTree` component for the common case). `useWidgetInput(inputName)` returns the latest payload a sibling widget published on the event the page author wired to this widget's declared `inputs` entry (`undefined` when unwired or not yet published).
2989
3024
  - `WidgetTree({ node })` — component that renders an author-authored child node through the host's renderer; used by Tabs / Card / custom containers to host arbitrary child widgets.
2990
3025
  - `Text`, `View`, `Pressable`, `Image`, `ScrollView`, `TextInput`, `FlatList`, `SectionList`, `ActivityIndicator`, `Switch`, `StyleSheet`, `Linking`, `Icon`, `DateTimePicker` — re-exported from `react-native` (the RN primitives) or implemented in the SDK (`Icon` wraps `lucide-react-native`; `DateTimePicker` wraps `@react-native-community/datetimepicker` on native and renders `<input type="date|time|datetime-local">` directly on web because the RN library has no react-native-web mapping). The web build aliases `react-native` to `react-native-web` so the RN-re-exported primitives render in the browser without any per-platform code; the exported Expo app's Metro bundler resolves the real `react-native` library. `Linking` is a static API (`Linking.openURL(url)`) — use it for external URLs, and use `useNavigation().goTo(pageId)` for internal page navigation. See https://reactnative.dev/docs/ for per-component props.
2991
3026
  - `Overlay` — the screen-level overlay (sc-6607). `<Overlay visible={!!preview} onRequestClose={() => setPreview(null)} size="full">…</Overlay>` renders its children OUTSIDE the widget's layout box on both hosts (web portals it to the document root, native uses the OS modal), so no clipping card, `ScrollView` or neighbouring widget can cut off a document/media preview, lightbox or confirm dialog — which absolute positioning inside the widget cannot achieve on either platform. Children stay in your own React tree, so state and hooks work normally. `onRequestClose` carries the backdrop press, Escape on web, and the Android back button; `size` is `sm` | `md` (default) | `lg` | `full`; the scrim, surface, radius, padding and elevation come from the workspace theme. An anchored dropdown still belongs inside your own root.
package/dist/contract.cjs CHANGED
@@ -1205,6 +1205,8 @@ const HOOKS = [
1205
1205
  "() => void — opens the signed-in user's own start page, the page the app opens on for them (it can differ per user, since page access is restricted per group). Use it for a 'back to start' or 'home' button instead of hard-coding a page. A no-op where there is no live router (the Studio canvas, a badge counter).",
1206
1206
  scrollToSection:
1207
1207
  "(sectionId: string) => void — scrolls THIS page to one of its sections (a root section or a layout container), landing its top just below any pinned site header; smooth unless the visitor prefers reduced motion. Take the id from a `sectionRef` prop the author picks; never hard-code a node id. An unknown, deleted or hidden section is a no-op, and so is the Studio canvas.",
1208
+ scrollIntoView:
1209
+ "(ref: { current }) => void — scrolls THIS page so an element inside the calling widget (the ref you attached to a View, Text or other primitive) lands just below any pinned site header; smooth unless the visitor prefers reduced motion. Use it for anchors within the widget's own content, such as a table of contents that jumps to one of its headings. Never fake page anchors with a height-capped inner ScrollView: the page itself scrolls. A ref that is not mounted is a no-op, and so is the Studio canvas.",
1208
1210
  goBack: "() => void",
1209
1211
  push: "(pageIdOrSlug: string, params?: object) => void",
1210
1212
  replace: "(pageIdOrSlug: string, params?: object) => void",
@@ -2062,8 +2064,8 @@ const HOOKS = [
2062
2064
  "`messages` is the running transcript, oldest first, each " +
2063
2065
  "{ id, role, content, created_at } with role 'user' | 'assistant'. " +
2064
2066
  "An assistant message that used a tool also carries `activity`: what " +
2065
- "the agent DID — the table it read, or the table and columns it " +
2066
- "CHANGED. Render a write: a reply saying 'done!' is not evidence a " +
2067
+ "the agent DID — the table it read and the search text and filters it " +
2068
+ "read with, or the table and columns it CHANGED. Render a write: a reply saying 'done!' is not evidence a " +
2067
2069
  "row was written, and the user has no other way to notice a mistake. " +
2068
2070
  "A talk-only turn omits the key entirely. " +
2069
2071
  "You CANNOT set the system prompt, replay an edited transcript, " +
@@ -6155,7 +6157,11 @@ const CONTRACT = deepFreeze({
6155
6157
  // the link that opens BankID on this device. Signing hooks document
6156
6158
  // polling, the two ways to start BankID and how to read a verdict;
6157
6159
  // useFileSignatures is described as the caller's own signatures.
6158
- version: "1.197.0",
6160
+ // 1.198.0: additive (sc-12088) — `useNavigation().scrollIntoView(ref)`
6161
+ // scrolls the page to an element inside the calling widget on both hosts.
6162
+ // 1.199.0: additive (sc-11952) — an `AgentActivity` read entry may carry
6163
+ // `search` and `filters`: what the agent searched for, never its rows.
6164
+ version: "1.199.0",
6159
6165
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
6160
6166
  hooks: HOOKS,
6161
6167
  primitives: PRIMITIVES,
package/dist/contract.js CHANGED
@@ -1205,6 +1205,8 @@ const HOOKS = [
1205
1205
  "() => void — opens the signed-in user's own start page, the page the app opens on for them (it can differ per user, since page access is restricted per group). Use it for a 'back to start' or 'home' button instead of hard-coding a page. A no-op where there is no live router (the Studio canvas, a badge counter).",
1206
1206
  scrollToSection:
1207
1207
  "(sectionId: string) => void — scrolls THIS page to one of its sections (a root section or a layout container), landing its top just below any pinned site header; smooth unless the visitor prefers reduced motion. Take the id from a `sectionRef` prop the author picks; never hard-code a node id. An unknown, deleted or hidden section is a no-op, and so is the Studio canvas.",
1208
+ scrollIntoView:
1209
+ "(ref: { current }) => void — scrolls THIS page so an element inside the calling widget (the ref you attached to a View, Text or other primitive) lands just below any pinned site header; smooth unless the visitor prefers reduced motion. Use it for anchors within the widget's own content, such as a table of contents that jumps to one of its headings. Never fake page anchors with a height-capped inner ScrollView: the page itself scrolls. A ref that is not mounted is a no-op, and so is the Studio canvas.",
1208
1210
  goBack: "() => void",
1209
1211
  push: "(pageIdOrSlug: string, params?: object) => void",
1210
1212
  replace: "(pageIdOrSlug: string, params?: object) => void",
@@ -2062,8 +2064,8 @@ const HOOKS = [
2062
2064
  "`messages` is the running transcript, oldest first, each " +
2063
2065
  "{ id, role, content, created_at } with role 'user' | 'assistant'. " +
2064
2066
  "An assistant message that used a tool also carries `activity`: what " +
2065
- "the agent DID — the table it read, or the table and columns it " +
2066
- "CHANGED. Render a write: a reply saying 'done!' is not evidence a " +
2067
+ "the agent DID — the table it read and the search text and filters it " +
2068
+ "read with, or the table and columns it CHANGED. Render a write: a reply saying 'done!' is not evidence a " +
2067
2069
  "row was written, and the user has no other way to notice a mistake. " +
2068
2070
  "A talk-only turn omits the key entirely. " +
2069
2071
  "You CANNOT set the system prompt, replay an edited transcript, " +
@@ -6151,7 +6153,11 @@ const CONTRACT = deepFreeze({
6151
6153
  // the link that opens BankID on this device. Signing hooks document
6152
6154
  // polling, the two ways to start BankID and how to read a verdict;
6153
6155
  // useFileSignatures is described as the caller's own signatures.
6154
- version: "1.197.0",
6156
+ // 1.198.0: additive (sc-12088) — `useNavigation().scrollIntoView(ref)`
6157
+ // scrolls the page to an element inside the calling widget on both hosts.
6158
+ // 1.199.0: additive (sc-11952) — an `AgentActivity` read entry may carry
6159
+ // `search` and `filters`: what the agent searched for, never its rows.
6160
+ version: "1.199.0",
6155
6161
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
6156
6162
  hooks: HOOKS,
6157
6163
  primitives: PRIMITIVES,
package/dist/hooks.js CHANGED
@@ -744,11 +744,14 @@ export function useContainerWidth() {
744
744
  }
745
745
 
746
746
  /**
747
- * Returns the host-provided navigation surface:
748
- * `{ goTo, goToStartPage, goBack, push, replace, back, currentRoute }`.
747
+ * Returns the host-provided navigation surface: `{ goTo, goToStartPage,
748
+ * scrollToSection, scrollIntoView, goBack, push, replace, back, currentRoute,
749
+ * openLink }`.
749
750
  *
750
751
  * `goTo(pageId, params?)` navigates to an internal app page. `goToStartPage()`
751
752
  * opens the signed-in user's own start page, which can differ per user.
753
+ * `scrollToSection(id)` scrolls the page to one of its sections, and (sc-12088)
754
+ * `scrollIntoView(ref)` to an element inside the calling widget.
752
755
  * `goBack()` pops the stack. `push` / `replace` / `back` are aliases that map to
753
756
  * the platform's native navigator (react-router on web, react-navigation
754
757
  * on native). Missing methods degrade to no-ops on the Studio canvas
package/dist/index.d.ts CHANGED
@@ -850,6 +850,7 @@ export interface WidgetContext<TProps = unknown> {
850
850
  goTo(pageId: string, params?: Record<string, string>): void;
851
851
  goToStartPage(): void;
852
852
  scrollToSection(sectionId: string): void;
853
+ scrollIntoView(ref: { readonly current: unknown } | object | null | undefined): void;
853
854
  goBack(): void;
854
855
  push(pageId: string, params?: Record<string, unknown>): void;
855
856
  replace(pageId: string, params?: Record<string, unknown>): void;
@@ -1789,6 +1790,17 @@ export interface AgentActivity {
1789
1790
  table?: string;
1790
1791
  record_id?: string;
1791
1792
  fields?: string[];
1793
+ /** A read's own search text: what the agent looked FOR, never what it found. */
1794
+ search?: string;
1795
+ /** A read's own filter conditions, as the agent asked for them. */
1796
+ filters?: AgentActivityFilter[];
1797
+ }
1798
+
1799
+ /** One condition a read filtered by; `operator` is eq, neq, gt, gte, lt, lte or contains. */
1800
+ export interface AgentActivityFilter {
1801
+ column: string;
1802
+ operator: string;
1803
+ value: string;
1792
1804
  }
1793
1805
 
1794
1806
  export interface AgentApi {
@@ -2221,6 +2233,8 @@ export function useFitScreenInsets(): FitScreenInsets;
2221
2233
  * opens the signed-in user's own start page, which can differ per user.
2222
2234
  * `scrollToSection(sectionId)` scrolls this page to one of its sections (the
2223
2235
  * id comes from a `sectionRef` prop); an unknown section is a no-op.
2236
+ * `scrollIntoView(ref)` scrolls this page to an element inside the calling
2237
+ * widget, such as a heading its table of contents jumps to.
2224
2238
  * Missing methods degrade to no-ops on the Studio canvas preview where no
2225
2239
  * live router is mounted. For external URLs use the `Linking` primitive.
2226
2240
  */
@@ -2228,6 +2242,7 @@ export function useNavigation(): {
2228
2242
  goTo(pageId: string, params?: Record<string, unknown>): void;
2229
2243
  goToStartPage(): void;
2230
2244
  scrollToSection(sectionId: string): void;
2245
+ scrollIntoView(ref: { readonly current: unknown } | object | null | undefined): void;
2231
2246
  goBack(): void;
2232
2247
  push(pageId: string, params?: Record<string, unknown>): void;
2233
2248
  replace(pageId: string, params?: Record<string, unknown>): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.243.0",
3
+ "version": "0.245.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
@@ -1205,6 +1205,8 @@ const HOOKS = [
1205
1205
  "() => void — opens the signed-in user's own start page, the page the app opens on for them (it can differ per user, since page access is restricted per group). Use it for a 'back to start' or 'home' button instead of hard-coding a page. A no-op where there is no live router (the Studio canvas, a badge counter).",
1206
1206
  scrollToSection:
1207
1207
  "(sectionId: string) => void — scrolls THIS page to one of its sections (a root section or a layout container), landing its top just below any pinned site header; smooth unless the visitor prefers reduced motion. Take the id from a `sectionRef` prop the author picks; never hard-code a node id. An unknown, deleted or hidden section is a no-op, and so is the Studio canvas.",
1208
+ scrollIntoView:
1209
+ "(ref: { current }) => void — scrolls THIS page so an element inside the calling widget (the ref you attached to a View, Text or other primitive) lands just below any pinned site header; smooth unless the visitor prefers reduced motion. Use it for anchors within the widget's own content, such as a table of contents that jumps to one of its headings. Never fake page anchors with a height-capped inner ScrollView: the page itself scrolls. A ref that is not mounted is a no-op, and so is the Studio canvas.",
1208
1210
  goBack: "() => void",
1209
1211
  push: "(pageIdOrSlug: string, params?: object) => void",
1210
1212
  replace: "(pageIdOrSlug: string, params?: object) => void",
@@ -2062,8 +2064,8 @@ const HOOKS = [
2062
2064
  "`messages` is the running transcript, oldest first, each " +
2063
2065
  "{ id, role, content, created_at } with role 'user' | 'assistant'. " +
2064
2066
  "An assistant message that used a tool also carries `activity`: what " +
2065
- "the agent DID — the table it read, or the table and columns it " +
2066
- "CHANGED. Render a write: a reply saying 'done!' is not evidence a " +
2067
+ "the agent DID — the table it read and the search text and filters it " +
2068
+ "read with, or the table and columns it CHANGED. Render a write: a reply saying 'done!' is not evidence a " +
2067
2069
  "row was written, and the user has no other way to notice a mistake. " +
2068
2070
  "A talk-only turn omits the key entirely. " +
2069
2071
  "You CANNOT set the system prompt, replay an edited transcript, " +
@@ -6151,7 +6153,11 @@ const CONTRACT = deepFreeze({
6151
6153
  // the link that opens BankID on this device. Signing hooks document
6152
6154
  // polling, the two ways to start BankID and how to read a verdict;
6153
6155
  // useFileSignatures is described as the caller's own signatures.
6154
- version: "1.197.0",
6156
+ // 1.198.0: additive (sc-12088) — `useNavigation().scrollIntoView(ref)`
6157
+ // scrolls the page to an element inside the calling widget on both hosts.
6158
+ // 1.199.0: additive (sc-11952) — an `AgentActivity` read entry may carry
6159
+ // `search` and `filters`: what the agent searched for, never its rows.
6160
+ version: "1.199.0",
6155
6161
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
6156
6162
  hooks: HOOKS,
6157
6163
  primitives: PRIMITIVES,