@lotics/app-sdk 0.64.0 → 0.64.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.
Files changed (2) hide show
  1. package/docs/queries.md +18 -7
  2. package/package.json +1 -1
package/docs/queries.md CHANGED
@@ -431,7 +431,7 @@ derived surfaces share one implementation.
431
431
 
432
432
  | Field type | Column type | Source-layer operators | Derived-layer differences | Sort / group / aggregate notes |
433
433
  | --- | --- | --- | --- | --- |
434
- | text | `text` | `equals`, `not_equals`, `contains`, `does_not_contain`, `starts_with`, `ends_with`, `is_empty`, `is_not_empty`, `is_any_of`, `is_none_of` | same set | Sort is lexicographic in the database's default collation — no locale control. Text comparisons normalize both sides (trim + case-insensitive). |
434
+ | text | `text` | `equals`, `not_equals`, `contains`, `does_not_contain`, `starts_with`, `ends_with`, `is_empty`, `is_not_empty`, `is_any_of`, `is_none_of` | same set | Sort is lexicographic in the database's default collation — no locale control. Text comparisons normalize both sides (trim + case-insensitive). The LIKE family (`contains`, `does_not_contain`, `starts_with`, `ends_with`) additionally folds **diacritics**, so `contains "ha noi"` matches `"Hà Nội"`; the identity operators (`equals`, `not_equals`, `is_any_of`, `is_none_of`) do **not** — they compare a stored value, and `mã` is not `ma`. |
435
435
  | number | `number` | `equals`, `not_equals`, `greater_than`, `less_than`, `greater_than_or_equal_to`, `less_than_or_equal_to`, `is_empty`, `is_not_empty` | same set | Full numeric aggregate set (§8). |
436
436
  | date / datetime | `date` / `datetime` (datetime when the field's format includes time) | `on`, `before`, `after`, `on_or_before`, `on_or_after`, `between`, `time_of_day`, `is_empty`, `is_not_empty` — values are `DateTimePoint`s (below) | same set | Day-level filters on datetime values expand to the full-day window. Sortable; bucketable in `group.by` (§8); `earliest`/`latest`/`min`/`max`/`date_range` aggregate. |
437
437
  | boolean | `boolean` | `equals` (value `true`/`false`; `false` matches NULL/missing) | same | `checked`/`unchecked`/`percent_*` aggregate (`unchecked` counts false **or** empty). |
@@ -596,11 +596,20 @@ It AND-s with `filter` (search *within* a scope) and is templatable
596
596
  (`"search": "{{params.q}}"`). An empty or whitespace-only term matches everything; an
597
597
  unresolved optional token prunes to match-all (§6).
598
598
 
599
- **Search-as-you-type uses `search`, never a `contains` OR-group.** Per-field `contains` is
600
- accent-*sensitive* and unindexed — a zero-match keystroke forces a full-partition scan that
601
- hangs the picker. Reserve an OR-group of `contains` for when you must bound exactly *which*
602
- fields match. Gate the fetch on a non-empty term client-side (`enabled`), or first paint dumps
603
- the table.
599
+ **Search-as-you-type over a LARGE table uses `search`, never a `contains` OR-group.** Per-field
600
+ `contains` is unindexed — a zero-match keystroke forces a full-partition scan that hangs the
601
+ picker. (It is not accent-sensitive; the LIKE family folds diacritics, §5.) Gate the fetch on a
602
+ non-empty term client-side (`enabled`), or first paint dumps the table.
603
+
604
+ **But `search` matches the WHOLE record, and on an identity box that is usually wrong.** The
605
+ search document indexes select option labels, member names, dates in three formats, and each
606
+ linked record's cached display text — including the `Field=True/False` pill a boolean renders
607
+ into. A box captioned "find a person" then matches rows that merely share a stage label or an
608
+ owner, and folding makes near-homographs collide (`tiến` ≡ `tiền`). Measured on a real 88-row
609
+ customer book, one such term returned **every row**. When the box means *find this person*, bound
610
+ it: an OR-group of `contains` over the two or three identity fields (name, national id, phone),
611
+ with the term param `required: false` so an empty box prunes the group to match-all. Use `search`
612
+ when the box genuinely means *find this anywhere in the record*.
604
613
 
605
614
  ---
606
615
 
@@ -872,7 +881,9 @@ and is not — and a playbook rule applied to the wrong query costs effort while
872
881
  ### The authoring rules
873
882
 
874
883
  1. **Filter at the source.** Push every static predicate into `from_table.filter`.
875
- 2. **Prefer `search`** for any free-text box; `contains` OR-groups only to bound the fields.
884
+ 2. **Match the operator to what the box MEANS.** *Find this anywhere in the record* → `search`.
885
+ *Find this person/order* → a `contains` OR-group over the identity fields (§7) — the
886
+ whole-record index will surface rows that only share an owner or a status.
876
887
  3. **Aggregate and bucket server-side.** A dashboard reads grouped rows, never raw rows it
877
888
  reduces in JS — raw-row shipping burns the 10k cap, the timeout, and bandwidth at once.
878
889
  4. **Project narrow.** Every un-rendered column is wasted bytes; every un-rendered `files`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/app-sdk",
3
- "version": "0.64.0",
3
+ "version": "0.64.1",
4
4
  "description": "Runtime SDK for Lotics custom-code apps — typed hooks, postMessage bridge, mount entry point",
5
5
  "type": "module",
6
6
  "exports": {