@@ -70,6 +70,8 @@ const transition = (
7070 confirmText ?: string ;
7171 /** Grey the button out while a precondition the hook will refuse is unmet (see below). */
7272 disabled ?: ReturnType < typeof P > ;
73+ /** What the greyed button says on hover: the precondition `disabled` mirrors. Only beside `disabled`. */
74+ disabledReason ?: string ;
7375 } ,
7476) : Action => ( {
7577 name,
@@ -155,9 +157,17 @@ const transition = (
155157 // guard it mirrors. Like `visible` it is a convenience, not a control: the
156158 // guards in contract.hook.ts read the children directly and keep refusing.
157159 //
158- // The console shows its generic reason on hover (「对此记录不可用」 / "Not
159- // available for this record"): ActionSchema has no key for an authored one.
160+ // `disabledReason` is what the greyed button says on hover and to a screen
161+ // reader (issue #195; ActionSchema key since 18.3.0, objectstack#26619).
162+ // Without it the console says only 「对此记录不可用」 / "Not available for
163+ // this record". It is fixed copy, not an expression: it cannot tell WHICH
164+ // half of an `||` predicate is true, so a reason names every condition its
165+ // predicate tests. It mirrors the hook's refusal sentence for the same
166+ // guard, in the reader's words. Translated at
167+ // `objects.clm_contract._actions.ACTION.disabledReason`. Display only — the
168+ // hook still decides.
160169 ...( options ?. disabled ? { disabled : options . disabled } : { } ) ,
170+ ...( options ?. disabledReason ? { disabledReason : options . disabledReason } : { } ) ,
161171 // The status this button writes is the status the header shows, and the
162172 // status every one of these `visible` predicates reads. The spec defaults
163173 // `refreshAfter` to `false` (materialized at parse), and the record page
@@ -238,7 +248,10 @@ export const SendForApprovalAction: Action = transition(
238248 'Close legal review and start approval. Possible once a legal review is approved and no deviation is still open.' ,
239249 'Sent for approval.' ,
240250 [ 'clm_legal.access' ] ,
241- { disabled : P `record.open_deviation_count > 0 || record.approved_legal_review_count == 0` } ,
251+ {
252+ disabled : P `record.open_deviation_count > 0 || record.approved_legal_review_count == 0` ,
253+ disabledReason : 'An approved legal review is required, and no deviation may still be open, before the contract enters approval.' ,
254+ } ,
242255) ;
243256
244257/** 发起签署 — approved, now get it signed. The hook refuses it unless the
@@ -249,7 +262,10 @@ export const StartSigningAction: Action = transition(
249262 'Move an approved contract into signing. Possible once the current version is a clean copy.' ,
250263 'Signing started.' ,
251264 [ 'execute_contract' ] ,
252- { disabled : P `record.current_clean_version_count == 0` } ,
265+ {
266+ disabled : P `record.current_clean_version_count == 0` ,
267+ disabledReason : 'A current Clean version is required before signing.' ,
268+ } ,
253269) ;
254270
255271/** 生效 — the execution formalities are complete. F9 (`contract_activate`)
@@ -274,7 +290,10 @@ export const ActivateContractAction: Action = transition(
274290 'Bring a signed contract into force. Its activation date is recorded and, if it has an end date, a renewal reminder is set up.' ,
275291 'Contract activated.' ,
276292 [ 'execute_contract' ] ,
277- { disabled : P `record.final_signed_version_count == 0` } ,
293+ {
294+ disabled : P `record.final_signed_version_count == 0` ,
295+ disabledReason : 'A Final Signed version is required before activation.' ,
296+ } ,
278297) ;
279298
280299/**
0 commit comments