From 153ef58903b0246c6731486465260cc81c3f82d3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 07:35:06 +0000 Subject: [PATCH 1/9] feat(console): the param dialog's submit button reads the action's submitText Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01L9qibzPbVCsG2npDkDqikd --- .../src/hooks/useConsoleActionRuntime.tsx | 12 +++++++++--- .../app-shell/src/views/ActionParamDialog.tsx | 10 +++++++++- .../app-shell/src/views/RecordDetailView.tsx | 11 ++++++++--- ui/packages/core/src/actions/ActionRunner.ts | 10 ++++++++++ ui/packages/core/src/actions/actionKeys.ts | 8 ++++++-- .../useObjectLabel-identity-5564.test.tsx | 6 ++++-- ui/packages/i18n/src/useObjectLabel.ts | 16 ++++++++++++++++ ui/packages/types/src/ui-action.ts | 12 +++++++++++- 8 files changed, 73 insertions(+), 12 deletions(-) diff --git a/ui/packages/app-shell/src/hooks/useConsoleActionRuntime.tsx b/ui/packages/app-shell/src/hooks/useConsoleActionRuntime.tsx index a17e2680bbf..598d1e03ab5 100644 --- a/ui/packages/app-shell/src/hooks/useConsoleActionRuntime.tsx +++ b/ui/packages/app-shell/src/hooks/useConsoleActionRuntime.tsx @@ -25,7 +25,7 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAuth, createAuthenticatedFetch } from '@object-ui/auth'; import { usePermissions } from '@object-ui/permissions'; -import { useObjectLabel, useObjectTranslation } from '@object-ui/i18n'; +import { useObjectLabel, useObjectTranslation, pickLocalized } from '@object-ui/i18n'; import { ActionProvider, useGlobalUndo, useMetadata, type ActionProviderProps } from '@object-ui/react'; import { toast } from 'sonner'; import type { @@ -170,7 +170,7 @@ export function useConsoleActionRuntime(opts: ConsoleActionRuntimeOptions): Cons // `requiredPermissions` action closed on exactly the deployments this // doctrine exists to protect (objectui#4656). const { systemPermissions } = usePermissions(); - const { fieldLabel, fieldOptionLabel, actionParamText, actionParamOptionLabel, actionDescription, actionResultDialog } = useObjectLabel(); + const { fieldLabel, fieldOptionLabel, actionParamText, actionParamOptionLabel, actionDescription, actionSubmitText, actionResultDialog } = useObjectLabel(); // Entitlement 403s render as a dialog, not a toast — its copy is localized // here rather than taken from the server (objectui#2458 / cloud#959). // `language` also resolves inline per-locale action-param labels below, and @@ -342,6 +342,11 @@ export function useConsoleActionRuntime(opts: ConsoleActionRuntimeOptions): Cons const overrideNotice = typeof action?.overrideNotice === 'string' && action.overrideNotice ? action.overrideNotice : undefined; + // The submit button's verb (`submitText`): the bundle's + // `_actions..submitText` (`globalActions..submitText` for an + // object-less action) first, then the authored `I18nLabel` collapsed to + // the active language. Absent, the dialog keeps its default "Confirm". + const submitText = actionSubmitText(objForI18n, action?.name, pickLocalized(action?.submitText, language) || undefined); setParamState({ open: true, params: localized, @@ -356,6 +361,7 @@ export function useConsoleActionRuntime(opts: ConsoleActionRuntimeOptions): Cons description: overrideNotice ? [overrideNotice, declaredDescription].filter(Boolean).join('\n\n') : declaredDescription, + submitText, interpolate, resolve, // The runner's `run`, so Confirm runs the action from inside the @@ -363,7 +369,7 @@ export function useConsoleActionRuntime(opts: ConsoleActionRuntimeOptions): Cons run, }); })(); }); - }, [objectName, objectDef, objects, metadata, fieldLabel, fieldOptionLabel, actionParamText, actionParamOptionLabel]); + }, [objectName, objectDef, objects, metadata, fieldLabel, fieldOptionLabel, actionParamText, actionParamOptionLabel, actionSubmitText, language]); const currentUser = user ? { id: user.id, name: user.name, avatar: user.image, isPlatformAdmin: (user as any)?.isPlatformAdmin ?? false, systemPermissions } diff --git a/ui/packages/app-shell/src/views/ActionParamDialog.tsx b/ui/packages/app-shell/src/views/ActionParamDialog.tsx index bb5693535b7..11f12eda40b 100644 --- a/ui/packages/app-shell/src/views/ActionParamDialog.tsx +++ b/ui/packages/app-shell/src/views/ActionParamDialog.tsx @@ -96,6 +96,14 @@ export interface ParamDialogState { * so the dialog reads as the task, not a generic param prompt. */ title?: string; description?: string; + /** + * Label of the submit button — the action's declared `submitText`, already + * resolved by the caller (the translation bundle's + * `_actions..submitText` first, then the authored text collapsed to + * the active language). Absent, the button says the generic "Confirm"; while + * a file is uploading it says "Uploading…" either way. + */ + submitText?: string; /** * Fills the `${...}` holes of each param's `helpText` — the runner's * `ParamCollectionHandler` `interpolate` argument, passed through. It runs on @@ -913,7 +921,7 @@ export function ActionParamDialog({ state, onOpenChange }: ActionParamDialogProp aria-busy={submitting || undefined} > {submitting &&