@lotics/app-sdk 0.79.1 → 0.79.3

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.
@@ -89,6 +89,17 @@ server validates system conditions by `type` and never reads `field_key` on them
89
89
  - **`error`** is a `string | null`. A failed query surfaces immediately — there is **no automatic
90
90
  retry** (no retry loop that masks the error). The last successful rows for the same key stay
91
91
  rendered. The next focus revalidation or an explicit `refetch()` re-runs it.
92
+ - **Never derive a FIGURE from `rows` without gating on `error`.** On a failure `rows` is `[]`,
93
+ and `[]` is indistinguishable from a genuinely empty result — so `rows.reduce(…)` returns `0`,
94
+ `rows.length` returns `0`, and a screen states a confident number that no query answered. This
95
+ is the one place the empty-vs-broken distinction is load-bearing: a list that renders nothing
96
+ looks obviously wrong, whereas a total that reads `0` looks *fine*. It cost a sales register
97
+ nine days of showing every rep `0 ₫` of commission, because one query's runtime filter named a
98
+ column it did not project and nothing read `error`. A count, a sum, a "N of M", a progress
99
+ meter, a badge — anything a reader would act on — renders a dash and the failure, never a
100
+ number, while `error !== null`. Note a key CHANGE resets this: a new `params`/`filter`/`sort`
101
+ is a fresh key with no prior rows, so the "last successful rows stay rendered" behaviour above
102
+ does not save you.
92
103
  - **`refetch()`** re-runs the query. Call it after a known mutation point — a successful
93
104
  `useWorkflow` call — to pull the latest state (see [./mutations.md](./mutations.md)).
94
105
  `usePaginatedQuery.refetch()` refreshes the page, and the count when the hook owns it
@@ -110,7 +121,7 @@ server validates system conditions by `type` and never reads `field_key` on them
110
121
  | `query timed out after 15s — narrow the filter or simplify the query` | The query hit the per-query statement timeout (15 s). Usually an unindexed predicate scanning a large table — filter *shape* drives latency; see the performance contract in [./queries.md](./queries.md). | Narrow with an indexed filter or `search`, reduce the work per request. |
111
122
  | `The app is handling too many requests right now. Please retry in a moment.` | The server's bounded-concurrency gate shed the query under load (a 503). Distinct from a query error — nothing is wrong with the query itself. | Retry (e.g. surface a retry button wired to `refetch()`); the hook does not auto-retry. |
112
123
  | `query execution failed` | The query failed at the database. Deliberately generic — database internals are never sent to the client. | The app author diagnoses from the platform's server logs; the app surfaces the message. |
113
- | A specific validation message | e.g. an un-projected `field_key` in runtime `sort`/`filter`, invalid params, an unknown alias. | Fix the call site — these are contract violations, not transient. |
124
+ | A specific validation message | e.g. an un-projected `field_key` in runtime `sort`/`filter`, invalid params, an unknown alias. An unknown column names every column the query DOES project, so the valid set is in the message. | Fix the call site — these are contract violations, not transient. |
114
125
 
115
126
  ## Pagination — two models
116
127
 
package/docs/queries.md CHANGED
@@ -116,6 +116,14 @@ source_table_id?, source_field_key? }` per column. Rows are plain objects keyed
116
116
  column name; decode cells with the SDK readers (`row.*`, `readSelect`, `readMembers`,
117
117
  `readLinks`, `readFiles` — see [data_fetching.md](./data_fetching.md)).
118
118
 
119
+ **`source_fields_by_table_id` is always `{}`** — the key is part of the shape, the map is not
120
+ filled. The server reads the source tables' field definitions to resolve option and member
121
+ cells and then keeps them: sending them beside every page was 94% of a real register
122
+ response's compressed bytes, for a map no reader wanted there. **The complete option sets —
123
+ every option including those no loaded row holds, with colors — are `useFieldOptions`**
124
+ (`/field-options`, which derives them from the same definitions); a cell's own `{ key, label }`
125
+ comes down on the cell. See [members_and_options.md](./members_and_options.md).
126
+
119
127
  Delivery-layer enrichment (applied to the response, per request):
120
128
 
121
129
  - **`files` cells** — each entry gains a presigned `url` + `thumbnail_url` (24 h TTL) and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/app-sdk",
3
- "version": "0.79.1",
3
+ "version": "0.79.3",
4
4
  "description": "Runtime SDK for Lotics custom-code apps — typed hooks, postMessage bridge, mount entry point",
5
5
  "type": "module",
6
6
  "exports": {