Skip to content

Commit 46ec2ec

Browse files
fix(plugin-calendar): name the object by its label when a reschedule finds the record gone (#31528)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} --> _Requested by **Steedos** · [project thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbWVeUdPTouw9owJWMAZ76p7)_ Fixes #31494 Clause-②: yes (widening) Metadata-protocol: none ## 维护者速读 - **改了什么:** 日历拖拽改期被服务端以 404 RECORD_NOT_FOUND 拒绝时,提示不再显示服务端原句(英文、带记录 id 和对象机器名),改为当前语言的一句话,用对象的显示名称指代对象;卡片照旧弹回原日期。 - **为什么改:** 中文会话下用户看到 `Record WB5z1sLAsWgE1tXL not found in showcase_task` 这样的英文系统句,违反「显示名称优先于机器名」规范(QA #31486)。 - **风险与回滚:** 只在 404/RECORD_NOT_FOUND 分支生效;作者标注的 userMessage、外部数据源只读、权限拒绝三条分支仍排在前面不变。回滚即还原本 PR。 - **先看:** `ui/packages/plugin-calendar/src/ObjectCalendar.tsx`(新分支与 `isRecordNotFound`)、`ObjectCalendar.markedRefusalToast.test.tsx`(新 describe)、`ui/packages/i18n/src/locales/zh-Hans.ts`(新键译文)。 - **两分钟验证:** `cd ui && pnpm exec vitest run packages/plugin-calendar/src/ObjectCalendar.markedRefusalToast.test.tsx`,预期 12 个用例全绿,其中 zh 用例读到「这条任务记录已不存在,或者您已无权访问它。」 - **需要你做的:** 无,席位落地。 ## What changes - **`@object-ui/plugin-calendar`:** `handleEventDropDefault`'s toast chain gains one branch after the permission branch and before `extractWriteErrorMessage`: a refusal whose code (on the error or its `details`) is `RECORD_NOT_FOUND`, or whose status is 404, toasts `calendar.rescheduleNotFound` with the object named by its label. The label is the object schema's served `label` (already translated server-side), else the app's convention-key translation via `useSafeFieldLabel().objectLabel`, else no name at all: the toast then says the existing `form.recordNotFoundOrHidden` sentence. The server's sentence, the record id and the machine name never reach the toast. The rollback of the optimistic move is unchanged. The classifier is a small module-local `isRecordNotFound` built on `errorCodeIs` from `@object-ui/types`: `is404Error` lives in `@object-ui/data-objectstack`, which this package has only as a devDependency, so reusing it would have meant a new runtime dependency edge. - **`@object-ui/i18n`:** one new key, `calendar.rescheduleNotFound`, in all eleven `locales/*.ts` packs; zh-Hant generated from zh-Hans with `gen:zh-hant`. The sentence takes one placeholder, the object label. - The shared `extractWriteErrorMessage` is not touched (no caller census needed). ## Tests New top-level describe in the existing harness `ObjectCalendar.markedRefusalToast.test.tsx` (wire-shaped errors through the real `normaliseClientError`, real MonthView drag): - 404 RECORD_NOT_FOUND with a served label: the toast is the labelled en sentence, contains no id, no machine name, no "not found in"; the event is back on its own day and absent from the target day. - The same under a zh `I18nProvider` with the label 任务: the toast is the zh sentence. - No label anywhere: the generic `form.recordNotFoundOrHidden` sentence, no machine name. - A 404 that carries a `userMessage` still renders the marking. - The external-source refusal (EXTERNAL_WRITE_FORBIDDEN) keeps its read-only sentence; an unmarked 403 keeps the permission substitution. The test's drop-target lookup moved from the spoken cell label to the day number of the current-month cell, so the zh render finds it too; the previous `cellLabel` helper is removed. ## Acceptance notes - The same raw-sentence shape likely exists on other write toasts that end in `extractWriteErrorMessage` (11 non-test files under `ui/packages` and `ui/apps` call it, `git grep` at d15c0ad). Not changed here; the card scoped the fix to the calendar toast. ## Verification <details><summary>Local runs at a934d10 (suites re-run at d15c0ad)</summary> - `vitest run packages/plugin-calendar/src/ObjectCalendar.markedRefusalToast.test.tsx`: 12/12 passed. - `vitest run packages/plugin-calendar/ packages/i18n/`: Test Files 167 passed, Tests 2312 passed, 108 skipped. - `turbo run type-check --filter=@object-ui/plugin-calendar --filter=@object-ui/i18n`: 53/53 tasks successful. - `eslint` on the two calendar files: 0 errors (warnings all on untouched lines). - Ablation (`scripts/ablation-replace.mjs`, the `isRecordNotFound(err)` condition replaced by `false`): 3 failed, 9 passed; the three new not-found cases received `Record v1 not found in visit`; restore verified blob == HEAD, `git diff HEAD` empty. </details> <details><summary>Derived gates</summary> `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 38 families; all 38 ran at a934d10 and exited 0; `--ran` reconciliation: 38 derived, 38 run, 0 NOT-MEASURED, 0 UNRUN. ```text node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 node scripts/check-empty-changeset.mjs --self-test :: exit 0 node scripts/check-sdui-manifest.mjs :: exit 0 node scripts/check-sdui-manifest.mjs --self-test :: exit 0 node scripts/ci/snapshot-next.mjs --self-test :: exit 0 node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 node scripts/release-pending-publish.mjs --self-test :: exit 0 pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 pnpm check:changeset-gate-self-tests :: exit 0 pnpm check:closed-ceremony-text :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:dispatcher-error-vocabulary :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:engine-double-contract :: exit 0 pnpm check:gitlink-declared :: exit 0 pnpm check:merge-driver :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:published-files :: exit 0 pnpm check:query-options-erasure :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:registry-log-declared :: exit 0 pnpm check:rest-log-spy-declared :: exit 0 pnpm check:role-word :: exit 0 pnpm check:skill-identifier-liveness :: exit 0 pnpm check:test-source-alias :: exit 0 pnpm check:tier-file-adoption :: exit 0 pnpm check:type-check-coverage :: exit 0 pnpm check:type-check-debt :: exit 0 pnpm check:undeclared-dep-imports :: exit 0 pnpm check:watch-hint-literal :: exit 0 pnpm check:where-matcher :: exit 0 pnpm --dir ui exec vitest run scripts/ :: exit 0 ``` After merging main (head d15c0ad, the derived list unchanged), the ratchet and suite families were re-run: all exit 0, and the calendar + i18n suites again passed 167/167 files. ```text node scripts/check-sdui-manifest.mjs :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:test-source-alias :: exit 0 pnpm check:type-check-coverage :: exit 0 pnpm check:type-check-debt :: exit 0 pnpm check:undeclared-dep-imports :: exit 0 pnpm check:published-files :: exit 0 pnpm check:nul-bytes :: exit 0 ``` </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01QsR2jSxTNXbFBqyMe2bkhV)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7d7af56 commit 46ec2ec

14 files changed

Lines changed: 172 additions & 23 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/plugin-calendar': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
fix(plugin-calendar): a reschedule of a record that is gone for this session toasts a translated sentence naming the object by its label
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: none
10+
11+
- **`@object-ui/plugin-calendar`:** when a drag-to-reschedule is refused with `404 RECORD_NOT_FOUND` (the record was deleted, or row-level security no longer shows it to this user), the toast used to show the server's own sentence, "Record ID not found in OBJECT_NAME": English in every session, with the record id and the object's machine name. It now says, in the session language, that this record of the object (named by its label) no longer exists or is no longer accessible. The label is the one the object schema serves, else the app's translation of it; with neither, the toast says the existing generic "This record doesn't exist, or you don't have access to it." and never the machine name. The event still snaps back to its day. A refusal the producer marked for the user, an external-source refusal and a permission refusal keep their own messages, ahead of this one.
12+
- **`@object-ui/i18n`:** `calendar.rescheduleNotFound` in all eleven locale packs (zh-Hant generated from zh-Hans).

‎ui/packages/i18n/src/locales/ar.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,7 @@ const ar = {
864864
eventTitlePlaceholder: "ما موضوع هذا الحدث؟",
865865
creating: "جارٍ الإنشاء…",
866866
titleRequired: "العنوان مطلوب",
867+
rescheduleNotFound: "سجل {{object}} هذا لم يعد موجودًا، أو لم يعد لديك إذن للوصول إليه.",
867868
a11y: {
868869
region: "التقويم",
869870
grid: "شبكة التقويم",

‎ui/packages/i18n/src/locales/de.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,7 @@ const de = {
778778
eventTitlePlaceholder: "Worum geht es bei diesem Termin?",
779779
creating: "Wird erstellt…",
780780
titleRequired: "Titel ist erforderlich",
781+
rescheduleNotFound: "Dieser Datensatz ({{object}}) existiert nicht mehr, oder Sie haben keinen Zugriff mehr darauf.",
781782
a11y: {
782783
region: "Kalender",
783784
grid: "Kalenderraster",

‎ui/packages/i18n/src/locales/en.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,10 @@ const en = {
10461046
eventTitlePlaceholder: 'What\'s this event about?',
10471047
creating: 'Creating…',
10481048
titleRequired: 'Title is required',
1049+
// A drag-to-reschedule the server refused because the record is gone for
1050+
// this session: deleted, or no longer visible to it. `{{object}}` is the
1051+
// object's label; with none to show, `form.recordNotFoundOrHidden` is used.
1052+
rescheduleNotFound: 'This {{object}} record no longer exists, or you no longer have access to it.',
10491053
a11y: {
10501054
region: 'Calendar',
10511055
grid: 'Calendar grid',

‎ui/packages/i18n/src/locales/es.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,7 @@ const es = {
803803
eventTitlePlaceholder: "¿De qué trata este evento?",
804804
creating: "Creando…",
805805
titleRequired: "El título es obligatorio",
806+
rescheduleNotFound: "Este registro de {{object}} ya no existe o ya no tienes acceso a él.",
806807
a11y: {
807808
region: "Calendario",
808809
grid: "Cuadrícula del calendario",

‎ui/packages/i18n/src/locales/fr.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,7 @@ const fr = {
798798
eventTitlePlaceholder: "De quoi s'agit-il ?",
799799
creating: "Création…",
800800
titleRequired: "Le titre est obligatoire",
801+
rescheduleNotFound: "Cet enregistrement ({{object}}) n'existe plus, ou vous n'y avez plus accès.",
801802
a11y: {
802803
region: "Calendrier",
803804
grid: "Grille du calendrier",

‎ui/packages/i18n/src/locales/ja.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,7 @@ const ja = {
778778
eventTitlePlaceholder: "この予定の内容は?",
779779
creating: "作成中…",
780780
titleRequired: "タイトルは必須です",
781+
rescheduleNotFound: "この{{object}}レコードは存在しないか、アクセス権がなくなりました。",
781782
a11y: {
782783
region: "カレンダー",
783784
grid: "カレンダーグリッド",

‎ui/packages/i18n/src/locales/ko.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,7 @@ const ko = {
778778
eventTitlePlaceholder: "이 일정은 어떤 내용인가요?",
779779
creating: "생성 중…",
780780
titleRequired: "제목을 입력하세요",
781+
rescheduleNotFound: "이 {{object}} 레코드가 더 이상 존재하지 않거나 접근 권한이 없습니다.",
781782
a11y: {
782783
region: "캘린더",
783784
grid: "캘린더 그리드",

‎ui/packages/i18n/src/locales/pt-BR.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,7 @@ const pt = {
798798
eventTitlePlaceholder: "Sobre o que é este evento?",
799799
creating: "Criando…",
800800
titleRequired: "O título é obrigatório",
801+
rescheduleNotFound: "Este registro de {{object}} não existe mais ou você não tem mais acesso a ele.",
801802
a11y: {
802803
region: "Calendário",
803804
grid: "Grade do calendário",

‎ui/packages/i18n/src/locales/ru.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,7 @@ const ru = {
821821
eventTitlePlaceholder: "О чём это событие?",
822822
creating: "Создание…",
823823
titleRequired: "Укажите название",
824+
rescheduleNotFound: "Эта запись ({{object}}) больше не существует, или у вас больше нет к ней доступа.",
824825
a11y: {
825826
region: "Календарь",
826827
grid: "Сетка календаря",

0 commit comments

Comments
 (0)