@lotics/app-sdk 0.81.1 → 0.82.1

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.
@@ -1,8 +1,6 @@
1
1
  /** A file attached to a comment, in the resolved (serving) shape the list returns. */
2
2
  export interface AppCommentFile {
3
3
  id: string;
4
- /** Storage key — needed to re-send the file when editing a comment. */
5
- file_storage_key: string;
6
4
  filename: string;
7
5
  mime_type: string;
8
6
  url?: string;
@@ -32,14 +32,16 @@ import { useCallback } from "react";
32
32
  import useSWR from "swr";
33
33
  import { rpc } from "./rpc.js";
34
34
  import { useAppContext } from "./viewer.js";
35
- /** The reduced storage shape the update endpoint accepts for `files`. */
36
- function toStorageFiles(files) {
37
- return (files ?? []).map((f) => ({
38
- id: f.id,
39
- file_storage_key: f.file_storage_key,
40
- filename: f.filename,
41
- mime_type: f.mime_type,
42
- }));
35
+ /**
36
+ * The attachment set as ids — what the update endpoint wants.
37
+ *
38
+ * Sending whole file objects instead would be a full-snapshot write of metadata
39
+ * this client does not own: a filename or a storage key echoed back from a read
40
+ * lands in the row verbatim, so a stale copy overwrites what is stored. An id
41
+ * the server resolves for itself cannot do that.
42
+ */
43
+ function toFileIds(files) {
44
+ return (files ?? []).map((f) => f.id);
43
45
  }
44
46
  let optimisticCounter = 0;
45
47
  /**
@@ -122,7 +124,7 @@ export function useComments(args) {
122
124
  record_id,
123
125
  comment_id: id,
124
126
  content,
125
- files: toStorageFiles(currentFiles),
127
+ file_ids: toFileIds(currentFiles),
126
128
  });
127
129
  return rpc("comments.list", { record_id });
128
130
  }, {
package/docs/ai.md CHANGED
@@ -322,6 +322,25 @@ Publish the **rendered** view, not the whole table. A screen that shows page 2 o
322
322
 
323
323
  Declarative and lifecycle-bound: mounting or changing `context` pushes it; unmounting, renaming the `slot`, or passing `null` clears it. Independent components hold **different slots** at once (a list screen + an open drawer), and the newest value per slot wins. Re-posting is change-gated — passing a fresh inline object each render does **not** spam the host; only a real value change re-publishes.
324
324
 
325
+ ### A slot that never publishes looks exactly like a slot with nothing to say
326
+
327
+ The failure mode worth knowing before you wire one: this hook has **no visible output**. A
328
+ `useAiContext` behind a guard that never opens publishes `null` forever while the screen it
329
+ describes renders perfectly, and nothing anywhere reports it. Silence is the same shape as
330
+ "there is nothing to report", so neither the app nor a review catches it.
331
+
332
+ The usual cause is a guard on the query state. `useQuery`'s `error` is `string | null` — it is
333
+ **never `undefined`** — so `error !== undefined` is always true and holds the guard shut on every
334
+ render. Write `if (loading || error) return;`, which is correct whichever of the two the field
335
+ turns out to be, and reads as the question you meant.
336
+
337
+ **Verify by observing what is published, never by reading the code.** Run the app under
338
+ `lotics app dev` and listen for the host notification on the wrapper page — the payload is the
339
+ context as the chat agent will receive it. Check three things, because each fails differently:
340
+ the slot carries REAL values matching what is on screen (not placeholders), it clears to `null`
341
+ when the surface closes or the tab changes, and it does not publish while the first read is still
342
+ in flight — a count of zero taken from a pending query is an answer nothing measured.
343
+
325
344
  ### The caps (host-enforced — exceeding them truncates or drops, never errors)
326
345
 
327
346
  | Field | Cap |
@@ -279,8 +279,9 @@ updateComment, deleteComment, refetch }`.
279
279
  - `comments` — newest first on the wire (server order). Pass the array as-is to `@lotics/ui`'s
280
280
  `CommentList`, which re-sorts oldest-first for display. Each `AppComment`: `{ id, record_id,
281
281
  table_id, member_id, content, files, workspace_id, created_at, updated_at }`. Attachments
282
- (`AppCommentFile`) carry `id` / `filename` / `mime_type` plus `file_storage_key` (needed when
283
- re-sending files on edit). The `url` / `thumbnail_url` / `preview_url` fields exist on the type
282
+ (`AppCommentFile`) carry `id` / `filename` / `mime_type` a file's identity is its `id`, and
283
+ the server re-reads every attachment from storage by that id, so nothing else you hold about a
284
+ file can affect what is stored. The `url` / `thumbnail_url` / `preview_url` fields exist on the type
284
285
  but the server does not populate them today — render attachments by name and type (what
285
286
  `CommentList`'s default file row does), never by counting on a fetchable URL.
286
287
  - `createComment({ content, file_ids? })` — posts as the viewing member. `file_ids` come from
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/app-sdk",
3
- "version": "0.81.1",
3
+ "version": "0.82.1",
4
4
  "description": "Runtime SDK for Lotics custom-code apps \u2014 typed hooks, postMessage bridge, mount entry point",
5
5
  "type": "module",
6
6
  "exports": {