219 lines
11 KiB
Markdown
219 lines
11 KiB
Markdown
# Conditional Logic: Gap Analysis & Design
|
|
|
|
This document captures the structural differences between Adobe Sign's and DocuSign's
|
|
conditional field models, which gaps are solvable in the migrator, and the design of
|
|
proposed solutions. It complements `field-mapping.md` (current-state reference) and
|
|
`IMPLEMENTATION-PLAN.md` (feature history).
|
|
|
|
---
|
|
|
|
## Background: The Two Models
|
|
|
|
**Adobe Sign** supports full conditional logic per field:
|
|
- `conditionalAction.predicates[]` — one or more `{fieldName, operator, value}` tuples
|
|
- `anyOrAll` — ANY (OR) or ALL (AND) combining semantics
|
|
- `action` — SHOW or HIDE the field
|
|
- `operator` — EQUALS, NOT_EQUALS, GT, LT, CONTAINS, etc.
|
|
- The trigger field can belong to any recipient; cross-recipient conditions are valid
|
|
|
|
**DocuSign** supports a single, simple reveal condition per tab:
|
|
- `conditionalParentLabel` — the `tabLabel` or `groupName` of the trigger tab
|
|
- `conditionalParentValue` — the exact string value that reveals the tab
|
|
- The trigger tab must be a `listTab`, `radioGroupTab`, or `checkboxTab`
|
|
- The trigger and the dependent tab must belong to the **same recipient**
|
|
- Only one condition per tab, always EQUALS, always a SHOW (reveal)
|
|
|
|
This is the deepest structural asymmetry in the migration. Every gap below flows from
|
|
the gap between these two models.
|
|
|
|
---
|
|
|
|
## Gap Catalog
|
|
|
|
### Gap 1 — HIDE action
|
|
**Adobe**: `action: HIDE` — field is visible by default and hides when the condition is met.
|
|
**DocuSign**: No equivalent. DocuSign only supports revealing a tab that starts hidden.
|
|
**Current behavior**: Condition dropped; field always visible. `HIDE_ACTION` issue emitted.
|
|
|
|
### Gap 2 — NOT_EQUALS and other operators
|
|
**Adobe**: Supports NOT_EQUALS, GT, LT, CONTAINS, etc.
|
|
**DocuSign**: Only EQUALS is supported.
|
|
**Current behavior**: Condition dropped; field always visible. `UNSUPPORTED_OPERATOR` issue emitted.
|
|
|
|
The most common real-world case is `NOT_EQUALS ""` (show this field if the user
|
|
entered *anything* in another field). This is doubly broken in DocuSign:
|
|
1. NOT_EQUALS has no equivalent
|
|
2. Text fields can't be conditional parents at all (see Gap 5)
|
|
|
|
### Gap 3 — Cross-recipient conditionals
|
|
**Adobe**: Field B can show/hide based on the value of Field A even if A and B
|
|
belong to different recipients.
|
|
**DocuSign**: `conditionalParentLabel` resolves only within the same recipient's tab
|
|
set. The API silently ignores or rejects cross-recipient references.
|
|
**Current behavior**: Condition dropped. `CROSS_RECIPIENT_CONDITIONAL` issue emitted.
|
|
|
|
### Gap 4 — Multi-predicate ANY (OR) logic
|
|
**Adobe**: `anyOrAll: ANY` with multiple predicates — show if condition 1 OR condition 2 OR …
|
|
**DocuSign**: One `conditionalParentValue` per tab — no native OR.
|
|
**Current behavior**: Only the first EQUALS predicate is mapped; rest are dropped.
|
|
`MULTI_PREDICATE` issue emitted.
|
|
|
|
### Gap 5 — Invalid conditional parent tab types
|
|
**Adobe**: Any field can be a conditional trigger — text fields, signature fields, anything.
|
|
**DocuSign**: Only `listTabs`, `radioGroupTabs`, and `checkboxTabs` may be conditional
|
|
parents. All other types cause `CONDITIONALTAB_HAS_INVALID_PARENT` (400).
|
|
**Current behavior**: Strip pass removes conditions referencing invalid parent types.
|
|
`INVALID_PARENT_TAB` issue emitted.
|
|
|
|
### Gap 6 — Multi-predicate ALL (AND) logic
|
|
**Adobe**: `anyOrAll: ALL` — show only if ALL conditions are simultaneously true.
|
|
**DocuSign**: No AND logic at the template conditional level.
|
|
**Current behavior**: Only the first EQUALS predicate is mapped. `MULTI_PREDICATE` issue emitted.
|
|
|
|
---
|
|
|
|
## The DocuSign "Flags" Technique
|
|
|
|
DocuSign's "Building Advanced Templates" guide describes using **hidden intermediate
|
|
tabs** ("flags") to work around the platform's single-parent conditional model. The
|
|
pattern:
|
|
|
|
1. Create a hidden `checkboxTab` or `listTab` — the "flag" — placed out of the
|
|
visible form area or given `locked: true, selected: false`.
|
|
2. Make the flag conditionally revealed by one condition (the flag itself is shown
|
|
when condition A is true).
|
|
3. Make the dependent field conditionally revealed when the flag is in a specific state.
|
|
|
|
This creates two-level conditional depth. However, DocuSign tabs don't auto-set their
|
|
own value — a checkbox flag only "activates" when the signer checks it, or when the
|
|
sender pre-fills it before sending. This limits the technique to two scenarios:
|
|
|
|
**Scenario A — Sender-prefill flags**: The sender manually checks/sets flags before
|
|
routing the envelope, making explicit business logic decisions that the template cannot
|
|
express automatically. Useful for bespoke workflows but not for automated migration.
|
|
|
|
**Scenario B — Checkbox-group section selectors**: For OR-style logic driven by a
|
|
single dropdown, radio group, or checkbox, the flag technique is not needed — the
|
|
multi-copy approach (Gap 4 solution below) handles this cleanly.
|
|
|
|
**Bottom line**: The flag technique is a UI/manual-workflow tool, not an automated
|
|
migration output. It doesn't solve AND logic or cross-recipient conditions without
|
|
human intervention at sending time.
|
|
|
|
---
|
|
|
|
## Solvable Gaps: Proposed Designs
|
|
|
|
### Solution A — ANY multi-predicate fan-out (addresses Gap 4, partially Gap 2)
|
|
|
|
**Scope**: When `anyOrAll: ANY` and all predicates share the same parent field and all
|
|
use `EQUALS`, the OR logic can be emitted as **one copy of the dependent tab per
|
|
predicate value**, all at the same coordinates with the same `tabLabel`.
|
|
|
|
At signing time, DocuSign shows exactly the copies whose condition is satisfied.
|
|
Because the copies share a `tabLabel`, their values sync — the signer fills one and
|
|
DocuSign treats them as the same logical field.
|
|
|
|
**Before** (current output — only first predicate mapped):
|
|
```json
|
|
{ "tabLabel": "Notes", "conditionalParentLabel": "Category",
|
|
"conditionalParentValue": "Option A" }
|
|
```
|
|
|
|
**After** (fan-out):
|
|
```json
|
|
{ "tabLabel": "Notes", "conditionalParentLabel": "Category",
|
|
"conditionalParentValue": "Option A" },
|
|
{ "tabLabel": "Notes", "conditionalParentLabel": "Category",
|
|
"conditionalParentValue": "Option B" }
|
|
```
|
|
|
|
**NOT_EQUALS expansion** (Gap 2, dropdown parents only): When the operator is
|
|
NOT_EQUALS and the parent is a `listTab`, look up the parent field's option list and
|
|
compute the complement — emit one copy per remaining value. This covers the common
|
|
"show unless user picked X" pattern.
|
|
|
|
**Limitations**:
|
|
- Only works when all predicates reference the **same parent field**
|
|
- Mixed-parent ANY (field A = x OR field B = y) is still unsupported
|
|
- NOT_EQUALS expansion only works for `listTab` parents with a finite option set
|
|
- Text field parents remain invalid regardless of operator (Gap 5)
|
|
|
|
**New issue codes proposed**:
|
|
- `MULTI_PREDICATE_EXPANDED` (info) — OR logic successfully fanned out
|
|
- `NOT_EQUALS_EXPANDED` (info) — NOT_EQUALS resolved via complement enumeration
|
|
- `NOT_EQUALS_UNEXPANDABLE` (warning) — NOT_EQUALS on a non-list parent; condition dropped
|
|
|
|
---
|
|
|
|
### Solution B — Richer guidance messages for unsolvable gaps
|
|
|
|
All currently unsolvable gaps emit a generic "condition dropped" message. Each gap
|
|
warrants a specific, actionable message explaining the constraint and what to do
|
|
manually.
|
|
|
|
| Gap | Improved guidance |
|
|
|-----|------------------|
|
|
| `HIDE_ACTION` | "Restructure as a SHOW condition: start the field hidden (remove it from the base layout) and show it when the inverse condition is met. If the trigger is a dropdown, this requires listing all values that should reveal the field." |
|
|
| `CROSS_RECIPIENT_CONDITIONAL` | "Add a prefill recipient (routing order 0) that holds the controlling field. Downstream signers' conditional fields can then reference the prefill tab. The sender sets the value before routing." |
|
|
| `INVALID_PARENT_TAB` (text parent) | "DocuSign requires a dropdown, radio group, or checkbox as the condition trigger. Replace the text field trigger with a dropdown containing expected values, then the condition can be mapped." |
|
|
| `MULTI_PREDICATE` (mixed parents) | "DocuSign supports one condition per field. Split this field into two separate positioned fields, each conditional on one predicate." |
|
|
|
|
---
|
|
|
|
## Unsolvable Gaps (Fundamental Limits of DocuSign Templates)
|
|
|
|
These gaps cannot be addressed at the template layer regardless of technique:
|
|
|
|
| Gap | Why |
|
|
|-----|-----|
|
|
| HIDE action (general) | DocuSign has no "start visible, hide when X" model. Inversion via complement enumeration only works for dropdown triggers with finite option sets. |
|
|
| Text field as conditional parent | DocuSign's API flatly rejects any tab type other than `listTabs`, `radioGroupTabs`, `checkboxTabs` as a parent — this is a server-side constraint, not a schema quirk. |
|
|
| AND logic (multi-predicate ALL) | DocuSign template conditionals have no AND. The flag chaining technique requires a human to set intermediate flags at sending time, making it unsuitable for automated migration output. |
|
|
| Mixed-parent ANY | "Show if field A = x OR field B = y" requires two independent conditions on the same tab, which the single-parent model cannot express. |
|
|
| Cross-recipient without prefill restructure | True cross-recipient logic (signer 2's visibility driven by signer 1's choice) requires restructuring the recipient list to add a prefill signer — a structural change, not a tab-level fix. |
|
|
|
|
**The right platform for these cases is DocuSign Maestro (Workflow automation)**, which
|
|
supports proper conditional branching, cross-step data references, and decision gateways.
|
|
Templates with significant AND logic or cross-recipient conditionals should be flagged as
|
|
Maestro candidates in the migration report.
|
|
|
|
---
|
|
|
|
## Proposed Implementation Phases
|
|
|
|
### Phase 24 — NOT_EQUALS expansion for dropdown parents
|
|
- Detect `operator: NOT_EQUALS` where parent field is a `listTab`
|
|
- Look up parent's `hiddenOptions`/`visibleOptions` from the Adobe source fields
|
|
- Compute complement set (all options except the excluded value)
|
|
- Emit one tab copy per remaining option with `conditionalParentValue = option`
|
|
- Emit `NOT_EQUALS_EXPANDED` info issue
|
|
- Emit `NOT_EQUALS_UNEXPANDABLE` warning for non-list parents (unchanged drop behavior)
|
|
- Tests: unit tests for complement computation; regression snapshots updated
|
|
|
|
### Phase 25 — ANY multi-predicate fan-out
|
|
- Detect `anyOrAll: ANY`, multiple predicates, all referencing the **same parent field**,
|
|
all using `EQUALS`
|
|
- Emit one tab copy per predicate value at the same coordinates with the same `tabLabel`
|
|
- Emit `MULTI_PREDICATE_EXPANDED` info issue
|
|
- Cases with mixed parents or non-EQUALS operators keep `MULTI_PREDICATE` warning
|
|
- Tests: unit tests covering fan-out; mixed-parent case stays as warning
|
|
|
|
### Phase 26 — Richer issue guidance text
|
|
- Update `HIDE_ACTION`, `CROSS_RECIPIENT_CONDITIONAL`, `INVALID_PARENT_TAB`,
|
|
and `MULTI_PREDICATE` message strings to include specific manual fix instructions
|
|
- No behavioral change — UI display only
|
|
- Tests: update any tests that assert on exact message strings
|
|
|
|
### Phase 27 — Maestro escalation advisory
|
|
- Add template-level `MAESTRO_RECOMMENDED` advisory (distinct from per-field issues) when
|
|
a template contains unresolvable AND logic, cross-recipient conditionals, or HIDE actions
|
|
that affect more than N fields (threshold TBD)
|
|
- Surface in the Issues tab of the template detail view
|
|
- No code generation — guidance only
|
|
|
|
---
|
|
|
|
*Created: 2026-05-14. Companion documents: `field-mapping.md` (current-state reference),
|
|
`docs/IMPLEMENTATION-PLAN.md` (feature history), `tests/EDGE-CASES.md` (known edge cases).*
|