feat(view-preset): shared view presets with fork-on-edit semantics

Lands the view-preset / server-persisted personal-layer feature that has
been running in production, plus the hotfix for "the same preset shows
different filter configs to different users" (物流问题-已出轨迹 on
tblgsJTXhqwiDpsjeL5 / viwyszJmLQNOLhA9sh5).

Root cause of the divergence:
- usePersonalViewSync implicitly flushed any overlay edit by a
  view|update user INTO the shared preset row (last-writer-wins). Overlays
  are stale snapshots seeded at apply time, so one user's seed silently
  erased colleagues' filter changes (2026-09-17 02:55 the preset lost the
  流转角色=售后 condition), and active overlays never converged because
  preset updates do not propagate.

Fix (semantics: presets are shared definitions, edits are personal):
- fork-on-edit: the first user edit strips the seed markers from the
  overlay (toolbar switches to the personal config); edits only ever
  write the user's personal row
- shared presets change ONLY through the new explicit, confirmed
  "Update to current config" row action (canManage users)
- un-edited preset overlays auto-follow redefinitions: applyPreset stamps
  a presetVersion (preset.lastModifiedTime); when the preset list
  refetches with a newer row the overlay re-seeds. Edited overlays carry
  no markers and are never clobbered. Legacy rows without a version
  re-seed exactly once
- exits strip both seed markers from the persisted personal row

Also lands the pending WIP shipped with the deployed build: select-choice
visibility constraint, personal-view server sync + proxy, grid async
records scope/request tracking, CLI resource display, preset i18n (10
locales), view_preset migrations, docs/skill updates, .zcode gitignore.

Verified end-to-end on the live instance (headless chromium, real
session): apply -> edit flips toolbar to Personal with ZERO shared-row
PUTs; explicit update PUTs the shared row; API-side redefinition
auto-reseeds an un-edited overlay on the next list refetch.
This commit is contained in:
yeuimu
2026-09-17 04:07:30 +00:00
parent 1df830adc4
commit 035384131c
15 changed files with 1309 additions and 258 deletions
@@ -43,14 +43,16 @@ inkreach-teable table create --name <n> [--base-id <id>] [--fields '<json>']
inkreach-teable table update <tableId> --name <n>
inkreach-teable table delete <tableId>
# field (get/update/delete take <tableId> <fieldId> — 2026-08 fix; old /api/field/:id routes never existed)
inkreach-teable field list <tableId>
inkreach-teable field get <tableId> <fieldId>
# field (get/update/visible-choices take <tableId> <fieldIdOrName> — id or display name, auto-resolved)
inkreach-teable field list <tableId> [--with-options]
# --with-options: OPTIONS summary column + per-choice visibility conditions for select fields
inkreach-teable field get <tableId> <fieldIdOrName>
# human mode renders select choices with their visibilityFilter conditions; --json for raw
inkreach-teable field create --table-id <tid> --name <n> [--type <t|alias>] [--options '<json>'] \
[--description <d>] [--lookup-options '<json>'] [--is-lookup]
# --type optional: inferred from --options shape / --name when omitted
inkreach-teable field update <tableId> <fieldId> [--name <n>] [--options '<json>'] # --options goes via convert API
inkreach-teable field visible-choices <tableId> <recordId> <fieldId> # evaluate select-choice visibilityFilter
inkreach-teable field update <tableId> <fieldIdOrName> [--name <n>] [--options '<json>'] # --options goes via convert API
inkreach-teable field visible-choices <tableId> <recordId> <fieldIdOrName> # evaluate select-choice visibilityFilter
inkreach-teable field delete <tableId> <fieldId>
# record
@@ -67,6 +69,13 @@ inkreach-teable view get <viewId>
inkreach-teable view create --table-id <tid> --name <n> [--type <t>]
inkreach-teable view update <viewId> [--name <n>]
inkreach-teable view delete <viewId>
# view presets (view-scoped shared named filter/sort bundles, applied via toolbar)
inkreach-teable preset list <tableId> [--view-id <vid>]
inkreach-teable preset create --table-id <tid> --view-id <vid> --name <n> [--description <d>] \
[--filter '<json>'] [--sort '<json>'] [--group '<json>'] [--options '<json>'] [--column-meta '<json>']
inkreach-teable preset update <presetId> --table-id <tid> [--name <n>] [--description <d>] [--filter '<json>' ...]
inkreach-teable preset delete <presetId> --table-id <tid>
```
## workflow
@@ -157,6 +166,21 @@ inkreach-teable fire webhook <baseId> <workflowId> [--body '<json>'] [--method <
Manually trigger for testing. Webhook needs no auth (backend is `@Public`).
## subscription
```bash
inkreach-teable subscription list
inkreach-teable subscription get <subscriptionId>
inkreach-teable subscription create --name <n> --table-id <tid> [options]
inkreach-teable subscription update <subscriptionId> [options]
inkreach-teable subscription delete <subscriptionId>
inkreach-teable subscription events <subscriptionId>
inkreach-teable subscription send <subscriptionId>
```
Record digest/instant email subscriptions. Options (`--filter`, `--send-mode`,
`--schedule-*`, `--trigger-on-enter`, …) and semantics: [subscription.md](subscription.md).
## sql
```bash