@lotics/app-sdk 0.67.0 → 0.69.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/dist/src/members.d.ts +21 -2
- package/dist/src/members.js +12 -7
- package/docs/ai.md +2 -2
- package/docs/data_fetching.md +2 -2
- package/docs/members_and_options.md +24 -18
- package/docs/queries.md +4 -2
- package/package.json +1 -1
package/dist/src/members.d.ts
CHANGED
|
@@ -9,6 +9,20 @@
|
|
|
9
9
|
*
|
|
10
10
|
* If the wire format changes, the resolver and this reader move together.
|
|
11
11
|
*/
|
|
12
|
+
/**
|
|
13
|
+
* A member, in the ONE shape every door returns — a `select_member` cell in a
|
|
14
|
+
* `useQuery` row and the `useMembers` roster alike.
|
|
15
|
+
*
|
|
16
|
+
* The two used to disagree: the roster carried an avatar, a resolved cell did
|
|
17
|
+
* not, and both were typed `ResolvedMember`, so an app author holding one could
|
|
18
|
+
* not tell which. That is why no register row ever rendered an avatar — the
|
|
19
|
+
* data was absent and nothing said so.
|
|
20
|
+
*
|
|
21
|
+
* The private fields share ONE boundary, not three: an authenticated member of
|
|
22
|
+
* the app's own org sees `email`, `image` and `groups`; an anonymous visitor to
|
|
23
|
+
* a public app sees `id` and `name`. Absent ≠ empty — `image: null` means the
|
|
24
|
+
* member has no photo, `image` MISSING means you were never told.
|
|
25
|
+
*/
|
|
12
26
|
export interface ResolvedMember {
|
|
13
27
|
id: string;
|
|
14
28
|
/** `null` when the id resolves outside the app's org (e.g. removed
|
|
@@ -18,9 +32,14 @@ export interface ResolvedMember {
|
|
|
18
32
|
/** Present only on authenticated responses. Omitted on public-app
|
|
19
33
|
* responses (no PII exposure to anonymous visitors). */
|
|
20
34
|
email?: string | null;
|
|
21
|
-
/** Avatar URL
|
|
22
|
-
*
|
|
35
|
+
/** Avatar URL, already presigned — render it directly. `null` when the member
|
|
36
|
+
* has no profile image (the common case: photos are opt-in). Omitted on
|
|
37
|
+
* public-app responses. */
|
|
23
38
|
image?: string | null;
|
|
39
|
+
/** The member's group names — the platform's "department". There is no
|
|
40
|
+
* department field; a member group is what an org uses to say Sale, Kế toán,
|
|
41
|
+
* CSKH. `[]` when the member is in none. Omitted on public-app responses. */
|
|
42
|
+
groups?: string[];
|
|
24
43
|
}
|
|
25
44
|
/**
|
|
26
45
|
* Parse a `useQuery` cell value into `ResolvedMember[]`. Returns `[]` for
|
package/dist/src/members.js
CHANGED
|
@@ -28,13 +28,18 @@ export function readMembers(value) {
|
|
|
28
28
|
continue;
|
|
29
29
|
const name = typeof obj.name === "string" ? obj.name : obj.name === null ? null : null;
|
|
30
30
|
const m = { id, name };
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
// PRESENT ⇒ copy, ABSENT ⇒ leave off. The distinction is the permission
|
|
32
|
+
// boundary itself: a missing key means a public reader was never told,
|
|
33
|
+
// while `null` means the member genuinely has none. Defaulting either to
|
|
34
|
+
// null here would erase that and make a gated field look like an empty one.
|
|
35
|
+
if ("email" in obj)
|
|
36
|
+
m.email = typeof obj.email === "string" ? obj.email : null;
|
|
37
|
+
if ("image" in obj)
|
|
38
|
+
m.image = typeof obj.image === "string" ? obj.image : null;
|
|
39
|
+
if ("groups" in obj) {
|
|
40
|
+
m.groups = Array.isArray(obj.groups)
|
|
41
|
+
? obj.groups.filter((g) => typeof g === "string")
|
|
42
|
+
: [];
|
|
38
43
|
}
|
|
39
44
|
out.push(m);
|
|
40
45
|
}
|
package/docs/ai.md
CHANGED
|
@@ -24,8 +24,8 @@ A declaration carries:
|
|
|
24
24
|
| `knowledge_doc_ids` | The knowledge docs the agent may read, and the whole set it can reach — a doc absent from this list is unreadable even if the agent names its id. Declare `grep_knowledge` / `read_knowledge` in `tool_names` to read them. There is no size limit and nothing is inlined, so a multi-megabyte reference corpus (a full tariff, a regulation set) is a normal declaration. Reach for `code_exec` only to COMPUTE across the corpus — counting, cross-referencing — never merely to read it |
|
|
25
25
|
| `query_aliases` | The app's own named queries the agent may run via `run_app_query` — its **entire read surface** over records. You list them; there is no "all of them" shorthand, and adding a query to the app never widens an existing agent. Omitted means the agent reads nothing |
|
|
26
26
|
| `workflow_aliases` | The app's own workflows the agent may invoke via `run_app_workflow` — its **entire write surface** |
|
|
27
|
-
| `
|
|
28
|
-
| `effort_level` | Optional reasoning depth for adaptive-thinking
|
|
27
|
+
| `model_tier` | Optional model tier — `haiku` \| `sonnet` \| `opus`. Omit (preferred) to follow the platform default tier, resolved at run time. A tier names capability, not a version, so the agent tracks model generations with no rewrite. Pin only a deliberate, tested choice |
|
|
28
|
+
| `effort_level` | Optional reasoning depth for adaptive-thinking tiers. Requires an explicit `model_tier` pin — effort is tuned per tier |
|
|
29
29
|
| `inputs` | Optional typed input schema for one run — the same vocabulary as workflow inputs (`text`, `number`, `file`, `member`, `record_link`, `select`, …). The server validates every run payload against it |
|
|
30
30
|
| `outputs` | Optional typed output schema. Declared → **structured agent** (the run must emit a matching result); omitted → **free-text agent** (the answer is the final prose) |
|
|
31
31
|
|
package/docs/data_fetching.md
CHANGED
|
@@ -240,7 +240,7 @@ Wire shapes per output column type:
|
|
|
240
240
|
| `date` | `"YYYY-MM-DD"` — a calendar day, no time component (a reduced-precision date field may also carry `"YYYY-MM"` or `"YYYY"`; see below) |
|
|
241
241
|
| `datetime` | `"YYYY-MM-DDTHH:mm"` — workspace wall-clock, minute precision |
|
|
242
242
|
| `select` | `Array<{ key, label }>` — one entry per selected option |
|
|
243
|
-
| `select_member` | `Array<{ id, name, email? }>` —
|
|
243
|
+
| `select_member` | `Array<{ id, name, email?, image?, groups? }>` — the last three only for authenticated viewers of the app's own org; `image` presigned |
|
|
244
244
|
| `select_record_link` | `Array<{ id, display }>` — target record id + its display text |
|
|
245
245
|
| `files` | `Array<{ id, filename, mime_type, url, thumbnail_url?, size?, created_at? }>` — presigned |
|
|
246
246
|
|
|
@@ -268,7 +268,7 @@ emits none of these" is a fact the type states rather than one you have to remem
|
|
|
268
268
|
| `row.link(cell)` | link cell → `{ id, display } \| null` | the FIRST linked record |
|
|
269
269
|
| `readLinks(cell)` | link cell → `{ id, display }[]` | ALL linked records (`[]` when empty) |
|
|
270
270
|
| `readSelect(cell)` | select cell → `ResolvedOption[]` | all selected options as `{ key, label }` (`[]` when empty) |
|
|
271
|
-
| `readMembers(cell)` | member cell → `ResolvedMember[]` | `{ id, name, email? }[]` (`[]` when empty) |
|
|
271
|
+
| `readMembers(cell)` | member cell → `ResolvedMember[]` | `{ id, name, email?, image?, groups? }[]` (`[]` when empty) |
|
|
272
272
|
| `readFiles(cell)` | files cell → `AppFile[]` | attached files with presigned URLs (`[]` when empty) |
|
|
273
273
|
| `readLocked(rowObj)` | the whole **row** → `boolean` | the record's lock state from `__source_locked` |
|
|
274
274
|
|
|
@@ -16,8 +16,8 @@ Every select and member value reaches the app in one of two shapes, and most scr
|
|
|
16
16
|
| --- | --- | --- |
|
|
17
17
|
| Render a stored select value | `readSelect(cell)` | The options the record actually holds — `{ key, label }`, **no color** |
|
|
18
18
|
| Populate a select picker, or color a stored value | `useFieldOptions(alias)` | Each select column's **complete** option list — `{ key, label, color }` — plus a `byKey` index |
|
|
19
|
-
| Render a stored member value | `readMembers(cell)` | The members the record actually holds
|
|
20
|
-
| Populate a member picker (assign UIs) | `useMembers(opts?)` | The org roster —
|
|
19
|
+
| Render a stored member value | `readMembers(cell)` | The members the record actually holds |
|
|
20
|
+
| Populate a member picker (assign UIs) | `useMembers(opts?)` | The org roster — every member, not only the referenced ones |
|
|
21
21
|
|
|
22
22
|
Cells are **self-describing**: the server rewrites raw storage shapes into resolved objects before
|
|
23
23
|
rows reach the app, so an app never maintains a hardcoded key→label or id→name map. Catalogs are
|
|
@@ -108,18 +108,24 @@ cell's own `{ key, label }`, which renders as a neutral badge. Never hand-map op
|
|
|
108
108
|
## Member cells (`readMembers`)
|
|
109
109
|
|
|
110
110
|
A `select_member` column's storage shape is a bare array of member ids. The server rewrites every
|
|
111
|
-
projected `select_member` cell to `Array<{ id, name, email? }
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
- **
|
|
119
|
-
|
|
120
|
-
|
|
111
|
+
projected `select_member` cell to `Array<{ id, name, email?, image?, groups? }>` — **the same shape
|
|
112
|
+
`useMembers` returns**, so a member is a member wherever you got them from:
|
|
113
|
+
|
|
114
|
+
- **`email`, `image` and `groups` share ONE gate.** All three are present only for authenticated
|
|
115
|
+
members of the app's own organization. Anonymous visitors to a public app — and members of *other*
|
|
116
|
+
orgs viewing a publicly shared app — get `{ id, name }` only. A face and a team are org-internal
|
|
117
|
+
exactly as an address is.
|
|
118
|
+
- **Absent is not null.** A missing `image` key means you were not told; `image: null` means the
|
|
119
|
+
member genuinely has no photo. Same for `groups`: absent vs `[]`. Rendering the two the same way
|
|
120
|
+
turns a permission boundary into a missing-data bug.
|
|
121
|
+
- **`image` is already presigned** — render it directly. It is a `/v1/files/…` object behind a
|
|
122
|
+
cookie-authenticated proxy, so an unsigned URL would not load inside an app iframe at all.
|
|
123
|
+
- **`groups` is the platform's "department".** There is no department field; a member group is what
|
|
124
|
+
an org uses to say Sale, Kế toán, CSKH. `[]` when the member is in none.
|
|
125
|
+
- **An id that no longer resolves** (removed member, id outside the org) comes back with
|
|
126
|
+
`name: null` — an explicit missing state, never an empty string. Render a placeholder.
|
|
121
127
|
- Only ids already present in the projected rows are resolved — a member cell never exposes the
|
|
122
|
-
wider directory.
|
|
128
|
+
wider directory, and only their avatars are signed.
|
|
123
129
|
|
|
124
130
|
Decode with **`readMembers(cell)`** (`dist/src/members.d.ts`) → `ResolvedMember[]`. Same defensive
|
|
125
131
|
contract as `readSelect`: `[]` for null/empty/unexpected cells, malformed entries dropped.
|
|
@@ -133,9 +139,9 @@ const { members, loading, error } = useMembers({ group: "grp_fulfillment" });
|
|
|
133
139
|
<MemberSelect members={members} value={assignee} onValueChange={setAssignee} />
|
|
134
140
|
```
|
|
135
141
|
|
|
136
|
-
Each member is `{ id, name, email, image }`.
|
|
137
|
-
hours, or the member's external OAuth photo) and
|
|
138
|
-
members without a display name — fall back to `email`. On failure the hook does not throw: it
|
|
142
|
+
Each member is the same `ResolvedMember` a cell carries — `{ id, name, email, image, groups }`.
|
|
143
|
+
`image` is the avatar URL (a presigned URL valid 24 hours, or the member's external OAuth photo) and
|
|
144
|
+
may be `null`. `name` may be null/empty for members without a display name — fall back to `email`. On failure the hook does not throw: it
|
|
139
145
|
resolves `{ members: [], loading: false, error }`.
|
|
140
146
|
|
|
141
147
|
**Limitation:** `useMembers` is not SWR-cached — every mounted hook instance issues its own
|
|
@@ -294,7 +300,7 @@ directly (full props: `node_modules/@lotics/ui/AGENTS.md` and its `docs/`):
|
|
|
294
300
|
| Value | Component | Feed it |
|
|
295
301
|
| --- | --- | --- |
|
|
296
302
|
| A select value (stored or picker option) | `OptionBadge` | A `useFieldOptions` option, or `byKey(readSelect(cell)[0]?.key)`; accepts a single option, an array (multi → one badge each), or null (renders nothing). Missing/unknown color → neutral. |
|
|
297
|
-
| A person, inline | `MemberChip` | `name` / `image`
|
|
303
|
+
| A person, inline | `MemberChip` | `name` / `image` from a roster or a cell — both carry it; no image → initials |
|
|
298
304
|
| A member picker | `MemberSelect` | `members={useMembers().members}` — renders each option as a `MemberChip`; `MEMBER_UNASSIGNED` marks its optional "unassigned" option |
|
|
299
305
|
| A comment thread | `CommentList` + `CommentComposer` | `useComments` state; `resolveMember` bridges `member_id` → your member source |
|
|
300
306
|
|
|
@@ -305,7 +311,7 @@ The SDK never imports `@lotics/ui` — the app owns the (thin) data→UI adapter
|
|
|
305
311
|
| Surface | Embedded (signed-in member) | Standalone / public (anonymous) |
|
|
306
312
|
| --- | --- | --- |
|
|
307
313
|
| `readSelect` cell enrichment | ✓ | ✓ |
|
|
308
|
-
| `readMembers` cell enrichment | ✓ (email
|
|
314
|
+
| `readMembers` cell enrichment | ✓ (email + image + groups, same-org) | ✓ name-only |
|
|
309
315
|
| `useFieldOptions` | ✓ | ✓ |
|
|
310
316
|
| `useMembers` | ✓ (same-org + declaration gates) | ✗ errors |
|
|
311
317
|
| `useViewer` | member id (view-as target) | `null` |
|
package/docs/queries.md
CHANGED
|
@@ -124,8 +124,10 @@ Delivery-layer enrichment (applied to the response, per request):
|
|
|
124
124
|
server-bounded (§10). Public apps hand out direct presigned URLs — anonymous-fetchable,
|
|
125
125
|
time-boxed. (App workflow-execute responses presign returned files the same way, so their
|
|
126
126
|
URLs also work for anonymous public-app viewers — see [files.md](./files.md).)
|
|
127
|
-
- **`select_member` cells** — bare member-id arrays become
|
|
128
|
-
|
|
127
|
+
- **`select_member` cells** — bare member-id arrays become
|
|
128
|
+
`{ id, name, email?, image?, groups? }[]`, the same shape `useMembers` returns. The last three
|
|
129
|
+
are org-internal and share one gate: present only for authenticated members of the app's own org,
|
|
130
|
+
and `image` arrives presigned.
|
|
129
131
|
- **`select` cells** — bare option-key arrays become `{ key, label }[]` (colors ride in
|
|
130
132
|
`useFieldOptions`, not the cell — see [members_and_options.md](./members_and_options.md)).
|
|
131
133
|
**Enrichment needs the column's source addressing**, which a passthrough projection carries, a
|