LibreSign / LibreSign/libresign
Show signature rejection information on the validation page
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 818
- Forks
- 146
- Avg merge
- 11h 31m
- Merged PRs (30d)
- 326
Description
Update the validation page to support the signature rejection workflow defined by #7832.
Backend rejection support was added by #8159.
This issue is blocked by #8388, which defines the signer visibility and presentation contract used by the validation page.
#8161 is related, but it is not a technical blocker.
Goal
Make the validation page correctly represent:
- visible rejection;
- rejection date and comment when available;
- hidden signer state without discovering which signer rejected;
- canceled workflows;
- workflows that continue after rejection;
- normal files and envelopes.
The backend is the source of truth for visibility.
The frontend must render the response without reconstructing hidden workflow state.
Backend contract
#8388 introduces a separate presentation state:
displayStatus
Possible values are:
draft
ready_to_sign
signed
rejected
not_signed
displayStatus is not a workflow enum.
The real signer workflow state continues to use SignRequestStatus.
Visible rejection
A visible rejected signer may be returned as:
{
"status": 3,
"displayStatus": "rejected",
"rejection": {
"rejectedAt": "2026-09-09T12:00:00+00:00"
}
}
With a visible comment:
{
"status": 3,
"displayStatus": "rejected",
"rejection": {
"rejectedAt": "2026-09-09T12:00:00+00:00",
"comment": "I do not agree with this document",
"commentPrivate": false
}
}
For a privileged viewer, a private comment may also be returned.
Hidden rejection
When a file contains a rejection that the current viewer cannot see, every unsigned signer in that file receives:
{
"displayStatus": "not_signed"
}
This may represent a signer whose real state is:
DRAFT;ABLE_TO_SIGN;REJECTED.
The frontend must not determine which real state is behind not_signed.
Signed signers continue to receive:
displayStatus: "signed"
Status constants
Add the missing real workflow constants:
FILE_STATUS.CANCELED = 6
SIGN_REQUEST_STATUS.REJECTED = 3
These values must stay synchronized with:
lib/Enum/FileStatus.php;lib/Enum/SignRequestStatus.php.
Do not add not_signed to SIGN_REQUEST_STATUS.
It belongs only to the response presentation type.
TypeScript contract
Update:
src/services/validationDocument.ts
and related/generated types to support:
type SignerDisplayStatus =
| 'draft'
| 'ready_to_sign'
| 'signed'
| 'rejected'
| 'not_signed'
The validation parser must accept the displayStatus field and continue validating all other values.
Do not loosen validation to arbitrary strings.
Support:
FileStatus::CANCELED;SignRequestStatus::REJECTEDwhen exposed;- optional
rejection; - the defined
displayStatusvalues.
File status presentation
Update the shared file status helpers so:
FILE_STATUS.CANCELED
renders as:
Canceled
and never as Unknown.
Reuse the existing shared status infrastructure.
Normal file validation
Use the existing components:
src/components/validation/DocumentValidationDetails.vue
src/components/validation/SignerDetails.vue
Do not create a second signer component only for rejection.
Signed
When:
displayStatus === "signed"
keep the existing signed presentation.
Pending
When:
displayStatus === "ready_to_sign"
keep the current pending presentation.
Draft
When:
displayStatus === "draft"
keep the appropriate existing unsigned presentation.
Rejected
When:
displayStatus === "rejected"
show:
Rejected
instead of Not signed yet.
When available:
- show
rejection.rejectedAt; - show
rejection.comment; - indicate a visible private comment when
commentPrivate === true.
Do not show certificate information for rejected signers.
Not signed
When:
displayStatus === "not_signed"
show:
Not signed
using the frontend translation system.
This state is intentionally neutral.
Do not infer whether the signer:
- is pending;
- is draft;
- rejected;
- can sign.
Do not inspect another field to reconstruct the hidden state.
Canceled workflow
When:
document.status === FILE_STATUS.CANCELED
show the workflow as:
Canceled
Every signer that has not signed must be presented consistently as:
No longer able to sign
This applies regardless of whether their displayStatus is:
draft
ready_to_sign
not_signed
rejected
if rejection itself is not visible.
Signed signers keep their signed presentation.
Do not render not_signed differently from another unsigned signer in a canceled workflow, because that could identify the rejecting signer.
Workflow continues after rejection
When the document is not canceled and rejection is visible:
- rejected signer shows
Rejected; - other eligible signers keep their normal presentation;
- workflow remains active.
When rejection is hidden:
- unsigned signers may all have
displayStatus: "not_signed"; - render all of them as
Not signed; - do not try to recover
ready_to_sign; - do not identify the rejecting signer.
This loss of detail is intentional and required for privacy.
Envelope validation
Apply the same presentation rules to envelopes.
EnvelopeValidation.vue has its own signer summary, so updating only SignerDetails.vue is not sufficient.
For envelope validation:
- support
displayStatus; - support visible rejection;
- support
not_signed; - support canceled child files;
- keep signed signer progress working;
- do not identify a hidden rejecting signer through differences between unsigned entries.
Normal files and envelopes must consume the same backend contract.
Privacy boundary
The frontend must not evaluate backend rejection policy.
Do not:
- evaluate
public_status; - evaluate
show_comment_on_validation; - decide whether comments should be visible;
- use
commentPrivateto decide authorization; - derive real
SignRequestStatusfromdisplayStatus; - infer rejection from
not_signed; - infer rejection from
FILE_STATUS.CANCELED; - infer which signer caused cancellation.
If the backend response leaks private information, fix #8388 instead of hiding it only in Vue.
Existing list/sidebar UI
file/list and sidebar components may also receive displayStatus after #8388.
Changing those interfaces is not part of this issue unless the backend contract introduces a regression.
#8162 remains focused on validation-page behavior.
Main implementation areas
Review at least:
src/constants.js;src/utils/fileStatus.js;src/services/validationDocument.ts;- generated/related TypeScript types;
src/components/validation/SignerDetails.vue;src/components/validation/DocumentValidationDetails.vue;src/components/validation/EnvelopeValidation.vue;- related frontend tests.
Tests
This behavior must be covered by frontend automated tests.
Parser
Test that:
- every valid
displayStatusis accepted; - arbitrary
displayStatusstrings are rejected; SIGN_REQUEST_STATUS.REJECTEDis accepted;FILE_STATUS.CANCELEDis accepted;- visible rejection without comment is accepted;
- public comment is accepted;
- private visible comment is accepted;
- malformed rejection data is rejected.
Display status
Test each presentation mapping:
draft -> existing draft/unsigned presentation
ready_to_sign -> existing pending presentation
signed -> existing signed presentation
rejected -> Rejected
not_signed -> Not signed
Rejected signer
Test:
Rejectedlabel;- rejection date;
- visible rejection comment;
- visible private marker;
- no signed certificate presentation.
Hidden rejection
With multiple unsigned signers using:
displayStatus: "not_signed"
test that:
- each receives the same presentation;
- no rejection UI is shown;
- no signer is presented as
Ready to sign; - the frontend does not attempt to identify which signer rejected.
Canceled workflow
Test that:
- workflow shows
Canceled; - every non-signed signer shows
No longer able to sign; draft,ready_to_sign, andnot_signedget the same canceled presentation;- signed signers remain signed;
- no UI difference reveals the rejecting signer.
Continued workflow
Visible rejection:
- rejected signer shows
Rejected; - eligible signers keep normal state;
- workflow remains active.
Hidden rejection:
- all redacted unsigned signers show
Not signed; - no signer is identified as the rejecter;
- frontend does not reconstruct
ready_to_sign.
Envelope
Cover:
- normal signed/pending progress;
- visible rejection;
- hidden rejection;
- multiple
not_signedsigners; - canceled workflow;
- continued workflow;
- visible rejection comment.
Use fixtures generated from the final backend contract implemented by #8388.
Do not reproduce backend policy logic in frontend tests.
Regression
Existing validation must continue to work for:
- unsigned documents without rejection;
- partially signed documents;
- fully signed documents;
- normal files;
- envelopes;
- anonymous validation;
- authenticated validation.
Quality gates
The implementation must pass:
- ESLint;
- type checking;
- frontend tests.
Out of scope
This issue does not implement:
- rejection persistence;
- rejection authorization;
- rejection visibility decisions;
- Policy Workbench configuration;
- requester rejection configuration;
- signer Reject action/dialog;
- general sidebar or file-list redesign.
These are handled by #8159, #8388, #8160, and #8161.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
This work is blocked by #8388; begin with its final backend contract, then review src/constants.js, src/utils/fileStatus.js, src/services/validationDocument.ts, and the validation components, including EnvelopeValidation.vue. Run the related frontend tests and add coverage for parser validation, visible and hidden rejection, canceled workflows, continued workflows, and envelopes. Done means the frontend tests, type checking, and ESLint pass without exposing hidden signer state.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, php, typescript
- Domain
- frontend, testing-qa
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100