diff --git a/.changeset/11591-org-flows-copy.md b/.changeset/11591-org-flows-copy.md
new file mode 100644
index 0000000000..cacd51d0b6
--- /dev/null
+++ b/.changeset/11591-org-flows-copy.md
@@ -0,0 +1,10 @@
+---
+'@object-ui/app-shell': patch
+'@object-ui/i18n': patch
+---
+
+Studio's "Organization flows" page no longer says its drafts publish atomically, and a deep link to a flow that is not on the page no longer says no metadata designers are registered (objectui#11591).
+
+On the package-less page (`/studio/~org/automations`), the pending-changes sheet read "Publishing releases the 1 pending draft of this package atomically." That page has no package, and its Publish promotes each draft by itself: a draft that fails stays pending while the others go live. The sheet now says so there, through a new `preview.changes.confirmNoteSeparate` plural family in all ten language packs. A package's sheet keeps its sentence unchanged. `DraftChangesPanel` picks the sentence from its `packageId` prop: with a package, the atomic sentence; without one, the per-draft sentence.
+
+On the Automations pillar, the canvas chip read the designer registry for the open flow's type. With no flow open (a deep link naming a flow the list does not hold, or an empty list), it found none and showed "No metadata designers are registered in this session…" beside the right message, on a page whose flow designer is registered. The chip now reads the registry for the pillar's own type, `flow`, as the configuration panel beside it already did. The notice still shows when no designer is registered, whether or not a flow is open.
diff --git a/packages/app-shell/src/preview/DraftChangesPanel.tsx b/packages/app-shell/src/preview/DraftChangesPanel.tsx
index 56532f29f8..26054d525a 100644
--- a/packages/app-shell/src/preview/DraftChangesPanel.tsx
+++ b/packages/app-shell/src/preview/DraftChangesPanel.tsx
@@ -335,7 +335,12 @@ function EntryDetail({ entry }: { entry: DraftChangeEntry }) {
export interface DraftChangesPanelProps {
open: boolean;
onOpenChange: (open: boolean) => void;
- /** When set, list only pending drafts belonging to this package (Studio is package-scoped). */
+ /**
+ * When set, list only pending drafts belonging to this package (Studio is
+ * package-scoped), and the confirm note says they publish as one atomic
+ * batch. Without one, the note says each draft publishes by itself
+ * (objectui#11591).
+ */
packageId?: string | null;
/**
* Narrows the listed drafts further, for a host whose scope the `_drafts`
@@ -627,11 +632,22 @@ export function DraftChangesPanel({
{onPublish && (entries?.length ?? 0) > 0 && !error && (
- {t('preview.changes.confirmNote', {
- count: entries!.length,
- defaultValue:
- 'Publishing releases all {{count}} pending drafts of this package atomically.',
- })}
+ {/* objectui#11591 — the note states what THIS scope's Publish
+ does. Only a package publishes as one atomic batch (`POST
+ /packages/:id/publish-drafts`); with no package there is no
+ batch door, so the caller promotes each draft by itself and a
+ draft that fails stays pending while the others go live. */}
+ {packageId
+ ? t('preview.changes.confirmNote', {
+ count: entries!.length,
+ defaultValue:
+ 'Publishing releases all {{count}} pending drafts of this package atomically.',
+ })
+ : t('preview.changes.confirmNoteSeparate', {
+ count: entries!.length,
+ defaultValue:
+ 'Publishing releases the {{count}} pending drafts one at a time: a draft that fails stays pending while the others go live.',
+ })}
{
+ cleanup();
+ vi.restoreAllMocks();
+});
+
+type Draft = { type: string; name: string; packageId: string | null };
+
+/** Answer the `_drafts` feed with `drafts`, and every other read as absent. */
+function serveDrafts(drafts: Draft[]) {
+ global.fetch = vi.fn(async (input: RequestInfo | URL) => {
+ const url = String(input);
+ const ok = (body: unknown) => ({ ok: true, status: 200, json: async () => body });
+ if (url.includes('/_drafts')) return ok(drafts);
+ if (/\/meta\/flow(\?|$)/.test(url)) return ok([]);
+ return { ok: false, status: 404, json: async () => ({}) };
+ }) as unknown as typeof fetch;
+}
+
+function renderSheet(packageId: string | null) {
+ return render(
+
+ {}} packageId={packageId} onPublish={vi.fn()} />
+ ,
+ );
+}
+
+/** The confirm footer's note — the paragraph right above the publish button. */
+async function confirmNote(): Promise {
+ const button = await screen.findByTestId('draft-changes-publish', undefined, { timeout: 4000 });
+ const note = button.parentElement?.querySelector('p');
+ if (!note) throw new Error('the confirm footer rendered no note');
+ return note.textContent ?? '';
+}
+
+describe('the confirm note says what this scope publishes (objectui#11591)', () => {
+ it('package-less, one draft: no package, no atomicity', async () => {
+ serveDrafts([{ type: 'flow', name: 'qa_urgent_alert_clone', packageId: null }]);
+ renderSheet(null);
+
+ const note = await confirmNote();
+ expect(note).toBe('Publishing releases the 1 pending draft on its own: if it fails, it stays pending.');
+ expect(note).not.toMatch(/atomic/i);
+ expect(note).not.toMatch(/package/i);
+ });
+
+ it('package-less, several drafts: each separately, and a partial publish is possible', async () => {
+ serveDrafts([
+ { type: 'flow', name: 'qa_urgent_alert_clone', packageId: null },
+ { type: 'flow', name: 'qa_daily_digest_clone', packageId: null },
+ ]);
+ renderSheet(null);
+
+ const note = await confirmNote();
+ expect(note).toBe(
+ 'Publishing releases the 2 pending drafts one at a time: a draft that fails stays pending while the others go live.',
+ );
+ expect(note).not.toMatch(/atomic/i);
+ expect(note).not.toMatch(/package/i);
+ });
+
+ it('control — a package’s sheet keeps its sentence, byte for byte', async () => {
+ serveDrafts([{ type: 'flow', name: 'showcase_daily_digest', packageId: 'com.example.showcase' }]);
+ renderSheet('com.example.showcase');
+
+ await waitFor(async () =>
+ expect(await confirmNote()).toBe('Publishing releases the 1 pending draft of this package atomically.'),
+ );
+ });
+
+ it('control — a package’s sheet, several drafts', async () => {
+ serveDrafts([
+ { type: 'flow', name: 'showcase_daily_digest', packageId: 'com.example.showcase' },
+ { type: 'flow', name: 'showcase_urgent_task_alert', packageId: 'com.example.showcase' },
+ ]);
+ renderSheet('com.example.showcase');
+
+ expect(await confirmNote()).toBe('Publishing releases all 2 pending drafts of this package atomically.');
+ });
+});
diff --git a/packages/app-shell/src/views/studio-design/StudioDesignSurface.designerRegistryMissing.test.tsx b/packages/app-shell/src/views/studio-design/StudioDesignSurface.designerRegistryMissing.test.tsx
index ee187f96e7..86bfeea73c 100644
--- a/packages/app-shell/src/views/studio-design/StudioDesignSurface.designerRegistryMissing.test.tsx
+++ b/packages/app-shell/src/views/studio-design/StudioDesignSurface.designerRegistryMissing.test.tsx
@@ -314,4 +314,30 @@ describe('Automations pillar — the fourth site, found by sweeping (#6795 C, si
expect(document.body.textContent ?? '').not.toContain('click a node to configure');
expect(document.body.textContent ?? '').not.toContain('Click a node on the canvas');
});
+
+ /**
+ * objectui#11591 — the notice keys on the REGISTRY, not on an open flow: with
+ * no flow open (a deep link naming one the rail does not hold) it still
+ * shows here, where the registry is empty, and it does not show where the
+ * registry is populated — that half is pinned in
+ * `StudioDesignSurface.packageLessFlows-11553.test.tsx`.
+ */
+ it('with no flow open, still names the reason — the registry, not the selection, decides', async () => {
+ assertRegistriesEmptyWithControl();
+ mockClient.layered.mockClear();
+
+ render(
+
+
+ ,
+ );
+
+ await screen.findByText('The link names flow “nobody_has_this”, which is not here.', undefined, { timeout: 4000 });
+ const hits = screen.getAllByText(
+ 'No metadata designers are registered in this session, so this flow cannot be designed here.',
+ );
+ // The canvas header chip AND the rail, as with a flow open.
+ expect(hits.length).toBe(2);
+ expect(mockClient.layered).not.toHaveBeenCalledWith('flow', expect.anything());
+ });
});
diff --git a/packages/app-shell/src/views/studio-design/StudioDesignSurface.packageLessFlows-11553.test.tsx b/packages/app-shell/src/views/studio-design/StudioDesignSurface.packageLessFlows-11553.test.tsx
index 3b9bd32cfe..c6d07475c7 100644
--- a/packages/app-shell/src/views/studio-design/StudioDesignSurface.packageLessFlows-11553.test.tsx
+++ b/packages/app-shell/src/views/studio-design/StudioDesignSurface.packageLessFlows-11553.test.tsx
@@ -132,7 +132,7 @@ vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn(), info: vi.f
import { StudioDesignSurface } from './StudioDesignSurface';
import { createEmptyDataSource, failOnAbsorbedFetchError } from './__tests__/emptyDataSource';
-import { registerMetadataPreview } from '../metadata-admin/preview-registry';
+import { getMetadataPreview, registerMetadataPreview } from '../metadata-admin/preview-registry';
import { registerMetadataInspector } from '../metadata-admin/inspector-registry';
import { FlowPreview } from '../metadata-admin/previews/FlowPreview';
import { FlowInspector } from '../metadata-admin/inspectors/FlowInspector';
@@ -379,3 +379,65 @@ describe('the package-less scope reviews and publishes exactly its own drafts (o
expect(server.packageBatches).toEqual([]);
});
});
+
+/**
+ * objectui#11591 — the "no designers" notice keys on the designer registry,
+ * never on the absence of an open flow. On a deep-link miss and on an empty
+ * list no flow is open, and the canvas chip used to read the registry for the
+ * OPEN flow's type — none — so it said "No metadata designers are registered
+ * in this session…" on a page whose designers are registered (this file
+ * registers `FlowPreview` / `FlowInspector` above, and the controls below read
+ * the registry back). That the notice still shows when the registry really is
+ * empty, open flow or none, is pinned beside its other sites, in
+ * `StudioDesignSurface.designerRegistryMissing.test.tsx`.
+ */
+describe('a deep-link miss and an empty list show no "no designers" notice (objectui#11591)', () => {
+ const NO_DESIGNERS = 'No metadata designers are registered';
+
+ it('the measured miss: the package-less page, a link naming a packaged flow', async () => {
+ renderAt('/studio/~org/automations?surface=flow%3Ashowcase_urgent_task_alert');
+
+ await screen.findByText('The link names flow “showcase_urgent_task_alert”, which is not here.', undefined, {
+ timeout: 8000,
+ });
+ // Control: the designers are registered, so the notice would be false.
+ expect(getMetadataPreview('flow')).toBe(FlowPreview);
+ expect(document.body.textContent).not.toContain(NO_DESIGNERS);
+ expect(screen.getByText('Visual orchestration · click a node to configure')).toBeInTheDocument();
+ // No flow was opened in the missing one's place.
+ expect(mockClient.layered).not.toHaveBeenCalled();
+ expect(screen.getByRole('button', { name: /QA urgent alert clone/ })).toBeInTheDocument();
+ });
+
+ it('a package page’s miss, too', async () => {
+ renderAt(`/studio/${SHOWCASE}/automations?surface=flow%3Anobody_has_this`);
+
+ await screen.findByText('The link names flow “nobody_has_this”, which is not here.', undefined, { timeout: 8000 });
+ expect(getMetadataPreview('flow')).toBe(FlowPreview);
+ expect(document.body.textContent).not.toContain(NO_DESIGNERS);
+ });
+
+ it('an empty list shows the rail’s own empty state — no miss, no "no designers" notice', async () => {
+ server.active.delete('flow/qa_urgent_alert_clone');
+ renderAt('/studio/~org/automations');
+
+ await screen.findByText(
+ 'No flows outside a package yet. Clone a packaged flow in Setup › Packaged automation to edit the copy here.',
+ undefined,
+ { timeout: 8000 },
+ );
+ expect(screen.getByText('Select an automation')).toBeInTheDocument();
+ expect(getMetadataPreview('flow')).toBe(FlowPreview);
+ expect(document.body.textContent).not.toContain(NO_DESIGNERS);
+ expect(document.body.textContent).not.toContain('The link names flow');
+ expect(mockClient.layered).not.toHaveBeenCalled();
+ });
+
+ it('control: an open flow on the same page reads the same chip', async () => {
+ renderAt('/studio/~org/automations');
+ await openedFlow('qa_urgent_alert_clone');
+
+ expect(screen.getByText('Visual orchestration · click a node to configure')).toBeInTheDocument();
+ expect(document.body.textContent).not.toContain(NO_DESIGNERS);
+ });
+});
diff --git a/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx b/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx
index ea43b010c9..666866869a 100644
--- a/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx
+++ b/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx
@@ -4584,7 +4584,12 @@ export function AutomationsPillar({
// offer a way to author the first one (mirrors the object/app creators).
const [creating, setCreating] = React.useState(false);
const [createBusy, setCreateBusy] = React.useState(false);
- const Preview = getMetadataPreview(current?.type ?? '');
+ // objectui#11591 — keyed on the pillar's one type, as the inspector beside
+ // it is, never on the open flow's: with no flow open (a deep link naming one
+ // this rail does not hold, or an empty rail) a selection-keyed read found no
+ // designer and the canvas chip below said none were registered, on a page
+ // whose designers are.
+ const Preview = getMetadataPreview('flow');
const inspector = getMetadataInspector('flow');
const isEditable = !!Preview;
// objectui#6795 part C — the FOURTH site, found by sweeping past the three the
diff --git a/packages/i18n/src/__tests__/confirmNoteSeparate-11591.test.ts b/packages/i18n/src/__tests__/confirmNoteSeparate-11591.test.ts
new file mode 100644
index 0000000000..13a8dc6e2d
--- /dev/null
+++ b/packages/i18n/src/__tests__/confirmNoteSeparate-11591.test.ts
@@ -0,0 +1,115 @@
+/**
+ * ObjectUI
+ * Copyright (c) 2024-present ObjectStack Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+/**
+ * objectui#11591 — the package-less sheet's confirm note claims no atomicity
+ * and names no package, in any of the ten packs, and the package sheet's note
+ * keeps both.
+ *
+ * `preview.changes.confirmNote` is the package sheet's sentence: that Publish
+ * promotes the package's drafts in one atomic pass, and it does. The
+ * package-less "Organization flows" page publishes each draft by itself, so
+ * its sheet reads `preview.changes.confirmNoteSeparate` instead. The defect
+ * this pins is the package sentence reaching a surface that publishes draft
+ * by draft; copied into the new family, the claim would come back in that
+ * one language and no other gate would see it.
+ *
+ * ## How "claims atomicity" is read, per pack
+ *
+ * Each pack's package sentence spells the claim in its own words: an
+ * "atomically" adverb where the language has one, "all at once" where it does
+ * not (`ar` holds no atomic adverb at all). `CLAIM` names that spelling per
+ * pack, and every one is CONTROLLED: it must occur in every form of that
+ * pack's `confirmNote` family before its absence from `confirmNoteSeparate`
+ * means anything. A marker that stopped matching the package sentence would
+ * fail the control, not pass the check vacuously. `PACKAGE_WORD` is read and
+ * controlled the same way.
+ *
+ * ⛔ What this file does not judge: whether a translation says the rest of
+ * the sentence well — a translator does that. Slot completeness per CLDR
+ * category is `all-locales-key-parity.test.ts`'s, and slot selection
+ * `plural-categories-11432.test.ts`'s; both read this family by construction.
+ */
+import { describe, it, expect } from 'vitest';
+import { builtInLocales } from '../locales';
+
+type LocaleCode = keyof typeof builtInLocales;
+const LANGS = Object.keys(builtInLocales) as LocaleCode[];
+
+/** The atomicity claim, as each pack's package sentence spells it. */
+const CLAIM: Record = {
+ en: /atomically/u,
+ de: /atomar/u,
+ es: /atómicamente/u,
+ fr: /atomiquement/u,
+ pt: /atomicamente/u,
+ ru: /атомарно/u,
+ ar: /دفعة واحدة/u,
+ ja: /アトミック|まとめて/u,
+ ko: /원자적|한 번에/u,
+ zh: /原子|一次性/u,
+};
+
+/** The word for "package" in each pack's package sentence — controlled below. */
+const PACKAGE_WORD: Record = {
+ en: /package/iu,
+ de: /Paket/u,
+ es: /paquete/u,
+ fr: /paquet/u,
+ pt: /pacote/u,
+ ru: /пакет/u,
+ ar: /الحزمة/u,
+ ja: /パッケージ/u,
+ ko: /패키지/u,
+ zh: /包/u,
+};
+
+const changesOf = (lang: LocaleCode): Record =>
+ (builtInLocales[lang] as unknown as { preview: { changes: Record } }).preview.changes;
+
+/** Every leaf of one family in one pack: the base key and each suffixed slot. */
+function family(lang: LocaleCode, base: string): Array<[string, string]> {
+ return Object.entries(changesOf(lang))
+ .filter(([key]) => key === base || key.startsWith(`${base}_`))
+ .map(([key, value]) => [key, String(value)]);
+}
+
+describe('the package-less confirm note claims no atomicity in any pack (objectui#11591)', () => {
+ it('names a claim spelling for every pack — no pack left unjudged', () => {
+ expect(Object.keys(CLAIM).sort()).toEqual([...LANGS].sort());
+ expect(LANGS).toHaveLength(10);
+ });
+
+ it.each(LANGS)('%s: the package sentence carries the claim (control)', (lang) => {
+ const forms = family(lang, 'confirmNote');
+ expect(forms.length, `${lang} has no confirmNote family`).toBeGreaterThan(1);
+ const without = forms.filter(([, value]) => !CLAIM[lang].test(value)).map(([key]) => key);
+ expect(without, `${lang}: the claim spelling no longer matches these package forms`).toEqual([]);
+ });
+
+ it.each(LANGS)('%s: no package-less form carries it', (lang) => {
+ const forms = family(lang, 'confirmNoteSeparate');
+ expect(forms.length, `${lang} has no confirmNoteSeparate family`).toBeGreaterThan(1);
+ const claiming = forms.filter(([, value]) => CLAIM[lang].test(value)).map(([key]) => key);
+ expect(claiming, `${lang}: these package-less forms claim atomicity`).toEqual([]);
+ // And none of them speaks of a package the page does not have, in the
+ // words this pack's own package sentence uses for one.
+ expect(
+ forms.filter(([, value]) => PACKAGE_WORD[lang].test(value)).map(([key]) => key),
+ `${lang}: these package-less forms name a package`,
+ ).toEqual([]);
+ });
+});
+
+describe('the package word is read off the package sentence (objectui#11591)', () => {
+ it.each(LANGS)('%s: control — the package sentence names its package', (lang) => {
+ const forms = family(lang, 'confirmNote');
+ const without = forms.filter(([, value]) => !PACKAGE_WORD[lang].test(value)).map(([key]) => key);
+ expect(without, `${lang}: the package word no longer matches these package forms`).toEqual([]);
+ });
+});
diff --git a/packages/i18n/src/locales/ar.ts b/packages/i18n/src/locales/ar.ts
index 7d1cb7293d..1260e7fe37 100644
--- a/packages/i18n/src/locales/ar.ts
+++ b/packages/i18n/src/locales/ar.ts
@@ -4077,6 +4077,15 @@ const ar = {
confirmNote_few: 'النشر يُصدر كل المسودات المعلقة ({{count}}) لهذه الحزمة دفعة واحدة.',
confirmNote_many: 'النشر يُصدر كل المسودات المعلقة ({{count}}) لهذه الحزمة دفعة واحدة.',
confirmNote_other: 'النشر يُصدر كل المسودات المعلقة ({{count}}) لهذه الحزمة دفعة واحدة.',
+ // objectui#11591 — the package-less sheet: no package to batch, so each
+ // draft publishes by itself and one that fails stays pending.
+ confirmNoteSeparate: 'النشر يُصدر المسودات المعلقة ({{count}}) كلًّا على حدة: المسودة التي يتعذّر نشرها تبقى معلقة، وتُنشر البقية.',
+ confirmNoteSeparate_zero: 'النشر يُصدر المسودات المعلقة ({{count}}) كلًّا على حدة: المسودة التي يتعذّر نشرها تبقى معلقة، وتُنشر البقية.',
+ confirmNoteSeparate_one: 'النشر يُصدر المسودات المعلقة ({{count}}) كلًّا على حدة: المسودة التي يتعذّر نشرها تبقى معلقة، وتُنشر البقية.',
+ confirmNoteSeparate_two: 'النشر يُصدر المسودات المعلقة ({{count}}) كلًّا على حدة: المسودة التي يتعذّر نشرها تبقى معلقة، وتُنشر البقية.',
+ confirmNoteSeparate_few: 'النشر يُصدر المسودات المعلقة ({{count}}) كلًّا على حدة: المسودة التي يتعذّر نشرها تبقى معلقة، وتُنشر البقية.',
+ confirmNoteSeparate_many: 'النشر يُصدر المسودات المعلقة ({{count}}) كلًّا على حدة: المسودة التي يتعذّر نشرها تبقى معلقة، وتُنشر البقية.',
+ confirmNoteSeparate_other: 'النشر يُصدر المسودات المعلقة ({{count}}) كلًّا على حدة: المسودة التي يتعذّر نشرها تبقى معلقة، وتُنشر البقية.',
publishConfirm: 'نشر الكل',
// [`7a90afdf9`] Pre-publish security-posture findings, shown next to
// the confirm button so a refusal the door would issue is read BEFORE
diff --git a/packages/i18n/src/locales/de.ts b/packages/i18n/src/locales/de.ts
index 5b6ba315ff..f9748f3663 100644
--- a/packages/i18n/src/locales/de.ts
+++ b/packages/i18n/src/locales/de.ts
@@ -3754,6 +3754,11 @@ const de = {
confirmNote: 'Beim Veröffentlichen werden alle {{count}} ausstehenden Entwürfe dieses Pakets atomar freigegeben.',
confirmNote_one: 'Beim Veröffentlichen wird der {{count}} ausstehende Entwurf dieses Pakets atomar freigegeben.',
confirmNote_other: 'Beim Veröffentlichen werden alle {{count}} ausstehenden Entwürfe dieses Pakets atomar freigegeben.',
+ // objectui#11591 — the package-less sheet: no package to batch, so each
+ // draft publishes by itself and one that fails stays pending.
+ confirmNoteSeparate: 'Beim Veröffentlichen werden die {{count}} ausstehenden Entwürfe einzeln freigegeben: Ein Entwurf, der dabei scheitert, bleibt ausstehend, die übrigen werden trotzdem freigegeben.',
+ confirmNoteSeparate_one: 'Beim Veröffentlichen wird der {{count}} ausstehende Entwurf für sich allein freigegeben: Scheitert er, bleibt er ausstehend.',
+ confirmNoteSeparate_other: 'Beim Veröffentlichen werden die {{count}} ausstehenden Entwürfe einzeln freigegeben: Ein Entwurf, der dabei scheitert, bleibt ausstehend, die übrigen werden trotzdem freigegeben.',
publishConfirm: 'Alle veröffentlichen',
// [`7a90afdf9`] Pre-publish security-posture findings, shown next to
// the confirm button so a refusal the door would issue is read BEFORE
diff --git a/packages/i18n/src/locales/en.ts b/packages/i18n/src/locales/en.ts
index 30597d6e62..3419a6709c 100644
--- a/packages/i18n/src/locales/en.ts
+++ b/packages/i18n/src/locales/en.ts
@@ -3252,6 +3252,11 @@ const en = {
confirmNote: 'Publishing releases all {{count}} pending drafts of this package atomically.',
confirmNote_one: 'Publishing releases the {{count}} pending draft of this package atomically.',
confirmNote_other: 'Publishing releases all {{count}} pending drafts of this package atomically.',
+ // objectui#11591 — the package-less sheet: no package to batch, so each
+ // draft publishes by itself and one that fails stays pending.
+ confirmNoteSeparate: 'Publishing releases the {{count}} pending drafts one at a time: a draft that fails stays pending while the others go live.',
+ confirmNoteSeparate_one: 'Publishing releases the {{count}} pending draft on its own: if it fails, it stays pending.',
+ confirmNoteSeparate_other: 'Publishing releases the {{count}} pending drafts one at a time: a draft that fails stays pending while the others go live.',
publishConfirm: 'Publish all',
// [`7a90afdf9`] Pre-publish security-posture findings, shown next to
// the confirm button so a refusal the door would issue is read BEFORE
diff --git a/packages/i18n/src/locales/es.ts b/packages/i18n/src/locales/es.ts
index 1581382629..65a01eef17 100644
--- a/packages/i18n/src/locales/es.ts
+++ b/packages/i18n/src/locales/es.ts
@@ -3833,6 +3833,12 @@ const es = {
confirmNote_one: 'Publicar libera atómicamente {{count}} borrador pendiente de este paquete.',
confirmNote_many: 'Publicar libera atómicamente los {{count}} de borradores pendientes de este paquete.',
confirmNote_other: 'Publicar libera atómicamente los {{count}} borradores pendientes de este paquete.',
+ // objectui#11591 — the package-less sheet: no package to batch, so each
+ // draft publishes by itself and one that fails stays pending.
+ confirmNoteSeparate: 'Publicar libera los {{count}} borradores pendientes uno por uno: el que falle sigue pendiente y los demás se publican igualmente.',
+ confirmNoteSeparate_one: 'Publicar libera {{count}} borrador pendiente por sí solo: si falla, sigue pendiente.',
+ confirmNoteSeparate_many: 'Publicar libera los {{count}} de borradores pendientes uno por uno: el que falle sigue pendiente y los demás se publican igualmente.',
+ confirmNoteSeparate_other: 'Publicar libera los {{count}} borradores pendientes uno por uno: el que falle sigue pendiente y los demás se publican igualmente.',
publishConfirm: 'Publicar todo',
// [`7a90afdf9`] Pre-publish security-posture findings, shown next to
// the confirm button so a refusal the door would issue is read BEFORE
diff --git a/packages/i18n/src/locales/fr.ts b/packages/i18n/src/locales/fr.ts
index 7b7bc4ad4e..16f572aa73 100644
--- a/packages/i18n/src/locales/fr.ts
+++ b/packages/i18n/src/locales/fr.ts
@@ -3830,6 +3830,12 @@ const fr = {
confirmNote_one: 'La publication libère atomiquement {{count}} brouillon en attente de ce paquet.',
confirmNote_many: 'La publication libère atomiquement les {{count}} de brouillons en attente de ce paquet.',
confirmNote_other: 'La publication libère atomiquement les {{count}} brouillons en attente de ce paquet.',
+ // objectui#11591 — the package-less sheet: no package to batch, so each
+ // draft publishes by itself and one that fails stays pending.
+ confirmNoteSeparate: 'La publication libère les {{count}} brouillons en attente un par un : celui qui échoue reste en attente, les autres sont publiés quand même.',
+ confirmNoteSeparate_one: 'La publication libère isolément {{count}} brouillon en attente : s’il échoue, il reste en attente.',
+ confirmNoteSeparate_many: 'La publication libère les {{count}} de brouillons en attente un par un : celui qui échoue reste en attente, les autres sont publiés quand même.',
+ confirmNoteSeparate_other: 'La publication libère les {{count}} brouillons en attente un par un : celui qui échoue reste en attente, les autres sont publiés quand même.',
publishConfirm: 'Tout publier',
// [`7a90afdf9`] Pre-publish security-posture findings, shown next to
// the confirm button so a refusal the door would issue is read BEFORE
diff --git a/packages/i18n/src/locales/ja.ts b/packages/i18n/src/locales/ja.ts
index 7107105294..30654c24ae 100644
--- a/packages/i18n/src/locales/ja.ts
+++ b/packages/i18n/src/locales/ja.ts
@@ -3756,6 +3756,11 @@ const ja = {
confirmNote: '公開すると、このパッケージの保留中ドラフト {{count}} 件がまとめて(アトミックに)公開されます。',
confirmNote_one: '公開すると、このパッケージの保留中ドラフト {{count}} 件がまとめて(アトミックに)公開されます。',
confirmNote_other: '公開すると、このパッケージの保留中ドラフト {{count}} 件がまとめて(アトミックに)公開されます。',
+ // objectui#11591 — the package-less sheet: no package to batch, so each
+ // draft publishes by itself and one that fails stays pending.
+ confirmNoteSeparate: '公開すると、保留中のドラフト {{count}} 件がそれぞれ個別に公開されます。公開に失敗したドラフトは保留中のまま残り、ほかのドラフトは公開されます。',
+ confirmNoteSeparate_one: '公開すると、保留中のドラフト {{count}} 件がそれぞれ個別に公開されます。公開に失敗したドラフトは保留中のまま残り、ほかのドラフトは公開されます。',
+ confirmNoteSeparate_other: '公開すると、保留中のドラフト {{count}} 件がそれぞれ個別に公開されます。公開に失敗したドラフトは保留中のまま残り、ほかのドラフトは公開されます。',
publishConfirm: 'すべて公開',
// [`7a90afdf9`] Pre-publish security-posture findings, shown next to
// the confirm button so a refusal the door would issue is read BEFORE
diff --git a/packages/i18n/src/locales/ko.ts b/packages/i18n/src/locales/ko.ts
index 64b46630d8..f6fd36d8c4 100644
--- a/packages/i18n/src/locales/ko.ts
+++ b/packages/i18n/src/locales/ko.ts
@@ -3753,6 +3753,11 @@ const ko = {
confirmNote: '게시하면 이 패키지의 대기 중인 초안 {{count}}개가 한 번에(원자적으로) 게시됩니다.',
confirmNote_one: '게시하면 이 패키지의 대기 중인 초안 {{count}}개가 한 번에(원자적으로) 게시됩니다.',
confirmNote_other: '게시하면 이 패키지의 대기 중인 초안 {{count}}개가 한 번에(원자적으로) 게시됩니다.',
+ // objectui#11591 — the package-less sheet: no package to batch, so each
+ // draft publishes by itself and one that fails stays pending.
+ confirmNoteSeparate: '게시하면 대기 중인 초안 {{count}}개가 하나씩 따로 게시됩니다. 게시에 실패한 초안은 대기 상태로 남고, 나머지는 그대로 게시됩니다.',
+ confirmNoteSeparate_one: '게시하면 대기 중인 초안 {{count}}개가 하나씩 따로 게시됩니다. 게시에 실패한 초안은 대기 상태로 남고, 나머지는 그대로 게시됩니다.',
+ confirmNoteSeparate_other: '게시하면 대기 중인 초안 {{count}}개가 하나씩 따로 게시됩니다. 게시에 실패한 초안은 대기 상태로 남고, 나머지는 그대로 게시됩니다.',
publishConfirm: '모두 게시',
// [`7a90afdf9`] Pre-publish security-posture findings, shown next to
// the confirm button so a refusal the door would issue is read BEFORE
diff --git a/packages/i18n/src/locales/pt.ts b/packages/i18n/src/locales/pt.ts
index 5ba7cd0597..64e56b0dc4 100644
--- a/packages/i18n/src/locales/pt.ts
+++ b/packages/i18n/src/locales/pt.ts
@@ -3828,6 +3828,12 @@ const pt = {
confirmNote_one: 'Publicar libera atomicamente {{count}} rascunho pendente deste pacote.',
confirmNote_many: 'Publicar libera atomicamente todos os {{count}} de rascunhos pendentes deste pacote.',
confirmNote_other: 'Publicar libera atomicamente todos os {{count}} rascunhos pendentes deste pacote.',
+ // objectui#11591 — the package-less sheet: no package to batch, so each
+ // draft publishes by itself and one that fails stays pending.
+ confirmNoteSeparate: 'Publicar libera os {{count}} rascunhos pendentes um a um: o que falhar continua pendente e os demais são publicados mesmo assim.',
+ confirmNoteSeparate_one: 'Publicar libera {{count}} rascunho pendente isoladamente: se falhar, continua pendente.',
+ confirmNoteSeparate_many: 'Publicar libera os {{count}} de rascunhos pendentes um a um: o que falhar continua pendente e os demais são publicados mesmo assim.',
+ confirmNoteSeparate_other: 'Publicar libera os {{count}} rascunhos pendentes um a um: o que falhar continua pendente e os demais são publicados mesmo assim.',
publishConfirm: 'Publicar tudo',
// [`7a90afdf9`] Pre-publish security-posture findings, shown next to
// the confirm button so a refusal the door would issue is read BEFORE
diff --git a/packages/i18n/src/locales/ru.ts b/packages/i18n/src/locales/ru.ts
index 12202abf10..3cdd99b150 100644
--- a/packages/i18n/src/locales/ru.ts
+++ b/packages/i18n/src/locales/ru.ts
@@ -3928,6 +3928,13 @@ const ru = {
confirmNote_few: 'Публикация атомарно выпускает все {{count}} ожидающих черновика этого пакета.',
confirmNote_many: 'Публикация атомарно выпускает все {{count}} ожидающих черновиков этого пакета.',
confirmNote_other: 'Публикация атомарно выпускает {{count}} ожидающего черновика этого пакета.',
+ // objectui#11591 — the package-less sheet: no package to batch, so each
+ // draft publishes by itself and one that fails stays pending.
+ confirmNoteSeparate: 'Публикация выпускает {{count}} ожидающих черновиков по отдельности: черновик, который не удастся опубликовать, останется ожидающим, а остальные всё равно будут опубликованы.',
+ confirmNoteSeparate_one: 'Публикация выпускает {{count}} ожидающий черновик по отдельности: тот, который не удастся опубликовать, останется ожидающим.',
+ confirmNoteSeparate_few: 'Публикация выпускает {{count}} ожидающих черновика по отдельности: черновик, который не удастся опубликовать, останется ожидающим, а остальные всё равно будут опубликованы.',
+ confirmNoteSeparate_many: 'Публикация выпускает {{count}} ожидающих черновиков по отдельности: черновик, который не удастся опубликовать, останется ожидающим, а остальные всё равно будут опубликованы.',
+ confirmNoteSeparate_other: 'Публикация выпускает {{count}} ожидающего черновика по отдельности: черновик, который не удастся опубликовать, останется ожидающим, а остальные всё равно будут опубликованы.',
publishConfirm: 'Опубликовать всё',
// [`7a90afdf9`] Pre-publish security-posture findings, shown next to
// the confirm button so a refusal the door would issue is read BEFORE
diff --git a/packages/i18n/src/locales/zh.ts b/packages/i18n/src/locales/zh.ts
index c428ad22ef..43eb3c5d24 100644
--- a/packages/i18n/src/locales/zh.ts
+++ b/packages/i18n/src/locales/zh.ts
@@ -2869,6 +2869,11 @@ const zh = {
confirmNote: '发布将一次性(原子地)发布此包全部 {{count}} 个待发布草稿。',
confirmNote_one: '发布将一次性(原子地)发布此包全部 {{count}} 个待发布草稿。',
confirmNote_other: '发布将一次性(原子地)发布此包全部 {{count}} 个待发布草稿。',
+ // objectui#11591 — the package-less sheet: no package to batch, so each
+ // draft publishes by itself and one that fails stays pending.
+ confirmNoteSeparate: '发布会逐个单独发布这 {{count}} 个待发布草稿:发布失败的草稿仍保持待发布,其余草稿照常发布。',
+ confirmNoteSeparate_one: '发布会逐个单独发布这 {{count}} 个待发布草稿:发布失败的草稿仍保持待发布,其余草稿照常发布。',
+ confirmNoteSeparate_other: '发布会逐个单独发布这 {{count}} 个待发布草稿:发布失败的草稿仍保持待发布,其余草稿照常发布。',
publishConfirm: '全部发布',
// [`7a90afdf9`] Pre-publish security-posture findings, shown next to
// the confirm button so a refusal the door would issue is read BEFORE