diff --git a/doc/en/Proof/Proof_CAS_library.md b/doc/en/Proof/Proof_CAS_library.md
index f9bf0ea0f88dfd90f5a65fb056d07ba828900c8a..ff4b52a6a1c8edf508f5d6567161090994f1138f 100644
--- a/doc/en/Proof/Proof_CAS_library.md
+++ b/doc/en/Proof/Proof_CAS_library.md
@@ -2,6 +2,10 @@
 
 STACK provides libraries to represent and manage lines of a text-based proof in a tree structure.  This page is reference documentation for these CAS libraries.  For examples of how to use these see the topics page on using [Parson's problems](../Topics/Parsons.md).
 
+To use these functions you have to [load an optional library](../Authoring/Inclusions.md) into each question.
+
+E.g. `stack_include_contrib("prooflib.mac")` will include the library published in the master branch on github, which will be at or just ahead of an official release.
+
 ## Proof construction functions, and their manipulation
 
 Proofs are represented as "proof construction functions" with arguments.  For example, an if and only if proof would be represented as `proof_iff(A,B)`, where both `A` and `B` are sub-proofs. Proof construction functions don't typically modify their arguments, but some proof construction functions have simplification properties.  For example `proof_iff(A,B)` is normally equivalent to `proof_iff(B,A)`.
@@ -10,6 +14,7 @@ STACK supports the following types of proof construction functions.  The followi
 
 * `proof()`: general, unspecified proof.
 * `proof_c()`: general proof, with commutative arguments.  Typically each argument will be another proof block type.
+* `proof_opt()`: steps in a proof which are optional.  It assumes a single step.  Wrap each optional step individually.
 
 The following represent particular types of proof.
 
diff --git a/stack/maxima/contrib/prooflib.mac b/stack/maxima/contrib/prooflib.mac
index d3b9ea7e7db49c49f422d094532c1e12e5831a8f..c444458279102978a392b8a6b52e85ac4fa34bb2 100644
--- a/stack/maxima/contrib/prooflib.mac
+++ b/stack/maxima/contrib/prooflib.mac
@@ -41,6 +41,7 @@ tap:proof_display(proof_ans, proof_steps);
 /*                                                                */
 /* proof()       - general, unspecified proof                     */
 /* proof_c()     - general proof, with commutative arguments      */
+/* proof_opt()   - proof_opt()                                    */
 /*                                                                */
 /* proof_iff()   - if any only if                                 */
 /* proof_cases() - proof by exhaustive cases, the first element   */
@@ -53,7 +54,7 @@ tap:proof_display(proof_ans, proof_steps);
 /* Please update Proof/Proof_CAS_library.md with new types.       */
 
 /* Note, "proof" is assumed to come first in this list, as we use "rest" below for other types. */
-proof_types:[proof, proof_c, proof_iff, proof_cases, proof_ind];
+proof_types:[proof, proof_c, proof_opt, proof_iff, proof_cases, proof_ind];
 
 proofp(ex) := block(
   if atom(ex) then true,
@@ -62,10 +63,14 @@ proofp(ex) := block(
 );
 
 s_test_case(proofp(proof(1,2,3)), true);
+s_test_case(proofp(proof_iff(1,2)), true);
 s_test_case(proofp(sin(x)), false);
 
 proof_validatep(ex) := block(
   if atom(ex) then return(true),
+  if op(ex) = proof_opt then
+      if not(is(length(args(ex)) = 1)) then return(false)
+                                       else return(all_listp(proof_validatep, args(ex))),
   if op(ex) = proof_iff then
       if not(is(length(args(ex)) = 2)) then return(false)
                                        else return(all_listp(proof_validatep, args(ex))),
@@ -80,12 +85,15 @@ proof_validatep(ex) := block(
 s_test_case(proof_validatep(proof(1,2,3)), true);
 s_test_case(proof_validatep(proof(1,2,proof(4,5,6))), true);
 s_test_case(proof_validatep(proof(1,2,proof_iff(4,5))), true);
+/* proof_opt must have exactly one sub-proof. */
+s_test_case(proof_validatep(proof(1,2,proof_opt(4,5))), false);
 /* proof_iff must have exactly two sub-proofs. */
 s_test_case(proof_validatep(proof(1,2,proof_iff(4))), false);
 s_test_case(proof_validatep(proof(1,2,proof_iff(4,5,6))), false);
 /* proof_ind must have exactly four sub-proofs. */
 s_test_case(proof_validatep(proof_ind(1,proof(2,3),proof(4,5),6)), true);
 s_test_case(proof_validatep(proof_ind(1,proof(2,3),proof(4,5))), false);
+s_test_case(proof_validatep(proof(1,proof_opt(2),proof_iff(4,5))), true);
 
 /* Is this a type of proof which can reorder its arguments? */
 proof_commutep(ex):=block(
@@ -102,12 +110,14 @@ s_test_case(proof_flatten(proof_iff(proof(A,B),proof(C))), proof(A,B,C));
 s_test_case(proof_flatten(proof_c(proof(A,proof(B,C)),proof(D))), proof(A,B,C,D));
 
 /*
- * Create a normalised proof tree. 
+ * Create a normalised proof tree.
  * To establish equivalence of proof trees we compare the normalised form.
  * This basically sorts and "simplifies" its arguments.
+ * We also remove the proof_opt tag.
 */
 proof_normal(ex) := block(
   if atom(ex) then return(ex),
+  if op(ex) = proof_opt then return(first(args(ex))),
   /* Only sort arguments to types of proof which commute. */
   if proof_commutep(ex) then return(apply(op(ex), sort(map(proof_normal, args(ex))))),
   /* Some proof types have subsets of arguments which commute. */
@@ -120,6 +130,7 @@ s_test_case(proof_normal(proof_c(B,A,D,C)), proof_c(A,B,C,D));
 s_test_case(proof_normal(proof_iff(B,A)), proof_iff(A,B));
 s_test_case(proof_normal(proof_ind(D,C,B,A)), proof_ind(D,B,C,A));
 s_test_case(proof_normal(proof_cases(D,C,B,A)), proof_cases(D,A,B,C));
+s_test_case(proof_normal(proof_iff(proof_c(proof_opt(C),A), B)), proof_iff(proof_c(A,C),B));
 
 /******************************************************************/
 /*                                                                */
@@ -132,13 +143,15 @@ s_test_case(proof_normal(proof_cases(D,C,B,A)), proof_cases(D,A,B,C));
 */
 proof_alternatives(ex):=block([p1,p2],
     p2:proof_one_alternatives(ex),
-    do (p1:p2, p2:proof_one_distrib(p1), if is(p1=p2) then return(proof_ensure_list(p2)))
+    do (p1:p2, p2:proof_one_distrib(p1), if is(p1=p2) then return(map(proof_remove_nullproof, proof_ensure_list(p2))))
 );
 
 proof_one_alternatives(pr) := block(
   if atom(pr) then return(pr),
   if proof_commutep(pr) then return(apply(pf_one, map(lambda([ex], apply(op(pr), map(proof_one_alternatives, ex))), listify(permutations(args(pr)))))),
   /* In a proof by exhaustive cases the first element is fixed. */
+  if op(pr)=proof_opt then return(pf_one(first(pr), nullproof)),
+  /* In a proof by exhaustive cases the first element is fixed. */
   if op(pr)=proof_cases then return(apply(pf_one, map(lambda([ex], apply(op(pr), append([first(args(pr))], map(proof_one_alternatives, ex)))), listify(permutations(rest(args(pr))))))),
   /* In a proof by induction cases the first element and last elents are fixed. */
   if op(pr) = proof_ind then return(apply(pf_one, map(lambda([ex], apply(op(pr), append([first(args(pr))],
@@ -165,14 +178,20 @@ proof_one_distrib(ex):= block([_a,_i,_l],
    apply(pf_one, map(lambda([ex2], block([_aa], _aa:copy(args(ex)), _aa[_i]:_a[ex2], return(apply(op(ex),_aa)))), _l))
 );
 
-proof_ensure_list(ex):= if listp(ex) then return(ex) else return([ex]);
+proof_ensure_list(ex):= if listp(ex) then ex else [ex];
+
+proof_remove_nullproof(ex):= block(
+   if atom(ex) then return(ex),
+   if freeof(nullproof, ex) then return(ex),
+   apply(op(ex), map(proof_remove_nullproof, sublist(args(ex), lambda([ex2], not(is(ex2=nullproof)))))) 
+);
 
 s_test_case(proof_alternatives(proof(A,B,C,D)), [proof(A,B,C,D)]);
 s_test_case(proof_alternatives(proof_c(A,B)), [proof_c(A,B),proof_c(B,A)]);
 s_test_case(proof_alternatives(proof_iff(A,B)), [proof_iff(A,B),proof_iff(B,A)]);
 s_test_case(proof_alternatives(proof_ind(A,B,C,D)), [proof_ind(A,B,C,D),proof_ind(A,C,B,D)]);
 s_test_case(proof_alternatives(proof_cases(A,B,C)), [proof_cases(A,B,C),proof_cases(A,C,B)]);
-
+s_test_case(proof_alternatives(proof_iff(proof(proof_opt(A), B),C)), [proof_iff(proof(A,B),C),proof_iff(proof(B),C),proof_iff(C,proof(A,B)),proof_iff(C,proof(B))]);
 
 /******************************************************************/
 /*                                                                */
@@ -234,6 +253,8 @@ proof_getstep(k, pf) := block([keylist],
 proof_disp_replacesteps(ex, proof_steps) := block(
    if integerp(ex) or stringp(ex) then return(proof_getstep(ex, proof_steps)),
    if atom(ex) then return(sconcat("Error: the following atom does not index a step: ", string(ex))),
+   /* Flatten any optional steps now. */
+   if is(op(ex)=proof_opt) then return(proof_disp_replacesteps(first(args(ex)), proof_steps)),
    apply(op(ex), map(lambda([ex2], proof_disp_replacesteps(ex2, proof_steps)), args(ex)))
 );
 
@@ -299,6 +320,8 @@ proof_line_html(pfs) := block([st],
 proof_disp_replacesteps_html(ex, proof_steps) := block(
    if integerp(ex) or stringp(ex) then return(proof_getstep_html(ex, proof_steps)),
    if atom(ex) then return(sconcat("Error: the following atom does not index a step: ", string(ex))),
+   /* Flatten any optional steps now. */
+   if is(op(ex)=proof_opt) then return(proof_disp_replacesteps(first(args(ex)), proof_steps)),
    apply(op(ex), map(lambda([ex2], proof_disp_replacesteps_html(ex2, proof_steps)), args(ex)))
 );