From aa111455817b3c5a43f22d72b809d010c2cd24ed Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 00:29:35 +0000 Subject: [PATCH] fix(app-shell): re-point saveAdvisoryToast's TranslateFn at its one authority MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `TranslateFn` had three declarations, and `KNOWN_COLLISIONS` in `scripts/__tests__/one-authority-per-exported-name-6273.test.ts` carried all three as accepted debt. That baseline is SHRINK-ONLY by its own text, and nothing owned shrinking it. Re-measured first, because the card's own caution is the one that matters: a re-export of a type that had quietly diverged would silently change two call sites' contracts, and the gate counts declarations, not shapes. All three declarations are byte-identical on this base — 86 bytes each, one sha256 (29f2f334f47b8ac55bd05fc5dcf7f0d769c303f0e2deb1c79aff6a9cd46e3762) across the three, identical `od -c` dumps. So there is no shape to reconcile. The authority did not need choosing either: `AdapterProvider` is the single caller of all three emitters and already imports `TranslateFn` from `./writeWarningToast.js`, passing that one value into each of them. `saveAdvisoryToast.ts` now re-exports it, which the gate explicitly does not count as a declaration, and the baseline entry loses that site in the same change. The `packages/fields/src/widgets/file-size-guard.ts` site is deliberately NOT re-pointed and stays on the baseline. `@object-ui/app-shell` DEPENDS ON `@object-ui/fields`, so a re-export in that direction is a package cycle, and `TranslateFn` is on neither package's published face, so there is nothing to import either. Retiring the last copy means moving the authority DOWN into a package both depend on, which publishes a new name and is a decision nobody has made. Type-level only, on a name no package publishes, so the changeset declares an empty frontmatter: no package is released by this change. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S --- .changeset/8165-translatefn-one-authority.md | 9 +++++ .../src/providers/saveAdvisoryToast.ts | 34 +++++++++++++++++-- ...e-authority-per-exported-name-6273.test.ts | 22 +++++++++++- 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 .changeset/8165-translatefn-one-authority.md diff --git a/.changeset/8165-translatefn-one-authority.md b/.changeset/8165-translatefn-one-authority.md new file mode 100644 index 0000000000..a172590962 --- /dev/null +++ b/.changeset/8165-translatefn-one-authority.md @@ -0,0 +1,9 @@ +--- +--- + +Re-point `saveAdvisoryToast`'s `TranslateFn` at its one authority, +`writeWarningToast` (objectui#8165), and shrink the `KNOWN_COLLISIONS` baseline +in `scripts/__tests__/one-authority-per-exported-name-6273.test.ts` by that +site. Type-level only: the declaration was byte-identical to the one it now +re-exports, the name is on no package's published face, and `export type { X } +from '…'` erases at build — so no package is released by this change. diff --git a/packages/app-shell/src/providers/saveAdvisoryToast.ts b/packages/app-shell/src/providers/saveAdvisoryToast.ts index e32b714381..e7d158be20 100644 --- a/packages/app-shell/src/providers/saveAdvisoryToast.ts +++ b/packages/app-shell/src/providers/saveAdvisoryToast.ts @@ -38,9 +38,39 @@ */ import type { MetadataSaveAdvisoryEvent } from '@object-ui/data-objectstack'; +import type { TranslateFn } from './writeWarningToast.js'; -/** i18next's `t`, narrowed to what this module uses. */ -export type TranslateFn = (key: string, options?: Record) => string; +/** + * i18next's `t`, narrowed to what this module uses — RE-EXPORTED from + * `writeWarningToast`, never re-declared. + * + * This module carried its own byte-identical copy until objectui#8165. The + * objectui#6172 甲/A1 ruling is that every exported name has exactly one + * authority, and + * `scripts/__tests__/one-authority-per-exported-name-6273.test.ts` had been + * carrying `TranslateFn` as accepted debt across three files. The remedy that + * gate names is exactly this: `export type { X } from ''` is a + * re-export, not a second declaration, and the gate does not count it. ⛔ Its + * baseline is SHRINK-ONLY, so the entry lost this site in the same PR. + * + * Why `writeWarningToast` is the one pointed at — read off the tree rather + * than decided here: + * + * - `AdapterProvider` is the single caller of all three emitters, and it + * already imports `TranslateFn` from `./writeWarningToast.js` and passes + * that one value into `emitSaveAdvisories` alongside the other two. + * - `metadataReadWarningToast.ts` re-points at it for the same reason, and + * `file-size-guard.ts` names it in its own docblock as the established + * pattern it was copied from. + * + * ⚠️ The THIRD site — `packages/fields/src/widgets/file-size-guard.ts` — is + * deliberately NOT re-pointed and stays on the baseline. `@object-ui/app-shell` + * DEPENDS ON `@object-ui/fields`, so a re-export in that direction is a package + * cycle, and this name is on neither package's published face. Retiring that + * copy means moving the authority DOWN into a package both depend on — a + * different change, and one nobody has ruled on (objectui#8165 reports it). + */ +export type { TranslateFn } from './writeWarningToast.js'; /** * Where the message goes. Structurally satisfied by sonner's `toast`, which is diff --git a/scripts/__tests__/one-authority-per-exported-name-6273.test.ts b/scripts/__tests__/one-authority-per-exported-name-6273.test.ts index 4e49d1ff2d..e6ddbd54b9 100644 --- a/scripts/__tests__/one-authority-per-exported-name-6273.test.ts +++ b/scripts/__tests__/one-authority-per-exported-name-6273.test.ts @@ -457,7 +457,27 @@ const KNOWN_COLLISIONS: ReadonlyMap = new Map([ ['RecordDetailDrawerProps', ['packages/plugin-dashboard/src/RecordDetailDrawer.tsx', 'packages/plugin-detail/src/RecordDetailDrawer.tsx']], ['SchemaNode', ['packages/sdui-parser/src/types.ts', 'packages/types/src/base.ts']], ['ThemeProviderProps', ['packages/providers/src/types.ts', 'packages/react/src/context/ThemeContext.tsx']], - ['TranslateFn', ['packages/app-shell/src/providers/saveAdvisoryToast.ts', 'packages/app-shell/src/providers/writeWarningToast.ts', 'packages/fields/src/widgets/file-size-guard.ts']], + // `TranslateFn` lost its `packages/app-shell/src/providers/saveAdvisoryToast.ts` + // site in objectui#8165. All three declarations were BYTE-IDENTICAL when that + // was measured (86 bytes each, one sha256 across the three), so there was no + // shape to reconcile; and the authority did not need choosing, because + // `AdapterProvider` is the single caller of all three emitters and already + // imported the type from `./writeWarningToast.js`, passing that one value into + // each of them. `saveAdvisoryToast.ts` now re-exports it, which this gate does + // not count. + // + // ⚠️ The `packages/fields` site STAYS, and that is a measurement rather than an + // oversight. The re-export remedy is dependency-illegal there: + // `@object-ui/app-shell` DEPENDS ON `@object-ui/fields`, so pointing fields at + // app-shell is a package cycle — and `TranslateFn` is on neither package's + // published face (`app-shell/src/index.ts` names it nowhere and has no star + // re-export; `fields/src/index.tsx` stars 50-odd widget modules but not + // `file-size-guard.js`, and none of its three importers re-export the name), so + // there would be nothing to import either. Retiring the last copy means moving + // the authority DOWN into a package both depend on — the `KanbanSchema` route + // above — which publishes a new name from that package and is a decision + // nobody has made. objectui#8165 reports it instead of guessing. + ['TranslateFn', ['packages/app-shell/src/providers/writeWarningToast.ts', 'packages/fields/src/widgets/file-size-guard.ts']], ['UndoRedoState', ['packages/plugin-designer/src/hooks/useUndoRedo.ts', 'packages/types/src/ui-action.ts']], ['UserDataAdapter', ['packages/app-shell/src/context/UserStateAdapters.tsx', 'packages/data-objectstack/src/userState.ts']], // `ValidationFunction` sat here, colliding between