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
+14 -6
View File
@@ -182,9 +182,7 @@ function subscriptionCommand() {
}
}));
const getCmd = cmd.command('get <subscriptionId>').description('Show full subscription config');
getCmd
.option('--base-id <id>', 'Base id (overrides default)')
.action((0, ctx_1.run)(async (ctx, args, o) => {
getCmd.option('--base-id <id>', 'Base id (overrides default)').action((0, ctx_1.run)(async (ctx, args, o) => {
const id = args[0];
const opts = o;
const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId);
@@ -234,7 +232,11 @@ function subscriptionCommand() {
if (ctx.json)
(0, output_1.printJson)(vo);
else
(0, output_1.printKv)([['id', vo.id], ['name', vo.name], ['created', '✓']]);
(0, output_1.printKv)([
['id', vo.id],
['name', vo.name],
['created', '✓'],
]);
}));
const updateCmd = cmd
.command('update <subscriptionId>')
@@ -251,7 +253,10 @@ function subscriptionCommand() {
if (ctx.json)
(0, output_1.printJson)(vo);
else
(0, output_1.printKv)([['id', vo.id], ['updated', '✓']]);
(0, output_1.printKv)([
['id', vo.id],
['updated', '✓'],
]);
}));
cmd
.command('delete <subscriptionId>')
@@ -308,7 +313,10 @@ function subscriptionCommand() {
if (ctx.json)
(0, output_1.printJson)(result ?? { id });
else
(0, output_1.printKv)([['id', id], ['sent', '✓']]);
(0, output_1.printKv)([
['id', id],
['sent', '✓'],
]);
}));
return cmd;
}