diff --git a/ivette/src/dome/misc/format.ts b/ivette/src/dome/misc/format.ts
new file mode 100644
index 0000000000000000000000000000000000000000..feee25f0517b547b1f3834cd771037521594f1c4
--- /dev/null
+++ b/ivette/src/dome/misc/format.ts
@@ -0,0 +1,49 @@
+/* ************************************************************************ */
+/*                                                                          */
+/*   This file is part of Frama-C.                                          */
+/*                                                                          */
+/*   Copyright (C) 2007-2023                                                */
+/*     CEA (Commissariat à l'énergie atomique et aux énergies               */
+/*          alternatives)                                                   */
+/*                                                                          */
+/*   you can redistribute it and/or modify it under the terms of the GNU    */
+/*   Lesser General Public License as published by the Free Software        */
+/*   Foundation, version 2.1.                                               */
+/*                                                                          */
+/*   It is distributed in the hope that it will be useful,                  */
+/*   but WITHOUT ANY WARRANTY; without even the implied warranty of         */
+/*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          */
+/*   GNU Lesser General Public License for more details.                    */
+/*                                                                          */
+/*   See the GNU Lesser General Public License version 2.1                  */
+/*   for more details (enclosed in the file licenses/LGPLv2.1).             */
+/*                                                                          */
+/* ************************************************************************ */
+
+// --------------------------------------------------------------------------
+// --- Formatting Utilities
+// --------------------------------------------------------------------------
+
+/**
+   @packageDocumentation
+   @module dome/misc/format
+ */
+
+/**
+   Formats a duration, specified in seconds, into hour, minutes, seconds,
+   milliseconds or nanoseconds, depending on range.
+
+   For instance, returns `'250ms'` for an input time of `.25`.
+ */
+export function duration(time : number) : string {
+  if (time < 1.0e-3) return `${Math.round(time * 1.0e6)}µs`;
+  if (time < 1.0) return `${Math.round(time * 1.0e3)}ms`;
+  if (time < 60) return `${Math.round(time)}s`;
+  if (time < 3600) return `${Math.round(time / 60)}m`;
+  const h = Math.round(time / 3600);
+  const r = time - h * 3600;
+  const m = Math.round(r / 60);
+  return `${h}h ${m}m`;
+}
+
+// --------------------------------------------------------------------------
diff --git a/ivette/src/dome/misc/system.ts b/ivette/src/dome/misc/system.ts
index d3c6f8e5a7f6c01cfae4946ba67ec25d644d47d4..eb8b3ab6d98eae128290d02666fd97d5953b766e 100644
--- a/ivette/src/dome/misc/system.ts
+++ b/ivette/src/dome/misc/system.ts
@@ -36,6 +36,7 @@ import Emitter from 'events';
 import Exec from 'child_process';
 import fspath from 'path';
 import fs from 'fs';
+import { clipboard } from 'electron';
 
 // --------------------------------------------------------------------------
 // --- Logging
@@ -618,6 +619,26 @@ export function spawn(
   });
 }
 
+// --------------------------------------------------------------------------
+// --- ClipBoard
+// --------------------------------------------------------------------------
+
+/**
+   Get plain text from system clipboard.
+*/
+export function readClipboardText(): string
+{
+  return clipboard.readText();
+}
+
+/**
+   Copy plain text to system clipboard.
+*/
+export function writeClipboardText(text: string): void
+{
+  clipboard.writeText(text);
+}
+
 // --------------------------------------------------------------------------
 // --- Window Management
 // --------------------------------------------------------------------------
diff --git a/ivette/src/dome/renderer/controls/buttons.tsx b/ivette/src/dome/renderer/controls/buttons.tsx
index 15314c84a7eca2e4506515234ac7a68a5a92b751..3120b6ff51ccae5bf60c165f3075267a45cffccb 100644
--- a/ivette/src/dome/renderer/controls/buttons.tsx
+++ b/ivette/src/dome/renderer/controls/buttons.tsx
@@ -475,6 +475,8 @@ export interface SelectProps {
   disabled?: boolean;
   /** Currently selected value. */
   value?: string;
+  /** Keep focus when selected (default to false). */
+  focus?: boolean;
   /** Callback to selected values. */
   onChange?: (newValue?: string) => void;
   /** Default selected value. */
@@ -497,7 +499,7 @@ export interface SelectProps {
 
  */
 export function Select(props: SelectProps): JSX.Element {
-  const { onChange, placeholder } = props;
+  const { onChange, placeholder, focus=false } = props;
   const className = classes(
     'dome-xSelect dome-xBoxButton dome-xButton-default dome-xButton-label',
     props.className,
@@ -505,6 +507,8 @@ export function Select(props: SelectProps): JSX.Element {
   const disabled = onChange ? DISABLED(props) : true;
   const callback = (evt: React.ChangeEvent<HTMLSelectElement>): void => {
     if (onChange) onChange(evt.target.value);
+    const target = evt.target;
+    if (!focus) setImmediate(() => target.blur());
   };
   return (
     <select
@@ -522,6 +526,64 @@ export function Select(props: SelectProps): JSX.Element {
   );
 }
 
+// --------------------------------------------------------------------------
+// --- Spinner
+// --------------------------------------------------------------------------
+
+export interface SpinnerProps {
+  /** Field identifier (to make forms or labels) */
+  id?: string;
+  /** Button tooltip */
+  title?: string;
+  /** Button placeholder */
+  placeholder?: string;
+  /** Defaults to `true`. */
+  enabled?: boolean;
+  /** Defaults to `false`. */
+  disabled?: boolean;
+  /** Currently selected value. */
+  value?: number;
+  /** Minimum value. */
+  vmin?: number;
+  /** Maximum value. */
+  vmax?: number;
+  /** Increment and Decrement step. */
+  vstep?: number;
+  /** Callback to selected values. */
+  onChange?: (newValue: number) => void;
+  /** Default selected value. */
+  className?: string;
+  /** Additional style for the `< dov /> ` container of Raiods */
+  style?: React.CSSProperties;
+}
+
+export function Spinner(props: SpinnerProps): JSX.Element {
+  const { onChange } = props;
+  const className = classes( 'dome-xSpinner', props.className );
+  const disabled = onChange ? DISABLED(props) : true;
+  const callback = (evt: React.ChangeEvent<HTMLInputElement>): void => {
+    if (onChange) {
+      const newValue = Number.parseInt(evt.target.value);
+      if (!Number.isNaN(newValue)) onChange(newValue);
+    }
+  };
+  return (
+    <input
+      id={props.id}
+      type="number"
+      value={props.value}
+      min={props.vmin}
+      max={props.vmax}
+      step={props.vstep}
+      className={className}
+      style={props.style}
+      disabled={disabled}
+      placeholder={props.placeholder}
+      onChange={callback}
+    />
+  );
+}
+
 // --------------------------------------------------------------------------
 // --- Text Input
 // --------------------------------------------------------------------------
diff --git a/ivette/src/dome/renderer/controls/gallery.json b/ivette/src/dome/renderer/controls/gallery.json
index efae6ebd9938395b4f9028873133d3e3c4689b80..abba175abcc93fa95f87b43eb17ac615db306378 100644
--- a/ivette/src/dome/renderer/controls/gallery.json
+++ b/ivette/src/dome/renderer/controls/gallery.json
@@ -175,6 +175,12 @@
     "viewBox": "0 0 26 24",
     "path": "M23.853 12.469q0-0.469-0.71-0.469h-14.571q-0.536 0-1.145 0.288t-0.958 0.703l-3.938 4.862q-0.241 0.321-0.241 0.536 0 0.469 0.71 0.469h14.571q0.536 0 1.152-0.295t0.951-0.71l3.938-4.862q0.241-0.295 0.241-0.522zM8.571 10.286h10.286v-2.143q0-0.536-0.375-0.911t-0.911-0.375h-7.714q-0.536 0-0.911-0.375t-0.375-0.911v-0.857q0-0.536-0.375-0.911t-0.911-0.375h-4.286q-0.536 0-0.911 0.375t-0.375 0.911v11.424l3.429-4.219q0.589-0.71 1.554-1.172t1.875-0.462zM25.567 12.469q0 0.83-0.616 1.607l-3.951 4.862q-0.576 0.71-1.554 1.172t-1.875 0.462h-14.571q-1.232 0-2.116-0.884t-0.884-2.116v-12.857q0-1.232 0.884-2.116t2.116-0.884h4.286q1.232 0 2.116 0.884t0.884 2.116v0.429h7.286q1.232 0 2.116 0.884t0.884 2.116v2.143h2.571q0.723 0 1.326 0.328t0.897 0.944q0.201 0.429 0.201 0.911z"
   },
+  "EJECT": {
+    "section": "Desktop",
+    "title": "Eject Device",
+    "viewBox": "0 0 32 32",
+    "path": "M0 24h32v4h-32zM16 4l16 16h-32z"
+  },
   "DISPLAY": {
     "section": "Desktop",
     "viewBox": "0 0 24 24",
diff --git a/ivette/src/dome/renderer/controls/icons.tsx b/ivette/src/dome/renderer/controls/icons.tsx
index 2821f316da391314a136d2d4f213932c5b99bc72..158adb3266357546e38bcfdf488f7ac158e1443e 100644
--- a/ivette/src/dome/renderer/controls/icons.tsx
+++ b/ivette/src/dome/renderer/controls/icons.tsx
@@ -81,7 +81,6 @@ export function SVG(props: SVGprops): null | JSX.Element {
   return (
     <svg
       height={size}
-      width={size}
       style={{ bottom: offset }}
       viewBox={viewBox}
       className={className}
@@ -97,7 +96,7 @@ export function SVG(props: SVGprops): null | JSX.Element {
 // --------------------------------------------------------------------------
 
 export type IconKind =
-  'disabled' | 'selected' | 'positive' | 'negative' | 'warning';
+  'disabled' | 'selected' | 'positive' | 'negative' | 'warning' | 'default';
 
 /** Icon Component Properties */
 export interface IconProps extends SVGprops {
@@ -123,15 +122,14 @@ export interface IconProps extends SVGprops {
  */
 export function Icon(props: IconProps): JSX.Element {
   const {
-    id, title, onClick, fill, kind,
+    id, title, onClick, fill, kind='default',
     size, className, offset, style,
     visible = true, display = true,
   } = props;
   const divClass = classes(
-    'dome-xIcon',
+    'dome-xIcon', `dome-xIcon-${kind}`,
     !visible && 'dome-control-hidden',
     !display && 'dome-control-erased',
-    kind && ('dome-xIcon-' + kind),
     className
   );
   const divStyle = styles(fill && { fill }, style);
diff --git a/ivette/src/dome/renderer/controls/labels.tsx b/ivette/src/dome/renderer/controls/labels.tsx
index f098e54ab8b2e4f8977b2332114875d273a8894c..7eefc1f4a9fe635b38d8ca32f4c1093a32f38209 100644
--- a/ivette/src/dome/renderer/controls/labels.tsx
+++ b/ivette/src/dome/renderer/controls/labels.tsx
@@ -115,6 +115,7 @@ const DESCR = 'dome-xLabel dome-text-descr';
 const TDATA = 'dome-xLabel dome-text-data';
 const TCODE = 'dome-xLabel dome-text-code';
 const TCELL = 'dome-xLabel dome-text-cell';
+const TITEM = 'dome-xLabel dome-text-item';
 
 // --------------------------------------------------------------------------
 // --- Components
@@ -138,4 +139,7 @@ export const Code = React.forwardRef(makeLabel(TCODE));
 /** Selectable inlined source-code content with default cursor. */
 export const Cell = React.forwardRef(makeLabel(TCELL));
 
+/** Non-selectable inlined source-code content with default cursor. */
+export const Item = React.forwardRef(makeLabel(TITEM));
+
 // --------------------------------------------------------------------------
diff --git a/ivette/src/dome/renderer/controls/style.css b/ivette/src/dome/renderer/controls/style.css
index 4962ceb30e4ac250abcc607f2d1214b6ecadb419..b3c4e6c2e57b450dba94f73a3b92bd4bddfd9a3b 100644
--- a/ivette/src/dome/renderer/controls/style.css
+++ b/ivette/src/dome/renderer/controls/style.css
@@ -10,8 +10,7 @@
 }
 
 .dome-xLabel.dome-text-descr {
-    overflow: auto ;
-    white-space: wrap ;
+    white-space: normal ;
 }
 
 /* -------------------------------------------------------------------------- */
@@ -29,12 +28,12 @@
     position:relative ;
 }
 
-.dome-xIcon-default { fill: var(--var-info-text-discrete) ; }
+.dome-xIcon-default { fill: var(--text-discrete) ; }
 .dome-xIcon-disabled { fill: var(--background-intense) ; }
 .dome-xIcon-selected { fill: var(--activated-button-color) ; }
 .dome-xIcon-positive { fill: var(--positive-button-color) ; }
 .dome-xIcon-negative { fill: var(--negative-button-color) ; }
-.dome-xIcon-warning { fill: var(--warning-button-color) ; }
+.dome-xIcon-warning { fill: var(--warning-button-hover) ; }
 
 .dome-xBadge {
     flex: 0 0 auto ;
@@ -415,6 +414,16 @@ meter::-webkit-meter-even-less-good-value  {
     width: max-content ;
 }
 
+/* -------------------------------------------------------------------------- */
+/* --- Styling Spinner                                                    --- */
+/* -------------------------------------------------------------------------- */
+
+.dome-xSpinner {
+    text-align: right ;
+    padding-left:  12px ;
+    width: 80px ;
+}
+
 /* -------------------------------------------------------------------------- */
 /* --- Styling Field                                                     --- */
 /* -------------------------------------------------------------------------- */
diff --git a/ivette/src/dome/renderer/dark.css b/ivette/src/dome/renderer/dark.css
index b58523a06c79376636c1d4d56faa593f08686e7d..48b30c7062056c07916cccb823805f7eec88da50 100644
--- a/ivette/src/dome/renderer/dark.css
+++ b/ivette/src/dome/renderer/dark.css
@@ -56,7 +56,7 @@
     --positive-button-hover: #28661a;
     --positive-button-active: #226014;
 
-    --negative-button-color: #bc150e;
+    --negative-button-color: #a10f0f;
     --negative-led-img: radial-gradient(circle at center, #df3b36, #c72a25c7);
     --negative-button-img: linear-gradient(to bottom, #bc150e 0%, #cf1c17 100%);
     --negative-button-hover: #b60f08;
diff --git a/ivette/src/dome/renderer/dome.tsx b/ivette/src/dome/renderer/dome.tsx
index 789b04d5cd2d432f0fe01bc19314b9515fc57a18..27e7814d7493fb54ed50a164f7b825cd206b8c82 100644
--- a/ivette/src/dome/renderer/dome.tsx
+++ b/ivette/src/dome/renderer/dome.tsx
@@ -784,51 +784,71 @@ export function useTimer(period: number, callback: () => void): void
   }, [period, callback]);
 }
 
-/** Protected callback against unmounted component.
+type Callback<A> = (arg: A) => void;
 
-   The returned callback will not fired only when the component is mounted.
-
-   Unless constant, first create the callback with `React.useCallback()`, then
-   give it to `useActive()`.
+/**
+   Protected callback against unmounted component.
+   The returned callback will be only fired when the component is mounted.
+   The provided callback need not be memoized.
  */
-export function useActive<A>(
-  callback: (arg: A) => void,
-): (arg:A) => void
+export function useProtected<A>(fn: Callback<A> | undefined): Callback<A>
 {
-  const active = React.useRef(false);
+  const cb = React.useRef<Callback<A>>();
   React.useEffect(() => {
-    active.current = true;
-    return () => { active.current = false; };
+    cb.current = fn;
+    return () => { cb.current = undefined; };
+  }, [fn]);
+  const trigger = React.useCallback((arg: A) => {
+    const fn = cb.current;
+    if (fn) fn(arg);
   }, []);
-  return React.useCallback((arg: A) => {
-    if (active.current) callback(arg);
-  }, [callback]);
+  return trigger;
 }
 
-/** Debounced callback (period in milliseconds).
-
-   The debounceded callback will not be fired when the component is unmounted.
-
-   Unless constant, first create the callback with `React.useCallback()`, then
-   give it to `useDebounced()`.
+/**
+   Debounced callback (waiting time in milliseconds).
+   The returned callback will be only fired when the component is mounted.
+   The provided callback need not be memoized.
  */
 export function useDebounced<A=void>(
-  callback: (arg: A) => void,
-  period: number,
-): (arg:A) => void
+  fn: Callback<A> | undefined,
+  delay: number
+): Callback<A>
 {
-  const active = React.useRef(false);
+  const cb = React.useRef<Callback<A>>();
   React.useEffect(() => {
-    active.current = true;
-    return () => { active.current = false; };
-  }, []);
-  return React.useMemo(() =>
-    _.debounce(
-      (arg: A) => {
-        if (active.current) callback(arg);
-      }, period
-    ), [callback, period]
-  );
+    cb.current = fn;
+    return () => { cb.current = undefined; };
+  }, [fn]);
+  const trigger = React.useMemo(
+    () => _.debounce((arg: A) => {
+      const fn = cb.current;
+      if (fn) fn(arg);
+    }, delay), [delay]);
+  return trigger;
+}
+
+/**
+   Throttled callback (waiting time in milliseconds).
+   The returned callback will be only fired when the component is mounted.
+   The provided callback need not be memoized.
+ */
+export function useThrottled<A=void>(
+  fn: Callback<A> | undefined,
+  period: number
+): Callback<A>
+{
+  const cb = React.useRef<Callback<A>>();
+  React.useEffect(() => {
+    cb.current = fn;
+    return () => { cb.current = undefined; };
+  }, [fn]);
+  const trigger = React.useMemo(
+    () => _.throttle((arg: A) => {
+      const fn = cb.current;
+      if (fn) fn(arg);
+    }, period), [period]);
+  return trigger;
 }
 
 // --------------------------------------------------------------------------
diff --git a/ivette/src/dome/renderer/frame/style.css b/ivette/src/dome/renderer/frame/style.css
index 0f744024de94cbe6fae015aa249bd0959828b03f..43dd9b6fd4a060fbc5e2a4cbf1689456cd26978e 100644
--- a/ivette/src/dome/renderer/frame/style.css
+++ b/ivette/src/dome/renderer/frame/style.css
@@ -107,6 +107,7 @@
 
 .dome-xSideBarSection-title {
     position: sticky ;
+    z-index: +1;
     top: 0px ;
     padding-top: 0px;
     padding-bottom: 0px ;
diff --git a/ivette/src/dome/renderer/frame/toolbars.tsx b/ivette/src/dome/renderer/frame/toolbars.tsx
index a824168884bdd63fd4ed9d38fa53650c132b3060..51f7355c2b27223f1aaed805f0e0ae4031d1526a 100644
--- a/ivette/src/dome/renderer/frame/toolbars.tsx
+++ b/ivette/src/dome/renderer/frame/toolbars.tsx
@@ -161,6 +161,8 @@ export interface ButtonProps<A> {
   selection?: A;
   /** Selection callback. Receives the button's value. */
   onClick?: (value: A | undefined) => void;
+  /** Right-Click callback. Receives the button's value. */
+  onContextMenu?: (value: A | undefined) => void;
   /** Button contents */
   children?: React.ReactNode;
 }
@@ -172,7 +174,7 @@ export function Button<A = undefined>(
   const { visible = true, hidden = false } = props;
   if (!visible || hidden) return null;
   const { enabled = true, disabled = false } = props;
-  const { selected, value, selection, onClick } = props;
+  const { selected, value, selection, onClick, onContextMenu } = props;
   const isSelected = selected !== undefined
     ? selected
     : (value !== undefined && value === selection);
@@ -182,6 +184,7 @@ export function Button<A = undefined>(
       disabled={disabled || !enabled}
       className={isSelected ? SELECT : (BUTTON + KIND(props.kind))}
       onClick={onClick && (() => onClick(value))}
+      onContextMenu={onContextMenu && (() => onContextMenu(value))}
       title={props.title}
     >
       {props.icon && <SVG id={props.icon} />}
@@ -280,6 +283,12 @@ export function ButtonGroup<A>(props: ButtonGroupProps<A>): JSX.Element {
 // --- ToolBar Menu
 // --------------------------------------------------------------------------
 
+export interface SelectProps extends SelectionProps<string>
+{
+  className?: string;
+  style?: React.CSSProperties;
+}
+
 /** Toolbar Selector Menu.
 
    Behaves likes a standard `<select>` element, except that callback directly
@@ -287,15 +296,20 @@ export function ButtonGroup<A>(props: ButtonGroupProps<A>): JSX.Element {
    The list of options shall be given with standard
    `<option value={...} label={...}>` elements.
  */
-export function Select(props: SelectionProps<string>): JSX.Element {
+export function Select(props: SelectProps): JSX.Element {
   const { enabled = true, disabled = false, onChange } = props;
   const callback =
     (evt: React.ChangeEvent<HTMLSelectElement>): void => {
       if (onChange) onChange(evt.target.value);
     };
+  const className = classes(
+    'dome-xToolBar-control dome-color-frame',
+    props.className
+  );
   return (
     <select
-      className="dome-xToolBar-control dome-color-frame"
+      className={className}
+      style={props.style}
       title={props.title}
       value={props.value}
       disabled={disabled || !enabled}
diff --git a/ivette/src/dome/renderer/layout/boxes.tsx b/ivette/src/dome/renderer/layout/boxes.tsx
index 5606a8d469736c6ec1b449e95604ddede8376a80..b32ab949deea22e7537d2e8aab5aed25de915b5d 100644
--- a/ivette/src/dome/renderer/layout/boxes.tsx
+++ b/ivette/src/dome/renderer/layout/boxes.tsx
@@ -235,4 +235,34 @@ export const Folder = (props: FolderProps): JSX.Element => {
   );
 };
 
-// --------------------------------------------------------------------------
+/* -------------------------------------------------------------------------- */
+/* --- Overlay Boxes                                                      --- */
+/* -------------------------------------------------------------------------- */
+
+export interface OverlayProps {
+  /** Overlay displayed (default is false). */
+  display?: boolean;
+  /** Class of the overlay contents. */
+  className?: string;
+  /** Additional style of the overlay contents. */
+  style?: React.CSSProperties;
+  /** Contents of the overlay. */
+  children?: React.ReactNode;
+}
+
+export function Overlay(props: OverlayProps): JSX.Element {
+  const {
+    className, display=false, style, children,
+  } = props;
+  const anchor = classes('dome-xOverlay-anchor', !display && 'dome-erased');
+  const contents = classes('dome-xOverlay-contents', className);
+  return (
+    <div className={anchor}>
+      <div className={contents} style={style}>
+        {children}
+      </div>
+    </div>
+  );
+}
+
+/* -------------------------------------------------------------------------- */
diff --git a/ivette/src/dome/renderer/layout/style.css b/ivette/src/dome/renderer/layout/style.css
index fd96bab00f3b41f4612e3f4e0bb9b51769350525..a83d879a0141e6fe35a12ac0cc7dd8350da0684e 100644
--- a/ivette/src/dome/renderer/layout/style.css
+++ b/ivette/src/dome/renderer/layout/style.css
@@ -20,9 +20,8 @@
 
 .dome-xBoxes-grid
 {
-    flex: 1 1 auto ;
-    overflow: auto ;
-    display: grid ;
+    flex: 1 1 auto;
+    overflow: auto;
     display: grid;
     grid-auto-rows: auto;
     grid-auto-flow: row;
@@ -513,3 +512,17 @@
 }
 
 /* -------------------------------------------------------------------------- */
+/* --- Overlay                                                            --- */
+/* -------------------------------------------------------------------------- */
+
+.dome-xOverlay-anchor {
+    display: inline-block;
+    width: 0px;
+    height: 0px;
+}
+
+.dome-xOverlay-contents {
+    z-index: 1;
+}
+
+/* -------------------------------------------------------------------------- */
diff --git a/ivette/src/dome/renderer/light.css b/ivette/src/dome/renderer/light.css
index 919c24be478240035817a130e9419aa90c1ab458..55a5cf4c0d591dccd0ef50fa77088d17cc8bf8e8 100644
--- a/ivette/src/dome/renderer/light.css
+++ b/ivette/src/dome/renderer/light.css
@@ -64,7 +64,7 @@
 
     --warning-button-color: #fece72;
     --warning-led-img: radial-gradient(circle at center, #ffc749, #ecd44f);
-    --warning-button-img: linear-gradient(to bottom, #fece72 0%, #fcaa0e 100%);
+    --warning-button-img: linear-gradient(to bottom, #ffb000 0%, #ffb449 100%);
     --warning-button-hover: orange;
     --warning-button-active: #ffffff;
 
diff --git a/ivette/src/dome/renderer/style.css b/ivette/src/dome/renderer/style.css
index 89cb548cbcc943fde53f3b25fac3491b51d41c1f..ad640de4533911ff329e887669ac8c19cf4bf3e6 100644
--- a/ivette/src/dome/renderer/style.css
+++ b/ivette/src/dome/renderer/style.css
@@ -39,6 +39,10 @@ body {
     visibility: hidden !important;
 }
 
+.dome-positionned {
+    position: relative;
+}
+
 .dome-color-frame {
     fill:         var(--text-discrete) ;
     color:        var(--text-discrete) ;
@@ -94,10 +98,11 @@ div.dome-dragged {
 }
 
 .dome-text-descr {
+    font-weight: lighter ;
     font-family: sans-serif ;
     font-size: smaller ;
     user-select: none ;
-    white-space: wrap ;
+    white-space: normal ;
     text-overflow: ellipsis ;
 }
 
@@ -127,6 +132,15 @@ div.dome-dragged {
     text-overflow: clip ;
 }
 
+.dome-text-item {
+    cursor: default ;
+    user-select: none ;
+    font-family: 'Andale mono', monospace ;
+    font-size: 9pt ;
+    white-space: nowrap ;
+    text-overflow: clip ;
+}
+
 /* -------------------------------------------------------------------------- */
 /* --- Theme-compliant Scrollbars                                         --- */
 /* -------------------------------------------------------------------------- */
diff --git a/ivette/src/dome/renderer/text/richtext.tsx b/ivette/src/dome/renderer/text/richtext.tsx
index 03859f53447d43b877d70910a16cf0b431fbf111..02ea5b7a143800453597a82069a72213b64e9254 100644
--- a/ivette/src/dome/renderer/text/richtext.tsx
+++ b/ivette/src/dome/renderer/text/richtext.tsx
@@ -53,6 +53,12 @@ export function byOffset(a : Offset, b : Offset): number
 
 type View = CM.EditorView | null;
 
+const clipRange = (a: number, value: number, b: number): number => {
+  if (value < a) return a;
+  if (value > b) return b;
+  return value;
+};
+
 /* -------------------------------------------------------------------------- */
 /* --- Text View Updates                                                  --- */
 /* -------------------------------------------------------------------------- */
@@ -68,13 +74,17 @@ function dispatchContents(view: CM.EditorView, text: string | CS.Text): void {
 }
 
 function dispatchReplace(view: CM.EditorView, rg: Range, text: string): void {
-  const { offset: from, length } = rg;
-  view.dispatch({ changes: { from, to: from + length, insert: text } });
+  const docLength = view.state.doc.length;
+  const { offset, length } = rg;
+  const endOffset = offset + length;
+  if (0 <= offset && offset <= endOffset && endOffset <= docLength)
+    view.dispatch({ changes: { from: offset, to: endOffset, insert: text } });
 }
 
 function dispatchScroll(view: CM.EditorView, rg: Range): void {
-  const anchor = rg.offset;
-  const head = anchor + rg.length;
+  const length = view.state.doc.length;
+  const anchor = clipRange(0, rg.offset, length);
+  const head = clipRange(anchor, anchor + rg.length, length);
   view.dispatch({ scrollIntoView: true, selection: { anchor, head } });
 }
 
@@ -179,8 +189,11 @@ export class TextProxy {
 
   /** Returns 1 also when disconnected. */
   lineAt(offset: number): number {
+    if (offset < 0) return -1;
     const doc = this.toText();
     if (!doc) return -1;
+    const length = doc.length;
+    if (offset > length) return -1;
     return doc.lineAt(offset).number;
   }
 
@@ -401,7 +414,10 @@ class Field<A> extends Extension {
     const annot = CS.Annotation.define<A>();
     const field = CS.StateField.define<A>({
       create: () => init,
-      update: (fd: A, tr: CS.Transaction) => tr.annotation(annot) ?? fd,
+      update: (fd: A, tr: CS.Transaction): A => {
+        const newValue = tr.annotation(annot);
+        return newValue !== undefined ? newValue : fd;
+      }
     });
     this.annot = annot;
     this.field = field;
diff --git a/ivette/src/frama-c/plugins/wp/api/index.ts b/ivette/src/frama-c/plugins/wp/api/index.ts
index 5e73797a2f571719550adfa4714fdb0b2c4391b3..031e5b95806b3fc8df1c84536c3f8a8a5b28191a 100644
--- a/ivette/src/frama-c/plugins/wp/api/index.ts
+++ b/ivette/src/frama-c/plugins/wp/api/index.ts
@@ -78,6 +78,192 @@ export const byProver: Compare.Order<prover> = Compare.string;
 /** Default value for `prover` */
 export const proverDefault: prover = Json.jKey<'#prover'>('#prover')('');
 
+/** Signal for state [`provers`](#provers)  */
+export const signalProvers: Server.Signal = {
+  name: 'plugins.wp.signalProvers',
+};
+
+const getProvers_internal: Server.GetRequest<null,prover[]> = {
+  kind: Server.RqKind.GET,
+  name: 'plugins.wp.getProvers',
+  input: Json.jNull,
+  output: Json.jArray(jProver),
+  signals: [],
+};
+/** Getter for state [`provers`](#provers)  */
+export const getProvers: Server.GetRequest<null,prover[]>= getProvers_internal;
+
+const setProvers_internal: Server.SetRequest<prover[],null> = {
+  kind: Server.RqKind.SET,
+  name: 'plugins.wp.setProvers',
+  input: Json.jArray(jProver),
+  output: Json.jNull,
+  signals: [],
+};
+/** Setter for state [`provers`](#provers)  */
+export const setProvers: Server.SetRequest<prover[],null>= setProvers_internal;
+
+const provers_internal: State.State<prover[]> = {
+  name: 'plugins.wp.provers',
+  signal: signalProvers,
+  getter: getProvers,
+  setter: setProvers,
+};
+/** Selected Provers */
+export const provers: State.State<prover[]> = provers_internal;
+
+/** Signal for state [`process`](#process)  */
+export const signalProcess: Server.Signal = {
+  name: 'plugins.wp.signalProcess',
+};
+
+const getProcess_internal: Server.GetRequest<null,number> = {
+  kind: Server.RqKind.GET,
+  name: 'plugins.wp.getProcess',
+  input: Json.jNull,
+  output: Json.jNumber,
+  signals: [],
+};
+/** Getter for state [`process`](#process)  */
+export const getProcess: Server.GetRequest<null,number>= getProcess_internal;
+
+const setProcess_internal: Server.SetRequest<number,null> = {
+  kind: Server.RqKind.SET,
+  name: 'plugins.wp.setProcess',
+  input: Json.jNumber,
+  output: Json.jNull,
+  signals: [],
+};
+/** Setter for state [`process`](#process)  */
+export const setProcess: Server.SetRequest<number,null>= setProcess_internal;
+
+const process_internal: State.State<number> = {
+  name: 'plugins.wp.process',
+  signal: signalProcess,
+  getter: getProcess,
+  setter: setProcess,
+};
+/** Server Processes */
+export const process: State.State<number> = process_internal;
+
+/** Signal for state [`timeout`](#timeout)  */
+export const signalTimeout: Server.Signal = {
+  name: 'plugins.wp.signalTimeout',
+};
+
+const getTimeout_internal: Server.GetRequest<null,number> = {
+  kind: Server.RqKind.GET,
+  name: 'plugins.wp.getTimeout',
+  input: Json.jNull,
+  output: Json.jNumber,
+  signals: [],
+};
+/** Getter for state [`timeout`](#timeout)  */
+export const getTimeout: Server.GetRequest<null,number>= getTimeout_internal;
+
+const setTimeout_internal: Server.SetRequest<number,null> = {
+  kind: Server.RqKind.SET,
+  name: 'plugins.wp.setTimeout',
+  input: Json.jNumber,
+  output: Json.jNull,
+  signals: [],
+};
+/** Setter for state [`timeout`](#timeout)  */
+export const setTimeout: Server.SetRequest<number,null>= setTimeout_internal;
+
+const timeout_internal: State.State<number> = {
+  name: 'plugins.wp.timeout',
+  signal: signalTimeout,
+  getter: getTimeout,
+  setter: setTimeout,
+};
+/** Prover's Timeout */
+export const timeout: State.State<number> = timeout_internal;
+
+/** Data for array rows [`ProverInfos`](#proverinfos)  */
+export interface ProverInfosData {
+  /** Entry identifier. */
+  prover: prover;
+  /** Prover Name */
+  name: string;
+  /** Prover Version */
+  version: string;
+  /** Prover Full Name (description) */
+  descr: string;
+}
+
+/** Decoder for `ProverInfosData` */
+export const jProverInfosData: Json.Decoder<ProverInfosData> =
+  Json.jObject({
+    prover: jProver,
+    name: Json.jString,
+    version: Json.jString,
+    descr: Json.jString,
+  });
+
+/** Natural order for `ProverInfosData` */
+export const byProverInfosData: Compare.Order<ProverInfosData> =
+  Compare.byFields
+    <{ prover: prover, name: string, version: string, descr: string }>({
+    prover: byProver,
+    name: Compare.alpha,
+    version: Compare.alpha,
+    descr: Compare.alpha,
+  });
+
+/** Signal for array [`ProverInfos`](#proverinfos)  */
+export const signalProverInfos: Server.Signal = {
+  name: 'plugins.wp.signalProverInfos',
+};
+
+const reloadProverInfos_internal: Server.GetRequest<null,null> = {
+  kind: Server.RqKind.GET,
+  name: 'plugins.wp.reloadProverInfos',
+  input: Json.jNull,
+  output: Json.jNull,
+  signals: [],
+};
+/** Force full reload for array [`ProverInfos`](#proverinfos)  */
+export const reloadProverInfos: Server.GetRequest<null,null>= reloadProverInfos_internal;
+
+const fetchProverInfos_internal: Server.GetRequest<
+  number,
+  { reload: boolean, removed: prover[], updated: ProverInfosData[],
+    pending: number }
+  > = {
+  kind: Server.RqKind.GET,
+  name: 'plugins.wp.fetchProverInfos',
+  input: Json.jNumber,
+  output: Json.jObject({
+            reload: Json.jBoolean,
+            removed: Json.jArray(jProver),
+            updated: Json.jArray(jProverInfosData),
+            pending: Json.jNumber,
+          }),
+  signals: [],
+};
+/** Data fetcher for array [`ProverInfos`](#proverinfos)  */
+export const fetchProverInfos: Server.GetRequest<
+  number,
+  { reload: boolean, removed: prover[], updated: ProverInfosData[],
+    pending: number }
+  >= fetchProverInfos_internal;
+
+const ProverInfos_internal: State.Array<prover,ProverInfosData> = {
+  name: 'plugins.wp.ProverInfos',
+  getkey: ((d:ProverInfosData) => d.prover),
+  signal: signalProverInfos,
+  fetch: fetchProverInfos,
+  reload: reloadProverInfos,
+  order: byProverInfosData,
+};
+/** Available Provers */
+export const ProverInfos: State.Array<prover,ProverInfosData> = ProverInfos_internal;
+
+/** Default value for `ProverInfosData` */
+export const ProverInfosDataDefault: ProverInfosData =
+  { prover: proverDefault, name: '', version: '', descr: '' };
+
 /** Prover Result */
 export type result =
   { descr: string, cached: boolean, verdict: string, solverTime: number,
@@ -167,16 +353,6 @@ export const byStats: Compare.Order<stats> =
 export const statsDefault: stats =
   { summary: '', tactics: 0, proved: 0, total: 0 };
 
-const getAvailableProvers_internal: Server.GetRequest<null,prover[]> = {
-  kind: Server.RqKind.GET,
-  name: 'plugins.wp.getAvailableProvers',
-  input: Json.jNull,
-  output: Json.jArray(jProver),
-  signals: [],
-};
-/** Returns the list of configured provers from why3 */
-export const getAvailableProvers: Server.GetRequest<null,prover[]>= getAvailableProvers_internal;
-
 /** Data for array rows [`goals`](#goals)  */
 export interface goalsData {
   /** Entry identifier. */
@@ -203,6 +379,8 @@ export interface goalsData {
   status: status;
   /** Prover Stats Summary */
   stats: stats;
+  /** Proof Tree */
+  proof: boolean;
   /** Script File */
   script?: string;
   /** Saved Script */
@@ -224,6 +402,7 @@ export const jGoalsData: Json.Decoder<goalsData> =
     passed: Json.jBoolean,
     status: jStatus,
     stats: jStats,
+    proof: Json.jBoolean,
     script: Json.jOption(Json.jString),
     saved: Json.jBoolean,
   });
@@ -234,7 +413,7 @@ export const byGoalsData: Compare.Order<goalsData> =
     <{ wpo: goal, marker: marker, scope?: decl, property: marker,
        fct?: string, bhv?: string, thy?: string, name: string,
        smoke: boolean, passed: boolean, status: status, stats: stats,
-       script?: string, saved: boolean }>({
+       proof: boolean, script?: string, saved: boolean }>({
     wpo: byGoal,
     marker: byMarker,
     scope: Compare.defined(byDecl),
@@ -247,6 +426,7 @@ export const byGoalsData: Compare.Order<goalsData> =
     passed: Compare.boolean,
     status: byStatus,
     stats: byStats,
+    proof: Compare.boolean,
     script: Compare.defined(Compare.string),
     saved: Compare.boolean,
   });
@@ -303,7 +483,7 @@ export const goalsDataDefault: goalsData =
   { wpo: goalDefault, marker: markerDefault, scope: undefined,
     property: markerDefault, fct: undefined, bhv: undefined, thy: undefined,
     name: '', smoke: false, passed: false, status: statusDefault,
-    stats: statsDefault, script: undefined, saved: false };
+    stats: statsDefault, proof: false, script: undefined, saved: false };
 
 /** Proof Server Activity */
 export const serverActivity: Server.Signal = {
diff --git a/ivette/src/frama-c/plugins/wp/api/tac/index.ts b/ivette/src/frama-c/plugins/wp/api/tac/index.ts
index ee290243ed1d4610fe4ce2b696a50a7522296b46..f2c0881779d2d0711821f09746271545a2ba2c58 100644
--- a/ivette/src/frama-c/plugins/wp/api/tac/index.ts
+++ b/ivette/src/frama-c/plugins/wp/api/tac/index.ts
@@ -40,11 +40,19 @@ import * as State from 'frama-c/states';
 //@ts-ignore
 import { byNode } from 'frama-c/plugins/wp/api/tip';
 //@ts-ignore
+import { byTactic } from 'frama-c/plugins/wp/api/tip';
+//@ts-ignore
 import { jNode } from 'frama-c/plugins/wp/api/tip';
 //@ts-ignore
+import { jTactic } from 'frama-c/plugins/wp/api/tip';
+//@ts-ignore
 import { node } from 'frama-c/plugins/wp/api/tip';
 //@ts-ignore
 import { nodeDefault } from 'frama-c/plugins/wp/api/tip';
+//@ts-ignore
+import { tactic } from 'frama-c/plugins/wp/api/tip';
+//@ts-ignore
+import { tacticDefault } from 'frama-c/plugins/wp/api/tip';
 
 /** Parameter kind */
 export type kind =
@@ -173,7 +181,7 @@ export const parameterDefault: parameter =
 /** Data for array rows [`tactical`](#tactical)  */
 export interface tacticalData {
   /** Entry identifier. */
-  id: Json.key<'#tactic'>;
+  id: tactic;
   /** Tactic name */
   label: string;
   /** Tactic description */
@@ -189,7 +197,7 @@ export interface tacticalData {
 /** Decoder for `tacticalData` */
 export const jTacticalData: Json.Decoder<tacticalData> =
   Json.jObject({
-    id: Json.jKey<'#tactic'>('#tactic'),
+    id: jTactic,
     label: Json.jString,
     title: Json.jString,
     error: Json.jOption(Json.jString),
@@ -200,9 +208,9 @@ export const jTacticalData: Json.Decoder<tacticalData> =
 /** Natural order for `tacticalData` */
 export const byTacticalData: Compare.Order<tacticalData> =
   Compare.byFields
-    <{ id: Json.key<'#tactic'>, label: string, title: string, error?: string,
+    <{ id: tactic, label: string, title: string, error?: string,
        status: status, params: parameter[] }>({
-    id: Compare.string,
+    id: byTactic,
     label: Compare.string,
     title: Compare.string,
     error: Compare.defined(Compare.string),
@@ -227,7 +235,7 @@ export const reloadTactical: Server.GetRequest<null,null>= reloadTactical_intern
 
 const fetchTactical_internal: Server.GetRequest<
   number,
-  { reload: boolean, removed: Json.key<'#tactic'>[], updated: tacticalData[],
+  { reload: boolean, removed: tactic[], updated: tacticalData[],
     pending: number }
   > = {
   kind: Server.RqKind.GET,
@@ -235,7 +243,7 @@ const fetchTactical_internal: Server.GetRequest<
   input: Json.jNumber,
   output: Json.jObject({
             reload: Json.jBoolean,
-            removed: Json.jArray(Json.jKey<'#tactic'>('#tactic')),
+            removed: Json.jArray(jTactic),
             updated: Json.jArray(jTacticalData),
             pending: Json.jNumber,
           }),
@@ -244,11 +252,11 @@ const fetchTactical_internal: Server.GetRequest<
 /** Data fetcher for array [`tactical`](#tactical)  */
 export const fetchTactical: Server.GetRequest<
   number,
-  { reload: boolean, removed: Json.key<'#tactic'>[], updated: tacticalData[],
+  { reload: boolean, removed: tactic[], updated: tacticalData[],
     pending: number }
   >= fetchTactical_internal;
 
-const tactical_internal: State.Array<Json.key<'#tactic'>,tacticalData> = {
+const tactical_internal: State.Array<tactic,tacticalData> = {
   name: 'plugins.wp.tac.tactical',
   getkey: ((d:tacticalData) => d.id),
   signal: signalTactical,
@@ -257,33 +265,32 @@ const tactical_internal: State.Array<Json.key<'#tactic'>,tacticalData> = {
   order: byTacticalData,
 };
 /** Tactical Configurations */
-export const tactical: State.Array<Json.key<'#tactic'>,tacticalData> = tactical_internal;
+export const tactical: State.Array<tactic,tacticalData> = tactical_internal;
 
 /** Default value for `tacticalData` */
 export const tacticalDataDefault: tacticalData =
-  { id: Json.jKey<'#tactic'>('#tactic')(''), label: '', title: '',
-    error: undefined, status: statusDefault, params: [] };
+  { id: tacticDefault, label: '', title: '', error: undefined,
+    status: statusDefault, params: [] };
 
-const configureTactics_internal: Server.ExecRequest<{ node: node },null> = {
+const configureTactics_internal: Server.ExecRequest<node,null> = {
   kind: Server.RqKind.EXEC,
   name: 'plugins.wp.tac.configureTactics',
-  input: Json.jObject({ node: jNode,}),
+  input: jNode,
   output: Json.jNull,
   signals: [ { name: 'plugins.wp.tip.printStatus' } ],
 };
 /** Configure all tactics */
-export const configureTactics: Server.ExecRequest<{ node: node },null>= configureTactics_internal;
+export const configureTactics: Server.ExecRequest<node,null>= configureTactics_internal;
 
 const setParameter_internal: Server.ExecRequest<
-  { node: node, tactic: Json.key<'#tactic'>, param: Json.key<'#param'>,
-    value: Json.json },
+  { node: node, tactic: tactic, param: Json.key<'#param'>, value: Json.json },
   null
   > = {
   kind: Server.RqKind.EXEC,
   name: 'plugins.wp.tac.setParameter',
   input: Json.jObject({
            node: jNode,
-           tactic: Json.jKey<'#tactic'>('#tactic'),
+           tactic: jTactic,
            param: Json.jKey<'#param'>('#param'),
            value: Json.jAny,
          }),
@@ -292,19 +299,28 @@ const setParameter_internal: Server.ExecRequest<
 };
 /** Configure tactical parameter */
 export const setParameter: Server.ExecRequest<
-  { node: node, tactic: Json.key<'#tactic'>, param: Json.key<'#param'>,
-    value: Json.json },
+  { node: node, tactic: tactic, param: Json.key<'#param'>, value: Json.json },
   null
   >= setParameter_internal;
 
-const applyTactic_internal: Server.ExecRequest<Json.key<'#tactic'>,node[]> = {
+const applyTactic_internal: Server.ExecRequest<tactic,node[]> = {
   kind: Server.RqKind.EXEC,
   name: 'plugins.wp.tac.applyTactic',
-  input: Json.jKey<'#tactic'>('#tactic'),
+  input: jTactic,
   output: Json.jArray(jNode),
   signals: [],
 };
 /** Applies the (configured) tactic */
-export const applyTactic: Server.ExecRequest<Json.key<'#tactic'>,node[]>= applyTactic_internal;
+export const applyTactic: Server.ExecRequest<tactic,node[]>= applyTactic_internal;
+
+const applyTacticAndProve_internal: Server.ExecRequest<tactic,node[]> = {
+  kind: Server.RqKind.EXEC,
+  name: 'plugins.wp.tac.applyTacticAndProve',
+  input: jTactic,
+  output: Json.jArray(jNode),
+  signals: [],
+};
+/** Applies tactic and run provers on children */
+export const applyTacticAndProve: Server.ExecRequest<tactic,node[]>= applyTacticAndProve_internal;
 
 /* ------------------------------------- */
diff --git a/ivette/src/frama-c/plugins/wp/api/tip/index.ts b/ivette/src/frama-c/plugins/wp/api/tip/index.ts
index f1c3ac6961a529da08f2a64c9a231de1b44b2590..84c39050f29256945e2a7e898dc92dc4b0255cc2 100644
--- a/ivette/src/frama-c/plugins/wp/api/tip/index.ts
+++ b/ivette/src/frama-c/plugins/wp/api/tip/index.ts
@@ -75,6 +75,11 @@ export const proofStatus: Server.Signal = {
   name: 'plugins.wp.tip.proofStatus',
 };
 
+/** Updated TIP printer */
+export const printStatus: Server.Signal = {
+  name: 'plugins.wp.tip.printStatus',
+};
+
 /** Proof Node index */
 export type node = Json.index<'#node'>;
 
@@ -87,55 +92,90 @@ export const byNode: Compare.Order<node> = Compare.number;
 /** Default value for `node` */
 export const nodeDefault: node = Json.jIndex<'#node'>('#node')(-1);
 
+/** Tactic identifier */
+export type tactic = Json.key<'#tactic'>;
+
+/** Decoder for `tactic` */
+export const jTactic: Json.Decoder<tactic> = Json.jKey<'#tactic'>('#tactic');
+
+/** Natural order for `tactic` */
+export const byTactic: Compare.Order<tactic> = Compare.string;
+
+/** Default value for `tactic` */
+export const tacticDefault: tactic = Json.jKey<'#tactic'>('#tactic')('');
+
 const getNodeInfos_internal: Server.GetRequest<
   node,
-  { result: string, proved: boolean, pending: number, size: number,
-    stats: string, results: [ prover, result ][], tactic: string,
-    children: [ string, node ][] }
+  { title: string, proved: boolean, pending: number, size: number,
+    stats: string, results: [ prover, result ][], tactic?: tactic,
+    header?: string, child?: string, path: node[], children: node[] }
   > = {
   kind: Server.RqKind.GET,
   name: 'plugins.wp.tip.getNodeInfos',
   input: jNode,
   output: Json.jObject({
-            result: Json.jString,
+            title: Json.jString,
             proved: Json.jBoolean,
             pending: Json.jNumber,
             size: Json.jNumber,
             stats: Json.jString,
             results: Json.jArray(Json.jPair( jProver, jResult,)),
-            tactic: Json.jString,
-            children: Json.jArray(Json.jPair( Json.jString, jNode,)),
+            tactic: Json.jOption(jTactic),
+            header: Json.jOption(Json.jString),
+            child: Json.jOption(Json.jString),
+            path: Json.jArray(jNode),
+            children: Json.jArray(jNode),
           }),
   signals: [ { name: 'plugins.wp.tip.proofStatus' } ],
 };
 /** Proof node information */
 export const getNodeInfos: Server.GetRequest<
   node,
-  { result: string, proved: boolean, pending: number, size: number,
-    stats: string, results: [ prover, result ][], tactic: string,
-    children: [ string, node ][] }
+  { title: string, proved: boolean, pending: number, size: number,
+    stats: string, results: [ prover, result ][], tactic?: tactic,
+    header?: string, child?: string, path: node[], children: node[] }
   >= getNodeInfos_internal;
 
-const getProofState_internal: Server.GetRequest<
+const getResult_internal: Server.GetRequest<
+  { node: node, prover: prover },
+  result
+  > = {
+  kind: Server.RqKind.GET,
+  name: 'plugins.wp.tip.getResult',
+  input: Json.jObject({ node: jNode, prover: jProver,}),
+  output: jResult,
+  signals: [ { name: 'plugins.wp.tip.proofStatus' } ],
+};
+/** Result for specified node and prover */
+export const getResult: Server.GetRequest<
+  { node: node, prover: prover },
+  result
+  >= getResult_internal;
+
+const getProofStatus_internal: Server.GetRequest<
   goal,
-  { current: node, path: node[], index: number, pending: number }
+  { index: number, pending: number, current: node, above: node[],
+    below: node[], tactic?: tactic }
   > = {
   kind: Server.RqKind.GET,
-  name: 'plugins.wp.tip.getProofState',
+  name: 'plugins.wp.tip.getProofStatus',
   input: jGoal,
   output: Json.jObject({
-            current: jNode,
-            path: Json.jArray(jNode),
             index: Json.jNumber,
             pending: Json.jNumber,
+            current: jNode,
+            above: Json.jArray(jNode),
+            below: Json.jArray(jNode),
+            tactic: Json.jOption(jTactic),
           }),
   signals: [ { name: 'plugins.wp.tip.proofStatus' } ],
 };
 /** Current Proof Status of a Goal */
-export const getProofState: Server.GetRequest<
+export const getProofStatus: Server.GetRequest<
   goal,
-  { current: node, path: node[], index: number, pending: number }
-  >= getProofState_internal;
+  { index: number, pending: number, current: node, above: node[],
+    below: node[], tactic?: tactic }
+  >= getProofStatus_internal;
 
 const goForward_internal: Server.SetRequest<goal,null> = {
   kind: Server.RqKind.SET,
@@ -177,15 +217,45 @@ const goToNode_internal: Server.SetRequest<node,null> = {
 /** Set current node of associated proof tree */
 export const goToNode: Server.SetRequest<node,null>= goToNode_internal;
 
-const removeNode_internal: Server.SetRequest<node,null> = {
+const clearNode_internal: Server.SetRequest<node,null> = {
+  kind: Server.RqKind.SET,
+  name: 'plugins.wp.tip.clearNode',
+  input: jNode,
+  output: Json.jNull,
+  signals: [],
+};
+/** Cancel all node results and sub-tree (if any) */
+export const clearNode: Server.SetRequest<node,null>= clearNode_internal;
+
+const clearNodeTactic_internal: Server.SetRequest<node,null> = {
+  kind: Server.RqKind.SET,
+  name: 'plugins.wp.tip.clearNodeTactic',
+  input: jNode,
+  output: Json.jNull,
+  signals: [],
+};
+/** Cancel node current tactic */
+export const clearNodeTactic: Server.SetRequest<node,null>= clearNodeTactic_internal;
+
+const clearParentTactic_internal: Server.SetRequest<node,null> = {
   kind: Server.RqKind.SET,
-  name: 'plugins.wp.tip.removeNode',
+  name: 'plugins.wp.tip.clearParentTactic',
   input: jNode,
   output: Json.jNull,
   signals: [],
 };
-/** Remove node from tree and go to parent */
-export const removeNode: Server.SetRequest<node,null>= removeNode_internal;
+/** Cancel parent node tactic */
+export const clearParentTactic: Server.SetRequest<node,null>= clearParentTactic_internal;
+
+const clearGoal_internal: Server.SetRequest<goal,null> = {
+  kind: Server.RqKind.SET,
+  name: 'plugins.wp.tip.clearGoal',
+  input: jGoal,
+  output: Json.jNull,
+  signals: [],
+};
+/** Remove the complete goal proof tree */
+export const clearGoal: Server.SetRequest<goal,null>= clearGoal_internal;
 
 /** Proof part marker */
 export type part = Json.key<'#part'>;
@@ -211,11 +281,6 @@ export const byTerm: Compare.Order<term> = Compare.string;
 /** Default value for `term` */
 export const termDefault: term = Json.jKey<'#term'>('#term')('');
 
-/** Updated TIP printer */
-export const printStatus: Server.Signal = {
-  name: 'plugins.wp.tip.printStatus',
-};
-
 /** Integer constants format */
 export type iformat = "dec" | "hex" | "bin";
 
@@ -318,7 +383,8 @@ const getSelection_internal: Server.GetRequest<
             part: Json.jOption(jPart),
             term: Json.jOption(jTerm),
           }),
-  signals: [ { name: 'plugins.wp.tip.printStatus' } ],
+  signals: [ { name: 'plugins.wp.tip.printStatus' },
+             { name: 'plugins.wp.tip.proofStatus' } ],
 };
 /** Get current selection in proof node */
 export const getSelection: Server.GetRequest<
@@ -326,4 +392,112 @@ export const getSelection: Server.GetRequest<
   { part?: part, term?: term }
   >= getSelection_internal;
 
+const runProvers_internal: Server.SetRequest<
+  { node: node, timeout?: number, provers?: prover[] },
+  null
+  > = {
+  kind: Server.RqKind.SET,
+  name: 'plugins.wp.tip.runProvers',
+  input: Json.jObject({
+           node: jNode,
+           timeout: Json.jOption(Json.jNumber),
+           provers: Json.jOption(Json.jArray(jProver)),
+         }),
+  output: Json.jNull,
+  signals: [],
+};
+/** Schedule provers on proof node */
+export const runProvers: Server.SetRequest<
+  { node: node, timeout?: number, provers?: prover[] },
+  null
+  >= runProvers_internal;
+
+const killProvers_internal: Server.SetRequest<
+  { node: node, provers?: prover[] },
+  null
+  > = {
+  kind: Server.RqKind.SET,
+  name: 'plugins.wp.tip.killProvers',
+  input: Json.jObject({
+           node: jNode,
+           provers: Json.jOption(Json.jArray(jProver)),
+         }),
+  output: Json.jNull,
+  signals: [],
+};
+/** Interrupt running provers on proof node */
+export const killProvers: Server.SetRequest<
+  { node: node, provers?: prover[] },
+  null
+  >= killProvers_internal;
+
+const clearProvers_internal: Server.SetRequest<
+  { node: node, provers?: prover[] },
+  null
+  > = {
+  kind: Server.RqKind.SET,
+  name: 'plugins.wp.tip.clearProvers',
+  input: Json.jObject({
+           node: jNode,
+           provers: Json.jOption(Json.jArray(jProver)),
+         }),
+  output: Json.jNull,
+  signals: [],
+};
+/** Remove prover results from proof node */
+export const clearProvers: Server.SetRequest<
+  { node: node, provers?: prover[] },
+  null
+  >= clearProvers_internal;
+
+const getScriptStatus_internal: Server.GetRequest<
+  goal,
+  { proof: boolean, script?: string, saved: boolean }
+  > = {
+  kind: Server.RqKind.GET,
+  name: 'plugins.wp.tip.getScriptStatus',
+  input: jGoal,
+  output: Json.jObject({
+            proof: Json.jBoolean,
+            script: Json.jOption(Json.jString),
+            saved: Json.jBoolean,
+          }),
+  signals: [ { name: 'plugins.wp.tip.proofStatus' } ],
+};
+/** Script Status for a given Goal */
+export const getScriptStatus: Server.GetRequest<
+  goal,
+  { proof: boolean, script?: string, saved: boolean }
+  >= getScriptStatus_internal;
+
+const saveScript_internal: Server.SetRequest<goal,null> = {
+  kind: Server.RqKind.SET,
+  name: 'plugins.wp.tip.saveScript',
+  input: jGoal,
+  output: Json.jNull,
+  signals: [],
+};
+/** Save Script for the Goal */
+export const saveScript: Server.SetRequest<goal,null>= saveScript_internal;
+
+const runScript_internal: Server.SetRequest<goal,null> = {
+  kind: Server.RqKind.SET,
+  name: 'plugins.wp.tip.runScript',
+  input: jGoal,
+  output: Json.jNull,
+  signals: [],
+};
+/** Replay Saved Script for the Goal (if any) */
+export const runScript: Server.SetRequest<goal,null>= runScript_internal;
+
+const clearProofScript_internal: Server.SetRequest<goal,null> = {
+  kind: Server.RqKind.SET,
+  name: 'plugins.wp.tip.clearProofScript',
+  input: jGoal,
+  output: Json.jNull,
+  signals: [],
+};
+/** Clear Proof and Remove any Saved Script for the Goal */
+export const clearProofScript: Server.SetRequest<goal,null>= clearProofScript_internal;
+
 /* ------------------------------------- */
diff --git a/ivette/src/frama-c/plugins/wp/goals.tsx b/ivette/src/frama-c/plugins/wp/goals.tsx
index 349d1f2c2f144105788669a60323f107f3e731c9..167f55efeff29a8f73a99be6ad935f2e2a01beb0 100644
--- a/ivette/src/frama-c/plugins/wp/goals.tsx
+++ b/ivette/src/frama-c/plugins/wp/goals.tsx
@@ -21,13 +21,39 @@
 /* ************************************************************************ */
 
 import React from 'react';
-import { IconKind, Cell } from 'dome/controls/labels';
+import { Icon } from 'dome/controls/icons';
+import { IconKind, Cell, Descr } from 'dome/controls/labels';
 import { Filter } from 'dome/table/models';
 import { Table, Column } from 'dome/table/views';
 import * as States from 'frama-c/states';
 import * as Ast from 'frama-c/kernel/api/ast';
 import * as WP from 'frama-c/plugins/wp/api';
 
+/* -------------------------------------------------------------------------- */
+/* --- Table Cells                                                        --- */
+/* -------------------------------------------------------------------------- */
+
+interface IconProps {
+  icon?: string;
+  title?: string;
+}
+
+function renderIcon(s : IconProps): JSX.Element {
+  const { icon=' ', title } = s;
+  return <Icon id={icon} title={title} />;
+}
+
+interface CellProps {
+  icon: string;
+  label: string;
+  kind: IconKind;
+  title: string;
+}
+
+function renderCell(s : CellProps): JSX.Element {
+  return <Cell {...s} />;
+}
+
 /* -------------------------------------------------------------------------- */
 /* --- Scope Column                                                       --- */
 /* -------------------------------------------------------------------------- */
@@ -36,27 +62,44 @@ function getScope(g : WP.goalsData): string {
   if (g.bhv && g.fct) return `${g.fct} — {g.bhv}}`;
   if (g.fct) return g.fct;
   if (g.thy) return g.thy;
-  return '';
+  return 'Global';
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- Script Column                                                      --- */
+/* -------------------------------------------------------------------------- */
+
+/* eslint-disable max-len */
+const savedScript: IconProps = { icon: 'FOLDER', title: 'Saved Script' };
+const updatedScript: IconProps = { icon: 'FOLDER.OPEN', title: 'Updated Script' };
+const proofEdit: IconProps = { icon: 'CODE', title: 'Proof Under Construction' };
+const proofNone: IconProps = { title: 'No Proof Script' };
+/* eslint-enable max-len */
+
+export function getScript(g : WP.goalsData): IconProps {
+  const { script, saved, proof } = g;
+  return (
+    script ? (saved ? savedScript : updatedScript)
+    : (proof ? proofEdit : proofNone)
+  );
 }
 
 /* -------------------------------------------------------------------------- */
 /* --- Status Column                                                      --- */
 /* -------------------------------------------------------------------------- */
 
-interface IconStatus {
+const noResult : CellProps =
+  { icon: 'MINUS', label: 'No Result', kind: 'disabled', title: 'No Result' };
+
+interface BaseProps {
   icon: string;
   label: string;
   kind: IconKind;
   title: string;
 }
 
-interface Status extends IconStatus { label: string }
-
-const noResult : IconStatus =
-  { icon: 'MINUS', label: 'No Result', kind: 'disabled', title: 'No Result' };
-
 /* eslint-disable max-len */
-const baseStatus : { [key:string]: IconStatus } = {
+const baseStatus : { [key:string]: BaseProps } = {
   'VALID': { icon: 'CHECK', label: 'Valid', kind: 'positive', title: 'Valid Goal' },
   'PASSED': { icon: 'CHECK', label: 'Passed', kind: 'positive', title: 'Passed Test' },
   'DOOMED': { icon: 'CROSS', label: 'Doomed', kind: 'negative', title: 'Doomed Test' },
@@ -68,15 +111,11 @@ const baseStatus : { [key:string]: IconStatus } = {
 };
 /* eslint-enable max-len */
 
-export function getStatus(g : WP.goalsData): Status {
+export function getStatus(g : WP.goalsData): CellProps {
   const { label, ...base } = baseStatus[g.status] ?? noResult;
   return { ...base, label: label + g.stats.summary };
 }
 
-function renderStatus(s : Status): JSX.Element {
-  return <Cell {...s} />;
-}
-
 /* -------------------------------------------------------------------------- */
 /* --- Goals Filter                                                       --- */
 /* -------------------------------------------------------------------------- */
@@ -99,6 +138,7 @@ function filterGoal(
 export interface GoalTableProps {
   display: boolean;
   failed: boolean;
+  scoped: boolean;
   scope: Ast.decl | undefined;
   current: WP.goal | undefined;
   setCurrent: (goal: WP.goal) => void;
@@ -109,8 +149,8 @@ export interface GoalTableProps {
 
 export function GoalTable(props: GoalTableProps): JSX.Element {
   const {
-    display, scope, failed,
-    current, setCurrent, setTIP,
+    display, scoped, failed,
+    scope, current, setCurrent, setTIP,
     setGoals, setTotal,
   } = props;
   const { model } = States.useSyncArrayProxy(WP.goals);
@@ -128,16 +168,27 @@ export function GoalTable(props: GoalTableProps): JSX.Element {
   );
 
   React.useEffect(() => {
-    if (failed || !!scope) {
+    if (failed || scoped) {
       model.setFilter(filterGoal(failed, scope));
     } else {
       model.setFilter();
     }
-  }, [model, scope, failed]);
+  }, [model, failed, scoped, scope]);
 
   React.useEffect(() => setGoals(goals), [goals, setGoals]);
   React.useEffect(() => setTotal(total), [total, setTotal]);
 
+  const renderEmpty = React.useCallback(() => {
+    const kind = failed ? ' failed' : '';
+    const loc = scoped ? ' in current scope' : '';
+    const icon = scoped ? 'CURSOR' : failed ? 'CIRC.INFO' : 'INFO';
+    return (
+      <Descr
+        className='wp-empty-goals'
+        icon={icon} label={`No${kind} goals${loc}`} />
+    );
+  }, [scoped, failed]);
+
   return (
     <Table
       model={model}
@@ -146,15 +197,19 @@ export function GoalTable(props: GoalTableProps): JSX.Element {
       selection={current}
       onSelection={onSelection}
       onDoubleClick={onDoubleClick}
+      renderEmpty={renderEmpty}
     >
       <Column id='scope' label='Scope'
               width={150}
               getter={getScope} />
       <Column id='name' label='Property'
               width={150} />
+      <Column id='script' icon='FILE'
+              fixed width={30}
+              getter={getScript} render={renderIcon} />
       <Column id='status' label='Status'
               fill={true}
-              getter={getStatus} render={renderStatus} />
+              getter={getStatus} render={renderCell} />
     </Table>
   );
 }
diff --git a/ivette/src/frama-c/plugins/wp/index.tsx b/ivette/src/frama-c/plugins/wp/index.tsx
index df58d4caa73ab40343036ccf7d77205a3d389c30..77e5de598048bea2e1d8efd22a0460e1dcbf4d59 100644
--- a/ivette/src/frama-c/plugins/wp/index.tsx
+++ b/ivette/src/frama-c/plugins/wp/index.tsx
@@ -75,16 +75,17 @@ function WPGoals(): JSX.Element {
           selected={failed}
           onClick={flipFailed} />
         <IconButton
-          icon={tip ? 'ITEMS.LIST' : 'MEDIA.PLAY'}
-          kind={tip ? 'warning' : 'positive'}
+          icon='MEDIA.PLAY'
           title={tip ? 'Back to Goals' : 'Interactive Proof Transformer'}
           enabled={!!current}
+          selected={tip}
           onClick={() => setTip(!tip)} />
       </Ivette.TitleBar>
       <GoalTable
         display={!tip}
-        scope={scope}
         failed={failed}
+        scoped={scoped}
+        scope={scope}
         current={current}
         setCurrent={setCurrent}
         setTIP={() => setTip(true)}
@@ -93,7 +94,9 @@ function WPGoals(): JSX.Element {
       />
       <TIPView
         display={tip}
-        goal={current} />
+        goal={current}
+        onClose={() => setTip(false)}
+      />
     </>
   );
 }
@@ -112,17 +115,21 @@ Ivette.registerComponent({
 /* -------------------------------------------------------------------------- */
 
 function ServerActivity(): JSX.Element {
-  const rq = States.useRequest(WP.getScheduledTasks, null);
-  const active = rq ? rq.active > 0 : false;
-  const status = active ? 'active' : 'inactive';
+  const rq = States.useRequest(WP.getScheduledTasks, null, { pending: null });
+  const procs = rq ? rq.procs : 0;
+  const active = rq ? rq.active : 0;
+  const status = active > 0 ? 'active' : 'inactive';
   const done = rq ? rq.done : 0;
   const todo = rq ? rq.todo : 0;
   const total = done + todo;
+  const progress = done + active;
+  const objective = done + todo + procs;
+  const title = `${done} / ${todo} (${active} running, ${procs} procs)`;
   return (
-    <Group display={total > 0}>
+    <Group display={total > 0} title={title}>
       <LED status={status} />
       <Label>WP</Label>
-      <Meter value={done} min={0} max={done + total} />
+      <Meter min={0} value={progress} max={objective} />
       <Inset />
     </Group>
   );
diff --git a/ivette/src/frama-c/plugins/wp/seq.tsx b/ivette/src/frama-c/plugins/wp/seq.tsx
index aadc99da689378d28b63cd60d49d2642aa2e6453..aaf2809e5019141c689d7421356640b075c768d1 100644
--- a/ivette/src/frama-c/plugins/wp/seq.tsx
+++ b/ivette/src/frama-c/plugins/wp/seq.tsx
@@ -115,10 +115,11 @@ class Sequent {
     const gutters: GutterDecoration[] = [];
     if (tag) {
       const selection: Range = { offset: tag.offset, length: 0 };
-      const lineFrom = proxy.lineAt(tag.offset+1);
-      const lineTo = proxy.lineAt(tag.endOffset);
-      for (let line = lineFrom; line <= lineTo; line++)
-        gutters.push({ className: 'wp-gutter-part', gutter: '|', line });
+      const lineP = proxy.lineAt(tag.offset+1);
+      const lineQ = proxy.lineAt(tag.endOffset);
+      if (0 <= lineP && 0 <= lineQ)
+        for (let line = lineP; line <= lineQ; line++)
+          gutters.push({ className: 'wp-gutter-part', gutter: '|', line });
       return { selection, gutters };
     } else
       return { selection: undefined, gutters };
@@ -155,6 +156,7 @@ class Sequent {
 
 export interface GoalViewProps {
   node: Node;
+  locked: boolean;
   autofocus: boolean;
   unmangled: boolean;
   iformat: TIP.iformat;
@@ -162,13 +164,18 @@ export interface GoalViewProps {
 }
 
 export function GoalView(props: GoalViewProps): JSX.Element {
-  const { node } = props;
+  const { node, locked } = props;
   const jtext = States.useRequest(TIP.printSequent, props, {
     pending: null,
     offline: undefined,
     onError: '',
   }) ?? null;
-  const { part, term } = States.useRequest(TIP.getSelection, node) ?? {};
+  const { part, term } =
+    States.useRequest(TIP.getSelection, node, {
+      pending: null,
+      offline: {},
+      onError: {},
+    }) ?? {};
   const proxy = React.useMemo(() => new TextProxy(), []);
   const sequent = React.useMemo(() => new Sequent(jtext), [jtext]);
   React.useEffect(() => proxy.updateContents(sequent.text), [proxy, sequent]);
@@ -190,7 +197,7 @@ export function GoalView(props: GoalViewProps): JSX.Element {
         Server.send(TIP.setSelection, { node, part, term });
         return;
       }
-    }
+    } // otherwise
     Server.send(TIP.clearSelection, node);
   }, [sequent, node]);
   return (
@@ -201,7 +208,7 @@ export function GoalView(props: GoalViewProps): JSX.Element {
       selection={selection}
       decorations={[hover, sequent.decorations, gutters]}
       onHover={onHover}
-      onClick={onClick}
+      onClick={locked ? undefined : onClick}
     />
   );
 }
diff --git a/ivette/src/frama-c/plugins/wp/style.css b/ivette/src/frama-c/plugins/wp/style.css
index eab44e5565f0e23534e141a1f2eb3d820a7ae515..b53b0b3718a35fc710c195e0d0068a5e54ead46e 100644
--- a/ivette/src/frama-c/plugins/wp/style.css
+++ b/ivette/src/frama-c/plugins/wp/style.css
@@ -1,5 +1,15 @@
 /* -------------------------------------------------------------------------- */
-/* --- WP Panel Styles                                                    --- */
+/* --- WP Goals                                                           --- */
+/* -------------------------------------------------------------------------- */
+
+.wp-empty-goals {
+    position: absolute;
+    top: 12px;
+    left: 20px;
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- Sequent Editor                                                     --- */
 /* -------------------------------------------------------------------------- */
 
 .wp .cm-gutter {
@@ -15,3 +25,192 @@
 {
     background: var(--code-hover);
 }
+
+.wp-printer
+{
+    position: absolute;
+    right: 18px;
+    top: 2px;
+    background: var(--background-report);
+}
+
+.wp-printer-field
+{
+    margin: 0px;
+    fill: var(--text-discrete);
+    color: var(--text-discrete);
+    font-size: x-small;
+    border-radius: 6px;
+    background: var(--background-report);
+}
+
+.wp-printer-field.selected
+{
+    font-weight: bolder;
+    fill: var(--positive-button-color);
+    color: var(--positive-button-color);
+}
+
+.wp-printer-field:hover
+{
+    /* fill: var(--default-button-color); */
+    /* color: var(--default-button-color); */
+    background: var(--background-button-hover) !important;
+}
+
+.wp-printer-button
+{
+    width: 30px;
+    padding: 5px 4px 5px 4px;
+}
+
+.wp-printer-locked
+{
+    fill: var(--negative-button-color);
+}
+
+.wp-printer-button dome-xIcon {
+    margin: 0px;
+    padding: 0px;
+}
+
+.wp-printer-select
+{
+    border: none;
+    box-shadow: none;
+    text-align: right;
+    padding: 0px;
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- Navigation Bar                                                     --- */
+/* -------------------------------------------------------------------------- */
+
+.wp-navbar {
+    overflow-x: hidden;
+    overflow-y: auto;
+    background: var(--background-sidebar);
+}
+
+.wp-navbar-node {
+    padding-right: 8px;
+}
+
+.wp-navbar-node.parent {
+}
+
+.wp-navbar-node.current {
+    font-weight: bold;
+    padding-left: 4px;
+    background-color: var(--code-select);
+}
+
+.wp-navbar-node:hover {
+    background-color: var(--code-hover);
+}
+
+.wp-navbar-node.child {
+    padding-left: 8px;
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- Tactics View                                                       --- */
+/* -------------------------------------------------------------------------- */
+
+.wp-sidebar-view {
+    padding: 2px 0px 2px 3px;
+    align-content: flex-start;
+    overflow-y: auto;
+    border-width: 1px;
+    border-left-style: solid;
+}
+
+.wp-tactical-item {
+    flex: 0 0 auto;
+    margin: 2px;
+    width: 120px;
+    border-style: solid;
+    border-width: 1px;
+    border-radius: 6px;
+}
+
+.wp-tactical-item.selected {
+    background: var(--code-select);
+}
+
+.wp-tactical-item:hover {
+    background: var(--code-hover);
+}
+
+.wp-tactical-cell {
+    flex: 1 0 90px;
+    margin-right: 0px;
+    text-overflow: ellipsis;
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- Tactic Configuration                                               --- */
+/* -------------------------------------------------------------------------- */
+
+.wp-configure {
+    min-height: 32px;
+    align-items: baseline;
+    align-content: center;
+}
+
+.wp-configure .dome-xLabel {
+    padding: 0px 3px 0px 3px;
+    margin: 0px 3px 0px 3px;
+}
+
+.wp-configure .dome-xIconButton {
+    padding: 2px 5px 2px 5px;
+    width: 20px;
+}
+
+.wp-configure .dome-xIconButton.dome-control-enabled:hover {
+    border-radius: 4px;
+    background: var(--background-interaction);
+}
+
+.wp-config-tactic {
+    font-weight: bold;
+}
+
+.wp-config-info {
+    overflow: hidden;
+}
+
+.wp-config-field {
+    border: none;
+    margin-left: 4px;
+    border-radius: 4px;
+    background: var(--background-interaction);
+}
+
+.wp-config-checkbox .dome-xIcon-disabled {
+    fill: var(--disabled-text);
+}
+
+.wp-config-checkbox:hover {
+    border-radius: 6px;
+    background: var(--background-interaction);
+}
+
+.wp-config-switch {
+    padding-top: 0px;
+    padding-bottom: 0px;
+}
+
+.wp-config-spinner {
+    width: 60px;
+    color: var(--text-discrete);
+    background: var(--background-interaction);
+}
+
+input.wp-config-spinner:disabled {
+    color: var(--text-disabled);
+    background: var(--background-profound);
+}
+
+/* -------------------------------------------------------------------------- */
diff --git a/ivette/src/frama-c/plugins/wp/tac.tsx b/ivette/src/frama-c/plugins/wp/tac.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b5cc19e36f857e4799b7caf88591e5f7ae2e94cf
--- /dev/null
+++ b/ivette/src/frama-c/plugins/wp/tac.tsx
@@ -0,0 +1,602 @@
+/* ************************************************************************ */
+/*                                                                          */
+/*   This file is part of Frama-C.                                          */
+/*                                                                          */
+/*   Copyright (C) 2007-2023                                                */
+/*     CEA (Commissariat à l'énergie atomique et aux énergies               */
+/*          alternatives)                                                   */
+/*                                                                          */
+/*   you can redistribute it and/or modify it under the terms of the GNU    */
+/*   Lesser General Public License as published by the Free Software        */
+/*   Foundation, version 2.1.                                               */
+/*                                                                          */
+/*   It is distributed in the hope that it will be useful,                  */
+/*   but WITHOUT ANY WARRANTY; without even the implied warranty of         */
+/*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          */
+/*   GNU Lesser General Public License for more details.                    */
+/*                                                                          */
+/*   See the GNU Lesser General Public License version 2.1                  */
+/*   for more details (enclosed in the file licenses/LGPLv2.1).             */
+/*                                                                          */
+/* ************************************************************************ */
+
+import React, { Fragment } from 'react';
+import { classes } from 'dome/misc/utils';
+import { Icon } from 'dome/controls/icons';
+import { IconButton, IconButtonKind } from 'dome/controls/buttons';
+import { Spinner, Select } from 'dome/controls/buttons';
+import { Label, Item, Descr } from 'dome/controls/labels';
+import { Hbox, Vbox, Filler } from 'dome/layout/boxes';
+import { Separator } from 'dome/frame/toolbars';
+import * as Dnd from 'dome/dnd';
+import * as Server from 'frama-c/server';
+import * as States from 'frama-c/states';
+import * as WP from 'frama-c/plugins/wp/api';
+import * as TIP from 'frama-c/plugins/wp/api/tip';
+import * as TAC from 'frama-c/plugins/wp/api/tac';
+
+type Goal = WP.goal | undefined;
+type Node = TIP.node | undefined;
+type Prover = WP.prover | undefined;
+type Tactic = TIP.tactic | undefined;
+
+/* -------------------------------------------------------------------------- */
+/* --- Use Actions                                                        --- */
+/* -------------------------------------------------------------------------- */
+
+type ProverRequest =
+  Server.SetRequest<{ node: TIP.node, provers?: WP.prover[] }, null>;
+
+function sendProver( rq: ProverRequest, node: Node, prover: Prover ): void
+{
+  if (node && prover) {
+    Server.send(rq, { node, provers: [prover] });
+  }
+}
+
+function sendProverTime( node: Node, prover: Prover, timeout: number): void
+{
+  if (node && prover && timeout > 0) {
+    Server.send(TIP.runProvers, { node, timeout, provers: [prover] });
+  }
+}
+
+function applyTactic(tactic: Tactic): void {
+  if (tactic) {
+    Server.send(TAC.applyTacticAndProve, tactic);
+  }
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- Prover Feedback                                                    --- */
+/* -------------------------------------------------------------------------- */
+
+interface ProverActionProps {
+  icon: string;
+  kind: IconButtonKind;
+  play?: boolean;
+  clear?: boolean;
+  forward?: number;
+  running?: boolean;
+}
+
+function getProverActions(result : WP.result) : ProverActionProps {
+  switch(result.verdict) {
+    case '':
+    case 'none':
+      return { icon: 'CIRC.INFO', kind: 'default', play: true, clear: false };
+    case 'computing':
+      return { icon: 'EXECUTE', kind: 'default', running: true, clear: false };
+    case 'valid':
+      return { icon: 'CIRC.CHECK', kind: 'positive' };
+    case 'unknown':
+    case 'stepout':
+      return { icon: 'CIRC.CLOSE', kind: 'warning' };
+    case 'timeout':
+      {
+        const forward = result.proverTime * 2;
+        return { icon: 'CIRC.QUESTION', kind: 'warning', forward };
+      }
+    case 'failed':
+    default:
+      return { icon: 'WARNING', kind: 'negative' };
+  }
+}
+
+interface ProverItemProps {
+  node: Node;
+  prover: WP.prover;
+  result: WP.result;
+  inactive?: boolean;
+  selected: Prover;
+  setSelected: (prv: Prover) => void;
+}
+
+function ProverItem(props : ProverItemProps): JSX.Element
+{
+  const { node, prover, inactive=false, result, selected, setSelected } = props;
+  const { name } = States.useSyncArrayElt(WP.ProverInfos, prover) ?? {};
+  const { descr='No Result' } = result;
+  const { icon: status, kind, running=false, play=false } =
+    getProverActions(result);
+  const isSelected = prover === selected;
+  const className = classes(
+    'dome-color-frame wp-tactical-item',
+    isSelected && 'selected',
+  );
+  const enabled = !inactive && (play || running);
+  const icon =
+    inactive ? 'LOCK' : running ? 'MEDIA.PAUSE' : 'MEDIA.PLAY';
+  const title =
+    inactive ? undefined :
+    running ? 'Interrupt Prover' : 'Run Prover';
+  const action = running ? TIP.killProvers : TIP.runProvers;
+  return (
+    <Hbox
+      className={className}
+      onClick={() => setSelected(prover)}
+    >
+      <Item
+        icon={status}
+        kind={kind}
+        className='wp-tactical-cell'
+        label={name}
+        title={inactive ? `${descr} (Inactive)` : descr}
+      />
+      <IconButton
+        icon={icon}
+        title={title}
+        kind={running ? 'warning' : 'positive'}
+        enabled={enabled}
+        onClick={() => sendProver(action, node, prover)}
+      />
+    </Hbox>
+  );
+}
+
+export interface ProverSelection {
+  node: Node;
+  selected: Prover;
+  setSelected: (prv: Prover) => void;
+}
+
+export function Provers(props: ProverSelection): JSX.Element {
+  const { node, selected, setSelected } = props;
+  const { results=[] } = States.useRequest(
+    TIP.getNodeInfos, node, { pending: null, onError: null }
+  ) ?? {};
+  const [ provers=[], setProvers ] = States.useSyncState(WP.provers);
+  const setItems = (prvs: string[]): void => setProvers(prvs.map(WP.jProver));
+  const children = [...provers].sort().map((prover) => {
+    const res = results.find(([p]) => p === prover);
+    const result = res ? res[1] : WP.resultDefault;
+    return (
+      <Dnd.Item id={prover} key={prover}>
+        <ProverItem
+          node={node}
+          prover={prover}
+          result={result}
+          selected={selected}
+          setSelected={setSelected} />
+      </Dnd.Item>
+    );
+  });
+  const isInactive = (p: WP.prover): boolean => (
+    p !== 'qed' && p !== 'script' && !provers.some(q => p === q)
+  );
+  const inactive =
+    results
+      .filter(([p]) => isInactive(p))
+      .map(([p, r]) => {
+        return (
+          <ProverItem
+            key={p}
+            node={node}
+            prover={p}
+            result={r}
+            inactive
+            selected={selected}
+            setSelected={setSelected} />
+        );
+      });
+  return (
+    <>
+      <Vbox>
+        <Dnd.List items={provers} setItems={setItems}>
+          {node ? children : null}
+        </Dnd.List>
+      </Vbox>
+      {inactive}
+    </>
+  );
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- Tactical Item                                                      --- */
+/* -------------------------------------------------------------------------- */
+
+export interface TacticSelection {
+  goal: Goal;
+  node: Node;
+  locked: boolean;
+  selected: Tactic;
+  setSelected: (tac: Tactic) => void;
+}
+
+interface TacticItemProps extends TAC.tacticalData, TacticSelection {}
+
+function TacticItem(props: TacticItemProps): JSX.Element | null {
+  const { id: tactic, status, locked, selected, setSelected } = props;
+  if (status === 'NotApplicable') return null;
+  const ready = !locked && status === 'Applicable';
+  const isSelected = selected === tactic;
+  const onSelect = (): void => setSelected(tactic);
+  const onPlay = (): void => {
+    if (ready) {
+      applyTactic(tactic);
+      setSelected(undefined);
+    }
+  };
+  const className = classes(
+    'dome-color-frame wp-tactical-item',
+    isSelected && 'selected',
+  );
+  return (
+    <Hbox
+      className={className}
+      onClick={onSelect}
+      onDoubleClick={onPlay}
+    >
+      <Item className="wp-tactical-cell" {...props}/>
+      <IconButton
+        icon={locked ? 'LOCK' : 'MEDIA.PLAY'}
+        title='Apply Tactic'
+        kind='positive'
+        enabled={ready}
+        onClick={onPlay} />
+    </Hbox>
+  );
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- All Tactics View                                                   --- */
+/* -------------------------------------------------------------------------- */
+
+export function Tactics(props: TacticSelection): JSX.Element {
+  const { node } = props;
+  const tactics = States.useSyncArrayData(TAC.tactical);
+  const items =
+    node
+    ? tactics.map(tac => <TacticItem key={tac.id} {...props} {...tac} />)
+    : null;
+  return <>{items}</>;
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- Tactical Parameter                                                 --- */
+/* -------------------------------------------------------------------------- */
+
+interface ParameterProps extends TAC.parameter {
+  node: TIP.node;
+  locked: boolean;
+  tactic: TIP.tactic;
+}
+
+function CheckBoxParam(props: ParameterProps): JSX.Element
+{
+  const { id: param, locked, node, tactic, label, title, value } = props;
+  const active = value === true;
+  const onClick = (): void => {
+    if (!locked)
+      Server.send(TAC.setParameter, { node, tactic, param, value: !active });
+  };
+  return (
+    <Label
+      className="wp-config-checkbox"
+      label={label}
+      title={title}
+      onClick={onClick} >
+      <Icon
+        className="wp-config-switch"
+        size={14}
+        offset={-2}
+        kind={locked ? 'disabled' : 'default'}
+        id={active ? 'SWITCH.ON' : 'SWITCH.OFF'} />
+    </Label>
+  );
+}
+
+function SpinnerParam(props: ParameterProps): JSX.Element
+{
+  const {
+    id: param, locked, node, tactic, label, title,
+    vmin, vmax, vstep, value: jval
+  } = props;
+  const onChange = (value: number): void => {
+    if (!locked)
+      Server.send(TAC.setParameter, { node, tactic, param, value });
+  };
+  const value = typeof(jval)==='number' ? jval : undefined;
+  return (
+    <Label label={label} title={title}>
+      <Spinner
+        className="wp-config-field wp-config-spinner"
+        value={value}
+        vmin={vmin}
+        vmax={vmax}
+        vstep={vstep}
+        disabled={locked}
+        onChange={onChange}
+      />
+    </Label>
+  );
+}
+
+function SelectorParam(props: ParameterProps): JSX.Element
+{
+  const {
+    id: param, locked, node, tactic, label, title,
+    value: jval, vlist=[]
+  } = props;
+  const value = typeof(jval) === 'string' ? jval : undefined;
+  const options = vlist.map(({ id, label, title }) =>
+    <option key={id} value={id} title={title}>{label}</option>
+  );
+  const onChange = (value: string | undefined): void => {
+    if (!locked)
+      Server.send(TAC.setParameter, { node, tactic, param, value });
+  };
+  return (
+    <Label label={label} title={title}>
+      <Select
+        className="wp-config-field wp-config-select"
+        value={value}
+        disabled={locked}
+        onChange={onChange}
+      >{options}</Select>
+    </Label>
+  );
+}
+
+function Parameter(props: ParameterProps): JSX.Element | null
+{
+  if (!props.enabled) return null;
+  switch(props.kind) {
+    case 'checkbox':
+      return <CheckBoxParam {...props} />;
+    case 'spinner':
+      return <SpinnerParam {...props} />;
+    case 'selector':
+      return <SelectorParam {...props} />;
+    default:
+      return null;
+  }
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- Prover Configuration                                               --- */
+/* -------------------------------------------------------------------------- */
+
+export function ConfigureProver(props: ProverSelection): JSX.Element {
+  const { node, selected: prover, setSelected } = props;
+  const { descr } = States.useSyncArrayElt(WP.ProverInfos, prover) ?? {};
+  const result = States.useRequest(
+    TIP.getResult, { node, prover }, { pending: null, onError: null }
+  ) ?? WP.resultDefault;
+  const [process = 1, setProcess] = States.useSyncState(WP.process);
+  const [timeout = 1, setTimeout] = States.useSyncState(WP.timeout);
+  const { icon, kind, clear=true, running=false, play=false, forward=0 } =
+    getProverActions(result);
+  const [fwdTime, setFwdTime] = React.useState(0);
+  const [fwdArmed, setFwdArmed] = React.useState(false);
+  const display = !!prover;
+  const action = running ? TIP.killProvers : TIP.runProvers;
+  const onPlay = (): void => sendProver(action, node, prover);
+  const onClear = (): void => sendProver(TIP.clearProvers, node, prover);
+  const onClose = (): void => setSelected(undefined);
+  const timedOut = forward > 0 && result.proverTime < timeout;
+  const enabled = play || running || timedOut;
+  const forwardTitle =
+    forward > 0 ? `Run Prover with ${forward}s` : 'Run Prover with Extra Time';
+  const onForward = (): void => {
+    setFwdTime(forward);
+    sendProverTime(node, prover, forward);
+  };
+  React.useEffect(() => {
+    if (fwdTime > 0) {
+      if (fwdArmed && !running) {
+        setFwdArmed(false);
+        setFwdTime(0);
+      }
+      if (!fwdArmed && running) {
+        setFwdArmed(true);
+      }
+    }
+  }, [running, fwdTime, fwdArmed] );
+  return (
+    <Hbox
+      className="dome-xToolBar dome-color-frame wp-configure"
+      display={display}
+    >
+      <Label
+        icon='SETTINGS'
+        label='Process' title='Server Processes (shared by all provers)'
+      >
+        <Spinner
+          className="wp-config-field wp-config-spinner"
+          onChange={setProcess}
+          value={process}
+          vmin={1}
+          vmax={36}
+          vstep={1}
+        />
+      </Label>
+      <Label
+        label='Timeout' title='Prover Timeout (shared by all provers)'
+      >
+        <Spinner
+          className="wp-config-field wp-config-spinner"
+          enabled={fwdTime===0}
+          onChange={setTimeout}
+          value={fwdTime > 0 ? fwdTime : timeout}
+          vmin={1}
+          vmax={3600}
+          vstep={1}
+        />
+      </Label>
+      <Separator />
+      <Item
+        key='prover'
+        title='Selected Prover Configuration'
+        className="wp-config-tactic"
+        label={descr} />
+      <Item
+        icon={icon}
+        kind={kind}
+        label={result.descr} />
+      <Filler />
+      <Hbox>
+        <IconButton
+          key='clear'
+          icon='MEDIA.PREV'
+          kind='negative'
+          enabled={clear}
+          title='Clear Prover Result'
+          onClick={onClear}
+        />
+        <IconButton
+          key='play'
+          icon={running ? 'MEDIA.PAUSE' : 'MEDIA.PLAY'}
+          kind={running ? 'warning' : 'positive'}
+          title={running ? 'Interrupt Prover' : 'Run Prover'}
+          enabled={enabled}
+          onClick={onPlay}
+        />
+        <IconButton
+          key='forward'
+          icon='MEDIA.NEXT'
+          kind='positive'
+          enabled={forward > 0}
+          title={forwardTitle}
+          onClick={onForward}
+        />
+      </Hbox>
+      <IconButton
+        key='close'
+        icon='CIRC.CLOSE'
+        title='Close Prover Configuration Panel'
+        onClick={onClose} />
+    </Hbox>
+  );
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- Tactical Configuration                                             --- */
+/* -------------------------------------------------------------------------- */
+
+const noTactic: TAC.tacticalData = {
+  ...TAC.tacticalDataDefault,
+  status: 'NotApplicable'
+};
+
+function useTactic(selected: Tactic): TAC.tacticalData {
+  const data = States.useSyncArrayElt(TAC.tactical, selected);
+  return data ?? noTactic;
+}
+
+interface StatusDescr {
+  icon: string;
+  kind: 'warning' | 'positive' | 'default';
+  label: string;
+}
+
+const Locked: StatusDescr = {
+  icon: 'CHECK',
+  kind: 'default',
+  label: 'Applied',
+};
+
+function getStatusDescription(tactical: TAC.tacticalData): StatusDescr {
+  const { status, error, params } = tactical;
+  if (error)
+    return { icon: 'WARNING', kind: 'warning', label: error };
+  if (status === 'NotConfigured')
+    return { icon: 'WARNING', kind: 'warning', label: 'Missing fields' };
+  if (params.length)
+    return { icon: 'CHECK', kind: 'positive', label: 'Configured' };
+  return { icon: 'CHECK', kind: 'positive', label: 'Ready' };
+}
+
+export function ConfigureTactic(props: TacticSelection): JSX.Element {
+  const { node, locked, selected: tactic, setSelected } = props;
+  const tactical = useTactic(tactic);
+  States.useRequest(TAC.configureTactics, node, { onError: null });
+  const { status, label, title, params } = tactical;
+  const isReady = !locked && status==='Applicable';
+  const display = !!tactic && (locked || status !== 'NotApplicable');
+  const statusDescr = locked ? Locked : getStatusDescription(tactical);
+  const onClose = (): void => setSelected(undefined);
+  const onPlay = (): void => { if (isReady) applyTactic(tactic); };
+  const onClear = (): void => {
+    Server.send(TIP.clearNode, node);
+    setSelected(undefined);
+  };
+  const parameters =
+    (node && tactic)
+    ? params.map((prm: TAC.parameter) =>
+      <Parameter
+        key={prm.id}
+        node={node}
+        tactic={tactic}
+        locked={locked}
+        {...prm}/>
+    ) : null;
+  return (
+    <Hbox
+      className="dome-xToolBar dome-color-frame wp-configure"
+      display={display}
+    >
+      <Item
+        key='tactic'
+        icon='TUNINGS'
+        title='Selected Tactic Configuration'
+        className="wp-config-tactic"
+        label={label} />
+      <Descr
+        key='info'
+        icon='CIRC.INFO'
+        className="wp-config-info"
+        label={title} />
+      <Filler key='filler'/>
+      <Descr
+        key='status'
+        className="wp-config-info"
+        title='Tactic Status'
+        {...statusDescr} />
+      <Fragment key='params'>{parameters}</Fragment>
+      <IconButton
+        key='play'
+        icon={locked ? 'LOCK' : 'MEDIA.PLAY'}
+        kind='positive'
+        title='Apply Tactic'
+        enabled={isReady}
+        display={!locked}
+        onClick={onPlay} />
+      <IconButton
+        key='clear'
+        icon='MEDIA.PREV'
+        kind='negative'
+        display={locked}
+        title='Cancel Tactic and Remove Sub-Tree'
+        onClick={onClear} />
+      <IconButton
+        key='close'
+        icon='CIRC.CLOSE'
+        title='Close Tactic Configuration Panel'
+        disabled={locked}
+        onClick={onClose} />
+    </Hbox>
+  );
+}
+
+/* -------------------------------------------------------------------------- */
diff --git a/ivette/src/frama-c/plugins/wp/tip.tsx b/ivette/src/frama-c/plugins/wp/tip.tsx
index 04f97441ccc80019792a7d75edd17761a0fecabd..dd937e929831375ad89fcebc0e07fe48957d0c07 100644
--- a/ivette/src/frama-c/plugins/wp/tip.tsx
+++ b/ivette/src/frama-c/plugins/wp/tip.tsx
@@ -23,53 +23,59 @@
 import React from 'react';
 import * as Dome from 'dome';
 
-import { json } from 'dome/data/json';
-import { Cell } from 'dome/controls/labels';
-import { ToolBar, Select, Filler } from 'dome/frame/toolbars';
-import { Vfill } from 'dome/layout/boxes';
+import { classes } from 'dome/misc/utils';
+import { Icon } from 'dome/controls/icons';
+import { Cell, Item } from 'dome/controls/labels';
+import { IconButton } from 'dome/controls/buttons';
+import {
+  ToolBar, Select, Filler,
+  Button, ButtonGroup
+} from 'dome/frame/toolbars';
+import { Hfill, Vfill, Vbox, Overlay } from 'dome/layout/boxes';
+import { writeClipboardText } from 'dome/system';
+
+import * as Server from 'frama-c/server';
 import * as States from 'frama-c/states';
 import * as WP from 'frama-c/plugins/wp/api';
 import * as TIP from 'frama-c/plugins/wp/api/tip';
 
 import { getStatus } from './goals';
 import { GoalView } from './seq';
+import { Provers, Tactics, ConfigureProver, ConfigureTactic } from './tac';
 
 /* -------------------------------------------------------------------------- */
 /* --- Sequent Printing Modes                                             --- */
 /* -------------------------------------------------------------------------- */
 
-type Focus = 'AUTOFOCUS' | 'FULLCONTEXT' | 'MEMORY' | 'RAW';
+type Node = TIP.node | undefined;
+type Prover = WP.prover | undefined;
+type Tactic = TIP.tactic | undefined;
 
 interface Selector<A> {
   value: A;
   setValue: (value: A) => void;
 }
 
-function jFocus(js: json): Focus {
-  switch(js) {
-    case 'AUTOFOCUS':
-    case 'FULLCONTEXT':
-    case 'MEMORY':
-    case 'RAW':
-      return (js as Focus);
-    default:
-      return 'AUTOFOCUS';
-  }
+interface BoolSelector extends Selector<boolean> {
+  label: string;
+  title: string;
 }
 
-function FocusSelector(props: Selector<Focus>): JSX.Element {
+function AFormatSelector(props: BoolSelector): JSX.Element {
   const { value, setValue } = props;
+  const className = classes(
+    'wp-printer-field wp-printer-button',
+    value && 'selected'
+  );
   return (
-    <Select
-      value={value}
-      title='Autofocus Mode'
-      onChange={(v) => setValue(jFocus(v))}
+    <Item
+      className={className}
+      label={props.label}
+      title={props.title}
+      onClick={() => setValue(!value)}
     >
-      <option value='AUTOFOCUS'>Auto Focus</option>
-      <option value='FULLCONTEXT'>Full Context</option>
-      <option value='MEMORY'>Memory Model</option>
-      <option value='RAW'>Raw Proof</option>
-    </Select>
+      <Icon id={value ? 'SWITCH.ON' : 'SWITCH.OFF'} />
+    </Item>
   );
 }
 
@@ -77,13 +83,14 @@ function IFormatSelector(props: Selector<TIP.iformat>): JSX.Element {
   const { value, setValue } = props;
   return (
     <Select
+      className='wp-printer-field wp-printer-select'
       value={value}
-      title='Large Integers Format'
+      title='Large integers format.'
       onChange={(v) => setValue(TIP.jIformat(v))}
     >
-      <option value='dec'>Int</option>
-      <option value='hex'>Hex</option>
-      <option value='bin'>Bin</option>
+      <option value='dec' title='Integer'>int</option>
+      <option value='hex' title='Hex.'>hex</option>
+      <option value='bin' title='Binary'>bin</option>
     </Select>
   );
 }
@@ -92,84 +99,353 @@ function RFormatSelector(props: Selector<TIP.rformat>): JSX.Element {
   const { value, setValue } = props;
   return (
     <Select
+      className='wp-printer-field wp-printer-select'
       value={value}
-      title='Floatting Point Constants Format'
+      title='Floatting point format.'
       onChange={(v) => setValue(TIP.jRformat(v))}
     >
-      <option value='ratio'>Real</option>
-      <option value='float'>Float</option>
-      <option value='double'>Double</option>
+      <option value='ratio' title='Rational fraction'>frac</option>
+      <option value='float' title='IEEE Float 32-bits'>f32</option>
+      <option value='double' title='IEEE Float 64-bits'>f64</option>
     </Select>
   );
 }
 
 /* -------------------------------------------------------------------------- */
-/* --- TIP View                                                           --- */
+/* --- Node Path                                                          --- */
 /* -------------------------------------------------------------------------- */
 
-interface ProofState {
-  current: TIP.node | undefined;
-  index: number;
-  pending: number;
+interface NodeProps {
+  node: TIP.node;
+  current: Node;
+  parent?: boolean;
 }
 
-function useProofState(target: WP.goal | undefined): ProofState {
-  const DefaultProofState: ProofState = {
-    current: undefined, index: 0, pending: 0
-  };
-  return States.useRequest(
-    TIP.getProofState,
-    target,
-    { onError: DefaultProofState }
-  ) ?? DefaultProofState;
+function Node(props: NodeProps): JSX.Element
+{
+  const cellRef = React.useRef<HTMLLabelElement>(null);
+  const { node, parent } = props;
+  const current = node === props.current;
+  const debug = `#${node}`;
+  const {
+    title=debug, child='Script', header=debug,
+    proved=false, pending=0, size=0
+  } = States.useRequest(
+    TIP.getNodeInfos, node, { pending: null, onError: null }
+  ) ?? {};
+  const elt = cellRef.current;
+  React.useEffect(() => {
+    if (current && elt) elt.scrollIntoView();
+  }, [elt, current]);
+  const className = classes(
+    'wp-navbar-node',
+    parent && 'parent',
+    current && 'current',
+    !parent && !current && 'child',
+  );
+  const icon =
+    current
+    ? (parent ? 'TRIANGLE.DOWN' : 'TRIANGLE.RIGHT')
+    : (parent ? 'ANGLE.DOWN' : 'ANGLE.RIGHT');
+  const kind = proved ? 'positive' : (parent ? 'default' : 'warning');
+  const nodeName = parent ? `${child}: ${header}` : child;
+  const nodeRest = size <= 1 ? '?' : `${pending}/${size}`;
+  const nodeFull = size <= 1 ? nodeName : `${nodeName} (${size})`;
+  const nodeLabel = proved ? nodeFull : `${nodeName} (${nodeRest})`;
+  const proofState =
+    proved ? 'proved' :
+    pending < size ? `pending ${pending}/${size}` : 'unproved';
+  const fullTitle = `${title} (${proofState})`;
+  const onSelection = (): void => { Server.send(TIP.goToNode, node); };
+  return (
+    <Item
+      ref={cellRef} className={className}
+      icon={icon} kind={kind}
+      label={nodeLabel}
+      title={fullTitle}
+      onClick={onSelection}
+    />
+  );
+}
+
+interface NavBarProps {
+  above: TIP.node[];
+  below: TIP.node[];
+  current: Node;
+}
+
+function NavBar(props: NavBarProps): JSX.Element {
+  const { current } = props;
+  const parents = props.above.map(n => (
+    <Node key={n} node={n} parent current={current} />
+  )).reverse();
+  const children = props.below.map(n => (
+    <Node key={n} node={n} current={current} />
+  ));
+  return (
+    <Vbox className='wp-navbar'>
+      <Vbox>
+        {parents}
+        {children}
+      </Vbox>
+    </Vbox>
+  );
 }
 
-function useTarget(target: WP.goal | undefined) : WP.goalsData {
-  return States.useSyncArrayElt( WP.goals, target ) ?? WP.goalsDataDefault;
+/* -------------------------------------------------------------------------- */
+/* --- Available Provers                                                  --- */
+/* -------------------------------------------------------------------------- */
+
+interface ProverConfig {
+  available: WP.ProverInfosData[];
+  provers: WP.prover[];
+  setProvers: (prvs: WP.prover[]) => void;
+}
+
+export function popupProvers(config: ProverConfig): void {
+  const { available, provers, setProvers } = config;
+  Dome.popupMenu(
+    available.map(({ prover: id, descr: label }) => {
+      const checked = provers.some(p => p === id);
+      const updated =
+        checked ? provers.filter(p => p !== id) : provers.concat(id);
+      const onClick = (): void => setProvers(updated);
+      return { id, label, checked, onClick };
+    })
+  );
 }
 
+/* -------------------------------------------------------------------------- */
+/* --- TIP View                                                           --- */
+/* -------------------------------------------------------------------------- */
+
 export interface TIPProps {
   display: boolean;
   goal: WP.goal | undefined;
+  onClose: () => void;
 }
 
 export function TIPView(props: TIPProps): JSX.Element {
-  const { display, goal } = props;
-  const infos = useTarget(goal);
-  const { current, index, pending } = useProofState(goal);
-  const [ focus, setFocus ] = Dome.useWindowSettings<Focus>(
-    'wp.tip.focus', jFocus, 'AUTOFOCUS'
-  );
+  const { display, goal, onClose } = props;
+  // --- current goal
+  const infos =
+    States.useSyncArrayElt( WP.goals, goal ) ?? WP.goalsDataDefault;
+  // --- proof status
+  const {
+    current, above=[], below=[], index=-1, pending=0, tactic: nodeTactic
+  } = States.useRequest( TIP.getProofStatus, goal, { pending: null } ) ?? {};
+  // --- script status
+  const { saved=false, proof=false, script } =
+    States.useRequest( TIP.getScriptStatus, goal, { pending: null } ) ?? {};
+  // --- provers
+  const available = States.useSyncArrayData(WP.ProverInfos);
+  const [ provers=[], setProvers ] = States.useSyncState(WP.provers);
+  // --- sidebar & toolbar states
+  const [ copied, setCopied ] = React.useState(false);
+  const [ tactic, setTactic ] = React.useState<Tactic>();
+  const [ prover, setProver ] = React.useState<Prover>();
+  // --- printer settings
+  const [ autofocus, setAF ] = Dome.useBoolSettings('wp.tip.autofocus', true);
+  const [ memory, setMEM ] = Dome.useBoolSettings('wp.tip.unmangled', true);
   const [ iformat, setIformat ] = Dome.useWindowSettings<TIP.iformat>(
     'wp.tip.iformat', TIP.jIformat, 'dec'
   );
   const [ rformat, setRformat ] = Dome.useWindowSettings<TIP.rformat>(
     'wp.tip.rformat', TIP.jRformat, 'ratio'
   );
-  const autofocus = focus === 'AUTOFOCUS' || focus === 'MEMORY';
-  const unmangled = focus === 'MEMORY' || focus === 'RAW';
+
+  // --- Script buttons
+  const onSave = (): void => { Server.send(TIP.saveScript, goal); };
+  const onReload = (): void => { Server.send(TIP.runScript, goal); };
+  const onTrash = (): void => { Server.send(TIP.clearProofScript, goal); };
+  const onCopied = Dome.useDebounced(() => setCopied(false), 1000);
+  const copyTitle = copied ? 'Copied' : `Script: ${script} (Click to copy)`;
+  const onCopy = (): void => {
+    if (script) writeClipboardText(script);
+    setCopied(true);
+    onCopied();
+  };
+
+  // --- derived states
+  const locked = nodeTactic !== undefined;
+  const configuredTactic = nodeTactic ?? tactic;
+
+  // --- current prover
+  const onSelectedProver = React.useCallback((prv) => {
+    setTactic(undefined);
+    setProver(prv);
+  }, []);
+
+  // --- current tactic
+  const onSelectedTactic = React.useCallback((tac) => {
+    setTactic(tac);
+    setProver(undefined);
+  }, []);
+
+  // --- prover selection
+  const onProverSelection = (): void => {
+    popupProvers({ available, provers, setProvers });
+  };
+
+  // --- Delete button
+  const deleteAble = locked || above.length > 0;
+  const onDelete = (): void => {
+    const request = locked ? TIP.clearNodeTactic : TIP.clearParentTactic;
+    Server.send(request, current);
+  };
+  const deleteTitle = locked ? 'Cancel Node Tactic' : 'Cancel Last Tactic';
+
+  // --- Next button
+  const nextIndex =
+    pending <= 0 ? undefined :
+    pending === 1 ? (index === 0 ? undefined : 0) :
+    (index + 1 < pending ? index + 1 : 0);
+  const prevIndex =
+    pending <= 0 ? undefined :
+    pending === 1 ? (index === 0 ? undefined : 0) :
+    (index < 1 ? pending - 1 : index - 1);
+  const nextAble = goal !== undefined && nextIndex !== undefined;
+  const prevAble = goal !== undefined && prevIndex !== undefined;
+  const nextTitle =
+    nextIndex !== undefined
+    ? `Goto Next Pending Goal (#${nextIndex+1} / ${pending})`
+    : `Goto Next Pending Goal (if any)`;
+  const prevTitle =
+    prevIndex !== undefined
+    ? `Goto Previous Pending Goal (#${prevIndex+1} / ${pending})`
+    : `Goto Previous Pending Goal (if any)`;
+  const onNext = (): void => {
+    Server.send(TIP.goToIndex, [goal, nextIndex]);
+  };
+  const onPrev = (): void => {
+    Server.send(TIP.goToIndex, [goal, prevIndex]);
+  };
+
+  // --- Component
   return (
     <Vfill display={display}>
       <ToolBar>
         <Cell
           icon='HOME'
           label={infos.wpo} title='Goal identifier' />
-        <Cell
+        <Item
           icon='CODE'
           display={0 <= index && index < pending && 1 < pending}
           label={`${index+1}/${pending}`} title='Pending proof nodes'/>
-        <Cell {...getStatus(infos)}/>
+        <Item {...getStatus(infos)}/>
         <Filler/>
-        <FocusSelector value={focus} setValue={setFocus}/>
-        <IFormatSelector value={iformat} setValue={setIformat}/>
-        <RFormatSelector value={rformat} setValue={setRformat}/>
+        <IconButton
+          icon={copied ? 'DUPLICATE' : (saved ? 'FOLDER' : 'FOLDER.OPEN')}
+          visible={script !== undefined}
+          title={copyTitle}
+          onClick={onCopy} />
+        <ButtonGroup>
+          <Button
+            icon='RELOAD'
+            enabled={ script !== undefined && !saved }
+            title='Replay Proof Script from Disk'
+            onClick={onReload}
+          />
+          <Button
+            icon='SAVE'
+            enabled={ proof && !saved }
+            title='Save Proof Script on Disk'
+            onClick={onSave}
+          />
+        </ButtonGroup>
+        <ButtonGroup>
+          <Button
+            icon='MEDIA.PREV'
+            kind='negative'
+            enabled={deleteAble}
+            title={deleteTitle}
+            onClick={onDelete} />
+          <Button
+            icon='CROSS'
+            kind='negative'
+            enabled={ proof || script !== undefined }
+            title='Clear Proof and Remove Script (if any)'
+            onClick={onTrash}
+          />
+        </ButtonGroup>
+        <ButtonGroup>
+          <Button
+            icon='ANGLE.LEFT'
+            enabled={prevAble}
+            title={prevTitle}
+            onClick={onPrev} />
+          <Button
+            icon='EJECT'
+            title='Close Proof Transformer (back to goals)'
+            onClick={onClose} />
+          <Button
+            icon='ANGLE.RIGHT'
+            enabled={nextAble}
+            title={nextTitle}
+            onClick={onNext} />
+        </ButtonGroup>
+        <Button
+          icon='SETTINGS'
+          title='Active Provers Selection'
+          onClick={onProverSelection} />
       </ToolBar>
-      <GoalView
+      <Hfill>
+        <NavBar
+          above={above}
+          below={below}
+          current={current}
+        />
+        <Vfill className='dome-positionned'>
+          <Overlay display className='wp-printer'>
+            <Icon
+              id='LOCK'
+              title='Tactical Selection Locked'
+              className='wp-printer-locked'
+              display={locked} />
+            <AFormatSelector
+              value={autofocus} setValue={setAF}
+              label='AF' title='Autofocus mode.' />
+            <AFormatSelector
+              value={memory} setValue={setMEM}
+              label='MEM' title='Memory model internals.' />
+            <IFormatSelector value={iformat} setValue={setIformat}/>
+            <RFormatSelector value={rformat} setValue={setRformat}/>
+          </Overlay>
+          <GoalView
+            node={current}
+            locked={locked}
+            autofocus={autofocus}
+            unmangled={!memory}
+            iformat={iformat}
+            rformat={rformat}
+          />
+        </Vfill>
+        <Vbox className='wp-sidebar-view dome-color-frame'>
+          <Provers
+            node={current}
+            selected={prover}
+            setSelected={onSelectedProver}
+          />
+          <Tactics
+            goal={goal}
+            node={current}
+            locked={locked}
+            selected={configuredTactic}
+            setSelected={onSelectedTactic}
+          />
+        </Vbox>
+      </Hfill>
+      <ConfigureProver
+        node={current}
+        selected={prover}
+        setSelected={onSelectedProver}
+      />
+      <ConfigureTactic
+        goal={goal}
         node={current}
-        autofocus={autofocus}
-        unmangled={unmangled}
-        iformat={iformat}
-        rformat={rformat}
+        locked={locked}
+        selected={configuredTactic}
+        setSelected={onSelectedTactic}
       />
     </Vfill>
   );
diff --git a/ivette/src/frama-c/states.ts b/ivette/src/frama-c/states.ts
index 3e857376950ae9d44da9b3801bf9d45d93d9031d..13644db3a403696352168de1b8cc81d216b26b53 100644
--- a/ivette/src/frama-c/states.ts
+++ b/ivette/src/frama-c/states.ts
@@ -72,19 +72,18 @@ export interface UseRequestOptions<A> {
 
   Options can be used to tune more precisely the behavior of the hook.
  */
-export function useRequest<In, Out>(
-  rq: Server.GetRequest<In, Out>,
+export function useRequest<Kd extends Server.RqKind, In, Out>(
+  rq: Server.Request<Kd, In, Out>,
   params: In | undefined,
   options: UseRequestOptions<Out> = {},
 ): Out | undefined {
   const initial = options.offline ?? undefined;
   const [response, setResponse] = React.useState<Out | undefined>(initial);
-  const doUpdateResponse = React.useCallback(
+  const updateResponse = Dome.useProtected(
     (opt: Out | undefined | null): void => {
       if (opt !== null) setResponse(opt);
-    }, []);
-  const updateResponse = Dome.useActive(doUpdateResponse);
-
+    }
+  );
   // Fetch Request
   async function trigger(): Promise<void> {
     if (Server.isRunning() && params !== undefined) {
@@ -93,7 +92,7 @@ export function useRequest<In, Out>(
         const r = await Server.send(rq, params);
         updateResponse(r);
       } catch (error) {
-        if (options.onError !== undefined) {
+        if (options.onError === undefined) {
           D.error(`Fail in useRequest '${rq.name}'. ${error}`);
         }
         updateResponse(options.onError);
@@ -211,6 +210,7 @@ class SyncState<A> extends GlobalState<A | undefined> {
     super(undefined);
     this.handler = h;
     this.fetch = this.fetch.bind(this);
+    this.update = this.update.bind(this);
   }
 
   signal(): Server.Signal { return this.handler.signal; }
@@ -235,7 +235,24 @@ class SyncState<A> extends GlobalState<A | undefined> {
       }
     } catch (error) {
       D.error(
-        `Fail to update SyncState '${this.handler.name}'.`,
+        `Fail to fetch state '${this.handler.name}'.`,
+        `${error}`,
+      );
+      this.setValue(undefined);
+    }
+  }
+
+  async update(value: A): Promise<void> {
+    try {
+      if (Server.isRunning() && this.handler.setter) {
+        this.status = SyncStatus.Loading;
+        await Server.send(this.handler.setter, value);
+        this.status = SyncStatus.Loaded;
+        this.setValue(value);
+      }
+    } catch (error) {
+      D.error(
+        `Fail to update state '${this.handler.name}'.`,
         `${error}`,
       );
       this.setValue(undefined);
@@ -276,7 +293,8 @@ export function useSyncState<A>(
   const st = lookupSyncState(state);
   Server.useSignal(st.signal(), st.fetch);
   st.online();
-  return useGlobalState(st);
+  const [v] = useGlobalState(st);
+  return [v, st.update];
 }
 
 /** Synchronization with a (projectified) server value. */
diff --git a/ivette/src/renderer/Application.tsx b/ivette/src/renderer/Application.tsx
index 42501b3e8f700935a47e0029efe1655488504167..8cf3fe1e9c6190168c3419d781fd9c033cf2470e 100644
--- a/ivette/src/renderer/Application.tsx
+++ b/ivette/src/renderer/Application.tsx
@@ -83,7 +83,7 @@ export default function Application(): JSX.Element {
           settings="frama-c.labview"
         />
       </LSplit>
-      <Toolbar.ToolBar>
+      <Toolbar.ToolBar className="statusbar">
         <Controller.Status />
         <>{StatusBar}</>
         <Toolbar.Filler />
diff --git a/ivette/src/renderer/Sidebar.tsx b/ivette/src/renderer/Sidebar.tsx
index 8ac673ab020f27eef06be8137f4a7f58ea9823a6..1e5a41d4b5d0e2af0965795308e646dfbe642071 100644
--- a/ivette/src/renderer/Sidebar.tsx
+++ b/ivette/src/renderer/Sidebar.tsx
@@ -73,6 +73,7 @@ function Wrapper(props: WrapperProps): JSX.Element {
 
   return (
     <SideBar className={className}>
+      <div className="sidebar-ruler"/>
       <Catch label={props.id}>
         {props.children}
       </Catch>
@@ -121,7 +122,7 @@ export function Panel(): JSX.Element {
   );
 
   return (
-    <div className="sidebar-ruler">
+    <div className="sidebar-view">
       <div className={selectorClasses}>
         {items}
       </div>
diff --git a/ivette/src/renderer/style.css b/ivette/src/renderer/style.css
index b65e65bdfe1e361742681f4f44ed56452b703f1d..1fc9fc6ef8ef7a843480238b892c2407be800b9c 100644
--- a/ivette/src/renderer/style.css
+++ b/ivette/src/renderer/style.css
@@ -2,20 +2,25 @@
 /* --- Main Frame                                                         --- */
 /* -------------------------------------------------------------------------- */
 
-.sidebar-ruler {
+.sidebar-view {
     display: flex;
     height: 100%;
     overflow-x: hidden;
     overflow-y: auto;
 }
 
+.sidebar-ruler {
+    width: 120px;
+    height: 0px;
+}
+
 .sidebar-items.dome-color-frame
 {
     background: var(--background-intense) ;
 }
 
 .sidebar-selector {
-    min-width: 35px;    
+    min-width: 35px;
     text-align: center;
 }
 
@@ -52,6 +57,10 @@
     font-size: smaller;
 }
 
+.statusbar {
+    height: 26px;
+}
+
 /* -------------------------------------------------------------------------- */
 /* --- Styling LabViews                                                   --- */
 /* -------------------------------------------------------------------------- */
diff --git a/ivette/src/sandbox/icons.tsx b/ivette/src/sandbox/icons.tsx
index 1c8f3b23a45723dceb647fad90602e909f27e067..9cf1b1153066f79a45469a020841d4ac6fd3f72c 100644
--- a/ivette/src/sandbox/icons.tsx
+++ b/ivette/src/sandbox/icons.tsx
@@ -52,7 +52,7 @@ function Gallery(): JSX.Element {
   gallery.forEach((icons, section) => {
     sections.push(
       <Section key={section} defaultUnfold label={section}>
-        <Grid columns="auto auto">{icons}</Grid>
+        <Grid style={{ paddingLeft: 24 }} columns="auto auto">{icons}</Grid>
       </Section>
     );
   });
diff --git a/src/plugins/server/data.ml b/src/plugins/server/data.ml
index bdaa0d0c3158856866febda02f142ddcd1b5b644..c3229cdde4a054cfb8912468b514f1538d0ea104 100644
--- a/src/plugins/server/data.ml
+++ b/src/plugins/server/data.ml
@@ -593,6 +593,7 @@ sig
   val empty : 'a t
   val add : key -> 'a -> 'a t -> 'a t
   val find : key -> 'a t -> 'a
+  val remove : key -> 'a t -> 'a t
 end
 
 module type Index =
@@ -601,6 +602,7 @@ sig
   type tag
   val get : t -> tag
   val find : tag -> t
+  val remove : t -> unit
   val clear : unit -> unit
 end
 
@@ -611,6 +613,7 @@ sig
   val clear : index -> unit
   val get : index -> M.key -> int
   val find : index -> int -> M.key
+  val remove : index -> M.key -> unit
   val to_json : index -> M.key -> json
   val of_json : index -> json -> M.key
 end =
@@ -643,6 +646,13 @@ struct
       m.index <- M.add a id m.index ;
       Hashtbl.add m.lookup id a ; id
 
+  let remove m a =
+    try
+      let id = M.find a m.index in
+      Hashtbl.remove m.lookup id ;
+      m.index <- M.remove a m.index ;
+    with Not_found -> ()
+
   let find m id = Hashtbl.find m.lookup id
 
   let to_json m a = `Int (get m a)
@@ -662,6 +672,7 @@ struct
   let clear () = INDEX.clear index
   let get = INDEX.get index
   let find = INDEX.find index
+  let remove = INDEX.remove index
   include
     (struct
       type t = M.key
@@ -694,6 +705,7 @@ struct
 
   let index () = STATE.get ()
   let clear () = INDEX.clear (index())
+  let remove a = INDEX.remove (index()) a
 
   let get a = INDEX.get (index()) a
   let find id = INDEX.find (index()) id
@@ -761,6 +773,11 @@ struct
     let hash = lookup () in
     if not (Hashtbl.mem hash tag) then Hashtbl.add hash tag x ; tag
 
+  let remove x =
+    let tag = A.id x in
+    let hash = lookup () in
+    Hashtbl.remove hash tag
+
   include
     (struct
       type t = A.t
diff --git a/src/plugins/server/data.mli b/src/plugins/server/data.mli
index c9fe14a36a55800db6d21a507ce99a6cf9d3d637..36a02e94a652e4255a27648b4034323a1bc0dd55 100644
--- a/src/plugins/server/data.mli
+++ b/src/plugins/server/data.mli
@@ -357,6 +357,7 @@ sig
   val empty : 'a t
   val add : key -> 'a -> 'a t -> 'a t
   val find : key -> 'a t -> 'a
+  val remove : key -> 'a t -> 'a t
 end
 
 (** Datatype extended with access to value identifiers. *)
@@ -368,6 +369,9 @@ sig
   val find : tag -> t
   (** @raise Not_found if not registered. *)
 
+  val remove : t -> unit
+  (** Remove item from index tables. Use with extreme care. *)
+
   val clear : unit -> unit
   (** Clear index tables. Use with extreme care. *)
 end
diff --git a/src/plugins/server/main.ml b/src/plugins/server/main.ml
index 9363447b71ea9d618e8682575207ebef3f9af3f1..8e5c71255249e55f85e0ba1f708a8c1d572ffd68 100644
--- a/src/plugins/server/main.ml
+++ b/src/plugins/server/main.ml
@@ -381,10 +381,19 @@ let in_range ~min:a ~max:b v = min (max a v) b
 
 let kill () = raise Killed
 
+let rooters = ref []
 let daemons = ref []
+let once callback = rooters := !rooters @ [ callback ]
 let on callback = daemons := !daemons @ [ callback ]
 let set_active activity =
-  List.iter (fun f -> try f activity with _ -> ()) !daemons
+  begin
+    if activity then
+      begin
+        List.iter (fun f -> try f () with _ -> ()) !rooters ;
+        rooters := [] ;
+      end ;
+    List.iter (fun f -> try f activity with _ -> ()) !daemons ;
+  end
 
 let create ~pretty ?(equal=(=)) ~fetch () =
   let polling = in_range ~min:1 ~max:200 (Senv.Polling.get ()) in
diff --git a/src/plugins/server/main.mli b/src/plugins/server/main.mli
index 9215a456db54f9aa3020f2401f945b964f1e678e..94bf6707567fe04c70e9e3b174887e2658b45161 100644
--- a/src/plugins/server/main.mli
+++ b/src/plugins/server/main.mli
@@ -141,6 +141,12 @@ val on_signal : signal -> (bool -> unit) -> unit
     Callbacks shall {i never} raise any exception. *)
 val on : (bool -> unit) -> unit
 
+(** Register a callback to listen for server initialization. All callbacks are
+    executed once, in their order of registration, and before activity
+    callbacks.
+    Callbacks shall {i never} raise any exception. *)
+val once : (unit -> unit) -> unit
+
 (** Register an asynchronous task on the server.
     When the server is not working in background, this is
     equivalent to [Task.call] ; otherwize,
diff --git a/src/plugins/server/states.ml b/src/plugins/server/states.ml
index 9d9ccc7357709b48a4965bb547839e161cb26e12..b7cfc0b2018bffb1107cbd114cdff273cf5dd714 100644
--- a/src/plugins/server/states.ml
+++ b/src/plugins/server/states.ml
@@ -192,6 +192,7 @@ type 'a array = {
   key : 'a -> string ;
   iter : ('a -> unit) -> unit ;
   getter : (string * ('a -> json option)) list ;
+  preload : ('a -> unit) option ;
   (* [LC+JS]
      The two following fields allow to keep an array in sync
      with the current project and still have a polymorphic data type. *)
@@ -267,13 +268,14 @@ type buffer = {
   mutable updated : json list ;
 }
 
-let add_entry buffer cols fkey key v =
+let add_entry array buffer key v =
+  Option.iter (fun f -> f v) array.preload ;
   let fjs = List.fold_left (fun fjs (fd,to_json) ->
       match to_json v with
       | Some js -> (fd , js) :: fjs
       | None | exception Not_found -> fjs
-    ) [] cols in
-  let row = (fkey, `String key) :: fjs in
+    ) [] array.getter in
+  let row = (array.fkey, `String key) :: fjs in
   buffer.updated <- `Assoc row :: buffer.updated ;
   buffer.capacity <- pred buffer.capacity
 
@@ -281,9 +283,9 @@ let remove_entry buffer key =
   buffer.removed <- key :: buffer.removed ;
   buffer.capacity <- pred buffer.capacity
 
-let update_entry buffer cols fkey key = function
+let update_entry array buffer key = function
   | Remove -> remove_entry buffer key
-  | Add v -> add_entry buffer cols fkey key v
+  | Add v -> add_entry array buffer key v
 
 let fetch array n =
   let m = content array in
@@ -303,7 +305,7 @@ let fetch array n =
           begin fun v ->
             let key = array.key v in
             if buffer.capacity > 0 then
-              add_entry buffer array.getter array.fkey key v
+              add_entry array buffer key v
             else
               ( m.updates <- Kmap.add key (Add v) m.updates ;
                 buffer.pending <- succ buffer.pending ) ;
@@ -313,7 +315,7 @@ let fetch array n =
       m.updates <- Kmap.filter
           begin fun key upd ->
             if buffer.capacity > 0 then
-              ( update_entry buffer array.getter array.fkey key upd ; false )
+              ( update_entry array buffer key upd ; false )
             else
               ( buffer.pending <- succ buffer.pending ; true )
           end m.updates ;
@@ -334,6 +336,7 @@ let register_array ~package ~name ~descr ~key
     ?(keyName="key")
     ?(keyType=Package.Jkey name)
     ~(iter : 'a callback)
+    ?(preload : ('a -> unit) option)
     ?(add_update_hook : 'a callback option)
     ?(add_remove_hook : 'a callback option)
     ?(add_reload_hook : unit callback option)
@@ -375,7 +378,7 @@ let register_array ~package ~name ~descr ~key
   let getter =
     List.map Package.(fun (fd,to_js) -> fd.fd_name , to_js) !model in
   let array = {
-    fkey = keyName ; key ; iter ; getter ; signal ;
+    fkey = keyName ; key ; iter ; preload ; getter ; signal ;
     current = None ; projects = Hashtbl.create 0
   } in
   let signature = Request.signature ~input:(module Jint) () in
diff --git a/src/plugins/server/states.mli b/src/plugins/server/states.mli
index ff52812c698287bced7066dda914765051118b63..1d73e64cf1e57c3026063b50eed84d716251f3eb 100644
--- a/src/plugins/server/states.mli
+++ b/src/plugins/server/states.mli
@@ -161,6 +161,10 @@ val signal : 'a array -> Request.signal
     Columns added to the model after registration are {i not} taken into
     account.
 
+    The optional [~preload] function will be called just before
+    every column getters. Notice that column getters are called in
+    registration order.
+
     If provided, the [~add_xxx_hook] options are used to register hooks
     to notify the server of corresponding array updates.
     Each hook will be installed only once the client starts to listen for array
@@ -177,6 +181,7 @@ val register_array :
   ?keyName:string ->
   ?keyType:jtype ->
   iter:('a callback) ->
+  ?preload:('a -> unit) ->
   ?add_update_hook:('a callback) ->
   ?add_remove_hook:('a callback) ->
   ?add_reload_hook:(unit callback) ->
diff --git a/src/plugins/wp/Cache.ml b/src/plugins/wp/Cache.ml
index 3ee82fe8b4aea89b7d1ec7a6d6de393a42d5f709..090482b711c92fca8f90279b30762822e69779d1 100644
--- a/src/plugins/wp/Cache.ml
+++ b/src/plugins/wp/Cache.ml
@@ -37,7 +37,8 @@ let get_miss () = !miss
 let get_removed () = !removed
 
 let mark_cache ~mode hash =
-  if mode = Cleanup || Fc_config.is_gui then Hashtbl.replace cleanup hash ()
+  if mode = Cleanup || Wp_parameters.is_interactive () then
+    Hashtbl.replace cleanup hash ()
 
 module CACHEDIR = WpContext.StaticGenerator(Datatype.Unit)
     (struct
diff --git a/src/plugins/wp/ProofEngine.ml b/src/plugins/wp/ProofEngine.ml
index 7bb611619a4158a1763e98c00dbf3b052746d62b..f2b8583fdf6e9359e7084c203252befb03b22c62 100644
--- a/src/plugins/wp/ProofEngine.ml
+++ b/src/plugins/wp/ProofEngine.ml
@@ -27,9 +27,10 @@
 type node = {
   tree : Wpo.t ; (* root, to check consistency *)
   goal : Wpo.t ; (* only GoalAnnot of a sequent *)
+  child : string option ; (* child name from parent node *)
   parent : node option ;
   mutable script : script ;
-  mutable stats : Stats.stats ;
+  mutable stats : Stats.stats option ; (* memoized *)
   mutable strategy : string option ; (* hint *)
   mutable search_index : int ;
   mutable search_space : Strategy.t array ; (* sorted by priority *)
@@ -53,6 +54,7 @@ type tree = {
   main : Wpo.t ; (* Main goal to be proved. *)
   mutable pool : Lang.F.pool option ; (* Global pool variable *)
   mutable saved : bool ; (* Saved on Disk. *)
+  mutable dirty : bool ; (* Tactical result is outdated *)
   mutable gid : int ; (* WPO goal numbering *)
   mutable head : node option ; (* the current node *)
   mutable root : node option ; (* the root node *)
@@ -70,24 +72,76 @@ module PROOFS = WpContext.StaticGenerator(Wpo.S)
           pool = None ;
           head = None ;
           root = None ;
+          dirty = true ;
           saved = false ;
         }
     end)
 
-let () = Wpo.add_removed_hook PROOFS.remove
-let () = Wpo.add_cleared_hook PROOFS.clear
+module NODES = WpContext.Static
+    (struct
+      type key = Wpo.t
+      type data = node
+      let name = "Wp.ProofEngine.Nodes"
+      let compare = Wpo.S.compare
+      let pretty = Wpo.S.pretty
+    end)
+
+(* -------------------------------------------------------------------------- *)
+(* --- Signaling                                                          --- *)
+(* -------------------------------------------------------------------------- *)
+
+let goal_hooks = ref []
+let clear_hooks = ref []
+let remove_hooks = ref []
+let update_hooks = ref []
+
+let add_goal_hook fn = goal_hooks := !goal_hooks @ [fn]
+let add_clear_hook fn = clear_hooks := !clear_hooks @ [fn]
+let add_remove_hook fn = remove_hooks := !remove_hooks @ [fn]
+let add_update_hook fn = update_hooks := !update_hooks @ [fn]
+
+let signal_goal g = List.iter (fun fn -> fn g) !goal_hooks
+let signal_node n = List.iter (fun fn -> fn n) !update_hooks
+
+let dirty_root g =
+  try
+    let tree = PROOFS.find g in
+    if not tree.dirty then
+      begin
+        tree.dirty <- true ;
+        signal_goal g ;
+      end
+  with Not_found -> ()
+
+let rec dirty_node n =
+  match n.stats with
+  | None -> ()
+  | Some _ ->
+    n.stats <- None ;
+    signal_node n ;
+    match n.parent with
+    | Some p -> dirty_node p
+    | None -> dirty_root n.tree
+
+let self_updating = ref false
+
+let dirty_goal g =
+  if not !self_updating then
+    match NODES.get g with
+    | Some n -> dirty_node n
+    | None -> dirty_root g
 
 let get wpo =
   try
-    let proof = PROOFS.get wpo in
+    let proof = PROOFS.find wpo in
     match proof.root with
     | None | Some { script = Opened | Script _ } -> raise Not_found
     | Some { script = Tactic _ } -> if proof.saved then `Saved else `Proof
   with Not_found ->
     if ProofSession.exists wpo then `Script else `None
 
-let iter_all f ns = List.iter (fun (_,n) -> f n) ns
-let map_all f ns = List.map (fun (k,n) -> k,f n) ns
+let iter_children f ns = List.iter (fun m -> f (snd m)) ns
+let map_children f ns = List.map (fun m -> fst m,f m) ns
 
 let pool tree =
   match tree.pool with
@@ -98,47 +152,99 @@ let pool tree =
     tree.pool <- Some pool ; pool
 
 (* -------------------------------------------------------------------------- *)
-(* --- Constructors                                                       --- *)
+(* --- Proofs                                                             --- *)
 (* -------------------------------------------------------------------------- *)
 
 let proof ~main =
   assert (not (Wpo.is_tactic main)) ;
   PROOFS.get main
 
-let rec reset_node n =
-  Wpo.clear_results n.goal ;
-  if Wpo.is_tactic n.goal then Wpo.remove n.goal ;
+let saved t = t.saved
+let set_saved t s = t.saved <- s ; signal_goal t.main
+
+(* -------------------------------------------------------------------------- *)
+(* --- Removal                                                            --- *)
+(* -------------------------------------------------------------------------- *)
+
+let rec revert_tactic t n =
   n.strategy <- None ;
   match n.script with
   | Opened | Script _ -> ()
   | Tactic(_,children) ->
-    n.script <- Opened ; iter_all reset_node children
-
-let reset_root = function None -> () | Some n -> reset_node n
-
-let reset t =
+    t.saved <- false ;
+    n.script <- Opened ;
+    iter_children (remove_node t) children
+
+and remove_node t n =
+  NODES.remove n.goal ;
+  if Wpo.is_tactic n.goal then
+    Wpo.remove n.goal
+  else
+    Wpo.clear_results n.goal ;
+  List.iter (fun f -> f n) !remove_hooks ;
+  Option.iter (fun n0 -> if n0 == n then t.head <- None) t.head ;
+  revert_tactic t n
+
+let clear_tree t =
   begin
-    Wpo.clear_results t.main ;
-    reset_root t.root ;
+    Option.iter (remove_node t) t.root ;
     t.gid <- 0 ;
     t.head <- None ;
     t.root <- None ;
     t.saved <- false ;
+    List.iter (fun fn -> fn t.main) !clear_hooks ;
+    signal_goal t.main ;
   end
 
-let clear w = if PROOFS.mem w then reset (PROOFS.get w)
+let clear_node_tactic t n =
+  revert_tactic t n ; dirty_node n ;
+  if t.head = None then t.head <- Some n
 
-let saved t = t.saved
-let set_saved t s = t.saved <- s
+let clear_parent_tactic t n =
+  match n.parent with
+  | None -> clear_tree t
+  | Some p as h -> revert_tactic t p ; dirty_node p ; t.head <- h
+
+let clear_node t n =
+  Wpo.clear_results n.goal ;
+  clear_node_tactic t n
+
+let clear_goal w =
+  try clear_tree (PROOFS.find w)
+  with Not_found -> Wpo.clear_results w
+
+(* -------------------------------------------------------------------------- *)
+(* --- Removal From Wpo                                                   --- *)
+(* -------------------------------------------------------------------------- *)
+
+let removed_from_wpo g =
+  if not @@ Wpo.is_tactic g then
+    begin
+      try
+        clear_tree (PROOFS.find g) ;
+        PROOFS.remove g ;
+      with Not_found ->
+        signal_goal g
+    end
+
+let cleared_from_wpo () =
+  begin
+    NODES.clear () ;
+    PROOFS.clear () ;
+  end
+
+let () = Wpo.add_removed_hook removed_from_wpo
+let () = Wpo.add_cleared_hook cleared_from_wpo
+let () = Wpo.add_modified_hook dirty_goal
 
 (* -------------------------------------------------------------------------- *)
 (* --- Walking                                                            --- *)
 (* -------------------------------------------------------------------------- *)
 
 let rec walk f node =
-  if not (Wpo.is_valid node.goal) then
+  if not (Wpo.is_locally_valid node.goal) then
     match node.script with
-    | Tactic (_,children) -> iter_all (walk f) children
+    | Tactic (_,children) -> iter_children (walk f) children
     | Opened | Script _ -> f node
 
 let iteri f tree =
@@ -157,50 +263,79 @@ let rec depth node =
 (* --- Consolidating                                                      --- *)
 (* -------------------------------------------------------------------------- *)
 
-let proved n = Wpo.is_valid n.goal
-
 let pending n =
   let k = ref 0 in
   walk (fun _ -> incr k) n ; !k
 
-let is_prover_result (p,_) = p <> VCS.Tactical
+let locally_proved n = Wpo.is_locally_valid n.goal
+
+let fully_proved n =
+  let exception HasPending in
+  try walk (fun _ -> raise HasPending) n ; true
+  with HasPending -> false
+
+let is_prover (p,_) = VCS.is_prover p
 
 let prover_stats ~smoke goal =
   Stats.results ~smoke @@
-  List.filter is_prover_result @@
+  List.filter is_prover @@
   Wpo.get_results goal
 
 let rec consolidate ~smoke n =
-  let s =
-    if Wpo.is_valid n.goal then
-      prover_stats ~smoke n.goal
-    else
-      match n.script with
-      | Opened | Script _ -> prover_stats ~smoke n.goal
-      | Tactic(_,children) ->
-        let qed = Wpo.qed_time n.goal in
-        let results = List.map (fun (_,n) -> consolidate ~smoke n) children in
-        Stats.tactical ~qed results
-  in n.stats <- s ; s
-
-let validate tree =
+  match n.stats with
+  | Some s -> s
+  | None -> let s = compute ~smoke n in n.stats <- Some s ; s
+
+and compute ~smoke n =
+  if Wpo.is_locally_valid n.goal then
+    prover_stats ~smoke n.goal
+  else
+    match n.script with
+    | Opened | Script _ -> prover_stats ~smoke n.goal
+    | Tactic(_,children) ->
+      Stats.tactical ~qed:(Wpo.qed_time n.goal) @@
+      List.map (fun (_,n) -> consolidate ~smoke n) children
+
+let tactical tree =
   match tree.root with
-  | None -> ()
+  | None -> VCS.no_result
   | Some root ->
-    let main = tree.main in
-    if not (Wpo.is_valid main) then
-      let stats = consolidate ~smoke:(Wpo.is_smoke_test main) root in
-      Wpo.set_result tree.main Tactical (Stats.script stats)
+    let smoke = Wpo.is_smoke_test tree.main in
+    Stats.script @@ consolidate ~smoke root
+
+let validate tree =
+  if tree.dirty then
+    let result =
+      if Wpo.is_locally_valid tree.main then VCS.no_result else tactical tree
+    in
+    try
+      self_updating := true ;
+      Wpo.set_result tree.main VCS.Tactical result ;
+      tree.dirty <- false ;
+      self_updating := false ;
+    with exn ->
+      self_updating := false ;
+      raise exn
 
 let consolidated wpo =
   let smoke = Wpo.is_smoke_test wpo in
   try
-    if smoke || not (PROOFS.mem wpo) then raise Not_found ;
-    match PROOFS.get wpo with
-    | { root = Some { stats ; script = Tactic _ } } -> stats
+    match (PROOFS.find wpo).root with
+    | Some node -> consolidate ~smoke node
     | _ -> raise Not_found
   with Not_found -> prover_stats ~smoke wpo
 
+let consolidate wpo =
+  try validate (PROOFS.find wpo) with Not_found -> ()
+
+let results wpo = consolidate wpo ; Wpo.get_results wpo
+
+let stats node =
+  match node.stats with Some s -> s | None ->
+    let smoke = Wpo.is_smoke_test node.tree in
+    let s = compute ~smoke node in
+    node.stats <- Some s ; s
+
 (* -------------------------------------------------------------------------- *)
 (* --- Accessors                                                          --- *)
 (* -------------------------------------------------------------------------- *)
@@ -213,23 +348,42 @@ let head_goal t = match t.head with
 let tree n = proof ~main:n.tree
 let goal n = n.goal
 
-let stats n = n.stats
 let tree_context t = Wpo.get_context t.main
 let node_context n = Wpo.get_context n.goal
 let parent n = n.parent
 let title n = n.goal.Wpo.po_name
+
 let tactical n =
   match n.script with
   | Tactic(tactic,_) -> Some tactic
   | Opened | Script _ -> None
+
 let get_strategies n = n.search_index , n.search_space
 let set_strategies n ?(index=0) hs =
-  n.search_index <- index ; n.search_space <- hs
+  n.search_index <- index ; n.search_space <- hs ; signal_node n
+
 let children n =
   match n.script with
   | Tactic(_,children) -> children
   | Opened | Script _ -> []
 
+let subgoals n = List.map snd @@ children n
+
+let rec path n = match n.parent with
+  | None -> []
+  | Some p -> p::path p
+
+let child_label n = n.child
+
+let tactic_label n =
+  match n.script with Tactic({ header }, _)  -> Some header | _ -> None
+
+let tactic n =
+  match n.script with
+  | Tactic({ tactic }, _)  ->
+    begin try Some (Tactical.lookup ~id:tactic) with Not_found -> None end
+  | _ -> None
+
 (* -------------------------------------------------------------------------- *)
 (* --- State & Status                                                     --- *)
 (* -------------------------------------------------------------------------- *)
@@ -246,7 +400,7 @@ type status = [
 let status t : status =
   match t.root with
   | None ->
-    if Wpo.is_valid t.main
+    if Wpo.is_locally_valid t.main
     then if Wpo.is_smoke_test t.main then `Invalid else `Proved
     else if Wpo.is_smoke_test t.main then `Passed else `Unproved
   | Some root ->
@@ -255,6 +409,9 @@ let status t : status =
       if Wpo.is_smoke_test t.main then `Passed else `Unproved
     | Tactic _ ->
       let n = pending root in
+      if n = 0 then
+        if Wpo.is_smoke_test t.main then `Invalid else `Proved
+      else
       if Wpo.is_smoke_test t.main then `StillResist n else `Pending n
 
 (* -------------------------------------------------------------------------- *)
@@ -273,41 +430,38 @@ let current t : current =
 type position = [ `Main | `Node of node | `Leaf of int ]
 let goto t = function
   | `Main ->
-    t.head <- t.root
+    t.head <- t.root ; signal_goal t.main
   | `Node n ->
-    if n.tree == t.main then t.head <- Some n
+    if n.tree == t.main then t.head <- Some n ; signal_goal t.main
   | `Leaf k ->
     t.head <- t.root ;
-    iteri (fun i n -> if i = k then t.head <- Some n) t
+    iteri (fun i n -> if i = k then t.head <- Some n) t ;
+    signal_goal t.main
 
 let fetch t node =
   try
     t.head <- t.root ;
-    walk (fun n -> t.head <- Some n ; raise Exit) node ; false
+    walk (fun n -> t.head <- Some n ; raise Exit) node ;
+    false
   with Exit -> true
 
 let rec forward t =
   match t.head with
-  | None -> t.head <- t.root
+  | None -> t.head <- t.root ; signal_goal t.main
   | Some hd ->
-    if not (fetch t hd) then
-      begin
-        t.head <- hd.parent ;
-        forward t ;
-      end
+    if not (fetch t hd)
+    then ( t.head <- hd.parent ; forward t )
+    else signal_goal t.main
 
-let remove t node =
-  begin
-    Wpo.clear_results node.goal ;
-    t.head <- node.parent ;
-    if t.head = None then t.root <- None ;
-    reset_node node ;
-  end
+let cancel_parent_tactic t =
+  match t.head with
+  | None -> ()
+  | Some n -> clear_parent_tactic t n
 
-let cancel t =
+let cancel_current_node t =
   match t.head with
   | None -> ()
-  | Some node -> remove t node
+  | Some node -> clear_node t node
 
 (* -------------------------------------------------------------------------- *)
 (* --- Sub-Goal                                                           --- *)
@@ -330,29 +484,19 @@ let mk_goal t ~title ~part ~axioms sequent =
       po_formula = mk_formula ~main:t.main.po_formula axioms sequent ;
     })
 
-let mk_tree_node ~tree ~anchor goal = {
-  tree = tree.main ; goal ;
-  parent = Some anchor ;
-  script = Opened ;
-  stats = Stats.empty ;
-  search_index = 0 ;
-  search_space = [| |] ;
-  strategy = None ;
-}
-
-let mk_root_node goal = {
-  tree = goal ; goal ;
-  parent = None ;
-  script = Opened ;
-  stats = Stats.empty ;
-  strategy = None ;
-  search_index = 0 ;
-  search_space = [| |] ;
-}
+let mk_node ~main ?parent ?child goal =
+  let node = {
+    tree=main ; parent ; child ; goal ;
+    script = Opened ;
+    stats = None ;
+    search_index = 0 ;
+    search_space = [| |] ;
+    strategy = None ;
+  } in NODES.define goal node ; node
 
 let mk_root ~tree =
-  let goal = tree.main in
-  let node = mk_root_node goal in
+  let main = tree.main in
+  let node = mk_node ~main main in
   let root = Some node in
   tree.root <- root ;
   tree.head <- root ;
@@ -385,7 +529,7 @@ struct
         (fun (part,s) -> part , mk_goal tree ~title ~part ~axioms s) dseqs
     in { tree ; tactic ; anchor ; goals }
 
-  let iter f w = iter_all f w.goals
+  let iter f w = iter_children f w.goals
 
   let header frk = frk.tactic.ProofScript.header
 end
@@ -411,35 +555,37 @@ let anchor tree ?node () =
 let commit fork =
   List.iter (fun (_,wp) -> ignore (Wpo.resolve wp)) fork.Fork.goals ;
   let tree = fork.Fork.tree in
-  let anchor = fork.Fork.anchor in
-  let children = map_all (mk_tree_node ~tree ~anchor) fork.Fork.goals in
+  let parent = fork.Fork.anchor in
+  let subnode (child,goal) = mk_node ~main:tree.main ~parent ~child goal in
+  let children = map_children subnode fork.Fork.goals in
   tree.saved <- false ;
-  anchor.script <- Tactic( fork.Fork.tactic , children ) ;
-  anchor , children
+  parent.script <- Tactic( fork.Fork.tactic , children ) ;
+  dirty_node parent ;
+  parent , children
 
 (* -------------------------------------------------------------------------- *)
 (* --- Scripting                                                          --- *)
 (* -------------------------------------------------------------------------- *)
 
-let results wpo =
-  List.map (fun (p,r) -> ProofScript.a_prover p r) (Wpo.get_results wpo)
+let script_provers wpo =
+  List.map (fun (p,r) -> ProofScript.a_prover p r) @@
+  Wpo.get_prover_results wpo
 
 let rec script_node (node : node) =
-  let provers = results node.goal in
-  let scripts =
+  script_provers node.goal @
+  begin
     match node.script with
     | Script s -> List.filter ProofScript.is_tactic s
     | Tactic( tactic , children ) ->
       [ ProofScript.a_tactic tactic (List.map subscript_node children) ]
     | Opened -> []
-  in
-  provers @ scripts
+  end
 
 and subscript_node (key,node) = key , script_node node
 
 let script tree =
   match tree.root with
-  | None -> results tree.main
+  | None -> script_provers tree.main
   | Some node -> script_node node
 
 let bind node script =
@@ -449,14 +595,39 @@ let bind node script =
     ()
   | Opened | Script _ ->
     (*TODO: saveback the previous script *)
-    node.script <- Script script
+    node.script <- Script script ;
+    signal_node node ;
+    signal_goal node.tree
 
 let bound node =
   match node.script with
   | Tactic _ | Opened -> []
   | Script s -> s
 
+let is_script_result ~margin (prv,res) =
+  VCS.is_extern prv && VCS.is_valid res &&
+  res.prover_time > margin
+
+let has_result tree =
+  let margin = float_of_string @@ Wp_parameters.TimeMargin.get () in
+  let results = Wpo.get_results tree.main in
+  List.exists (is_script_result ~margin) results
+
+let has_tactics tree =
+  match tree.root with
+  | None -> false
+  | Some node ->
+    match node.script with
+    | Opened -> false
+    | Tactic _ -> true
+    | Script s -> List.exists ProofScript.is_tactic s
+
+let has_script tree = has_tactics tree || has_result tree
+let has_proof wpo =
+  try has_script @@ PROOFS.find wpo
+  with Not_found -> false
+
 let get_hint node = node.strategy
-let set_hint node strategy = node.strategy <- Some strategy
+let set_hint node strategy = node.strategy <- Some strategy ; signal_node node
 
 (* -------------------------------------------------------------------------- *)
diff --git a/src/plugins/wp/ProofEngine.mli b/src/plugins/wp/ProofEngine.mli
index 5fdb346c81be8070ef5fb335450a5a36d9c04788..8e10e532600d53355127561f064e6d0f7458f19d 100644
--- a/src/plugins/wp/ProofEngine.mli
+++ b/src/plugins/wp/ProofEngine.mli
@@ -41,15 +41,19 @@ end
 
 val get : Wpo.t -> [ `Script | `Proof | `Saved | `None ]
 val proof : main:Wpo.t -> tree
-val reset : tree -> unit
-val clear : Wpo.t -> unit
 
-(** Re-compute stats & set status of the entire script *)
+(** Consolidate and update status of the entire script (memoized). *)
 val validate : tree -> unit
 
-(** Consolidate statistics wrt current script or prover results *)
+(** Consolidate proof tree (memoized). *)
+val consolidate : Wpo.t -> unit
+
+(** Consolidated statistics (memoized). *)
 val consolidated : Wpo.t -> Stats.stats
 
+(** Consolidated results (memoized). *)
+val results : Wpo.t -> (VCS.prover * VCS.result) list
+
 (** Leaves are numbered from 0 to n-1 *)
 
 type status = [
@@ -80,11 +84,17 @@ val tree_context : tree -> WpContext.t
 val node_context : node -> WpContext.t
 
 val title : node -> string
-val proved : node -> bool
+val fully_proved : node -> bool
+val locally_proved : node -> bool
 val pending : node -> int
 val stats : node -> Stats.stats
-val parent : node -> node option
 val depth : node -> int
+val path : node -> node list
+val parent : node -> node option
+val tactic : node -> Tactical.t option
+val child_label : node -> string option
+val tactic_label : node -> string option
+val subgoals : node -> node list
 val children : node -> (string * node) list
 val tactical : node -> ProofScript.jtactic option
 val get_strategies : node -> int * Strategy.t array (* current index *)
@@ -93,8 +103,19 @@ val get_hint : node -> string option
 val set_hint : node -> string -> unit
 
 val forward : tree -> unit
-val cancel : tree -> unit
-val remove : tree -> node -> unit
+val clear_goal : Wpo.t -> unit
+val clear_tree : tree -> unit
+val clear_node : tree -> node -> unit
+val clear_node_tactic : tree -> node -> unit
+val clear_parent_tactic : tree -> node -> unit
+
+val add_goal_hook : (Wpo.t -> unit) -> unit
+val add_clear_hook : (Wpo.t -> unit) -> unit
+val add_remove_hook : (node -> unit) -> unit
+val add_update_hook : (node -> unit) -> unit
+
+val cancel_parent_tactic : tree -> unit
+val cancel_current_node : tree -> unit
 
 type fork
 val anchor : tree -> ?node:node -> unit -> node
@@ -103,6 +124,8 @@ val iter : (Wpo.t -> unit) -> fork -> unit
 val commit : fork -> node * (string * node) list
 val pretty : Format.formatter -> fork -> unit
 
+val has_proof : Wpo.t -> bool
+val has_script : tree -> bool
 val script : tree -> ProofScript.jscript
 val bind : node -> ProofScript.jscript -> unit
 val bound : node -> ProofScript.jscript
diff --git a/src/plugins/wp/ProverScript.ml b/src/plugins/wp/ProverScript.ml
index 5d9686beb84f92aa96946e05e5da760662cb2c03..7f4a4c06386f9693d09b5ff0b2796cb3b10111aa 100644
--- a/src/plugins/wp/ProverScript.ml
+++ b/src/plugins/wp/ProverScript.ml
@@ -449,9 +449,9 @@ let apply env node jtactic subscripts =
   | Some fork ->
     let _,children = ProofEngine.commit fork in
     reconcile children subscripts ; (*TODO: saveback forgiven script ? *)
-    let ok = List.for_all
-        (fun (_,node) -> ProofEngine.proved node)
-        children in
+    let ok =
+      List.for_all
+        (fun (_,node) -> ProofEngine.locally_proved node) children in
     if ok then [] else children
 
 (* -------------------------------------------------------------------------- *)
@@ -524,7 +524,7 @@ let rec process env node =
   schedule
     begin fun () ->
       Wp_parameters.debug ~dkey:dkey_pp_allgoals "%a" (pp_subgoal env) node ;
-      if ProofEngine.proved node then
+      if ProofEngine.locally_proved node then
         begin
           env.pending <- pred env.pending ;
           Env.validate env ;
diff --git a/src/plugins/wp/ProverTask.mli b/src/plugins/wp/ProverTask.mli
index 8ff7feaf7f467e47cdd23960747b4610aab0111c..e617e446ed679722af845fa55773beef1ce6dbe9 100644
--- a/src/plugins/wp/ProverTask.mli
+++ b/src/plugins/wp/ProverTask.mli
@@ -86,6 +86,7 @@ class virtual command : string ->
 
   end
 
+(** If provided, the number of procs is forwarded to the Why3 and the server *)
 val server : ?procs:int -> unit -> Task.server
 
 val schedule : 'a Task.task -> unit
diff --git a/src/plugins/wp/TacArray.ml b/src/plugins/wp/TacArray.ml
index bab1eb2dfbb2197d5685eaf44330010237a0ac63..ab01c284899fb4ac682129923a27292fb3450032 100644
--- a/src/plugins/wp/TacArray.ml
+++ b/src/plugins/wp/TacArray.ml
@@ -42,7 +42,7 @@ class array =
   object
     inherit Tactical.make ~id:"Wp.array"
         ~title:"Array"
-        ~descr:"Decompose array access-update patterns"
+        ~descr:"Decompose access-update patterns"
         ~params:[]
 
     method select feedback (s : Tactical.selection) =
diff --git a/src/plugins/wp/TacBitrange.ml b/src/plugins/wp/TacBitrange.ml
index 4d8ec46291f09168e0420b8b0db364fe6135bc01..4bd424657a8e8169b14172cbe4d82029432b5676 100644
--- a/src/plugins/wp/TacBitrange.ml
+++ b/src/plugins/wp/TacBitrange.ml
@@ -205,14 +205,14 @@ open Tactical
 let positive_land =
   Tactical.checkbox
     ~id:"positive-land"
-    ~title:"Force positive logical-and"
+    ~title:"Pos."
     ~descr:"Requires to obtain a result from (at least one) positive operands"
     ~default:true ()
 
 let positive_lor =
   Tactical.checkbox
     ~id:"positive-lor"
-    ~title:"Force negative logical-or"
+    ~title:"Neg."
     ~descr:"Restrict to obtain a positive result from (all) positive operands"
     ~default:true ()
 
@@ -221,7 +221,7 @@ class bitrange =
     inherit Tactical.make
         ~id:"Wp.bitrange"
         ~title:"Bit Range"
-        ~descr:"Bounds of Bitwise Operators"
+        ~descr:"Compute bounds of bitwise operators."
         ~params:[snd positive_land;snd positive_lor]
 
     method select feedback = function
@@ -283,7 +283,7 @@ class autobitrange =
 
     method id = "wp:bitrange"
     method title = "Auto Bit-Range"
-    method descr = "Apply Bit-Range on comparison with bitwised operations"
+    method descr = "Compute bounds of bitwise operations."
 
     method search push (seq : Conditions.sequent) =
       let goal = snd seq in
diff --git a/src/plugins/wp/TacBittest.ml b/src/plugins/wp/TacBittest.ml
index 3921f2ed49516856267ece33fbc2c46d5741f147..f71958c8110f001a45da83d5a739dabff94238a1 100644
--- a/src/plugins/wp/TacBittest.ml
+++ b/src/plugins/wp/TacBittest.ml
@@ -55,7 +55,7 @@ class bittestrange =
     inherit Tactical.make
         ~id:"Wp.bittestrange"
         ~title:"Bit-Test Range"
-        ~descr:"Tighten Bounds with respect to bits"
+        ~descr:"Compute bounds with respect to bits."
         ~params:[]
 
     method select _feedback selection =
@@ -106,7 +106,7 @@ class autobittestrange : Strategy.heuristic =
 
     method id = "wp:bittestrange"
     method title = "Auto Bit-Test Range"
-    method descr = "Apply Bit-Test Range on bit-tests"
+    method descr = "Apply bitwise tactics on bit-tests expressions."
 
     method search push (seq : Conditions.sequent) =
       Conditions.iter
diff --git a/src/plugins/wp/TacBitwised.ml b/src/plugins/wp/TacBitwised.ml
index f9a56c88cb3831f156ac10f9919a2882af1ccbc3..80ccfb200cc3a6f3f2a406e9330bd0db04827109 100644
--- a/src/plugins/wp/TacBitwised.ml
+++ b/src/plugins/wp/TacBitwised.ml
@@ -43,14 +43,14 @@ let rewrite descr u v = Tactical.rewrite [ descr , F.p_true , u , v ]
 
 let vrange,prange = Tactical.spinner ~id:"Wp.bitwised.range"
     ~vmin:0 ~vmax:64 ~default:32
-    ~title:"Bits" ~descr:"Number of bits for bitwise equality" ()
+    ~title:"Bits" ~descr:"Size of bitwise equality." ()
 
 class bitcase =
   object(self)
     inherit Tactical.make
         ~id:"Wp.bitwised"
         ~title:"Bitwise Eq."
-        ~descr:"Decompose Bitwise Equality"
+        ~descr:"Decompose bitwise equality."
         ~params:[prange]
 
     (*   range:(0 <= a < 2^n && 0 <= b < 2^n)
@@ -124,7 +124,7 @@ class autobitwise =
       Printf.sprintf "Auto Bitwise Eq. (%d)" self#nbits
 
     method descr =
-      Printf.sprintf "Apply Bitwise Equality on wordsize bits (%d)" self#nbits
+      Printf.sprintf "Consider %d-bits bitwise equality." self#nbits
 
     method search push (seq : Conditions.sequent) =
       let goal = snd seq in
diff --git a/src/plugins/wp/TacChoice.ml b/src/plugins/wp/TacChoice.ml
index b760331b12d9dcbbeb1e4b55d517e9c06c744897..47e2c544432a1c613b969daf6d041c21b87d3052 100644
--- a/src/plugins/wp/TacChoice.ml
+++ b/src/plugins/wp/TacChoice.ml
@@ -33,7 +33,7 @@ class choice =
     inherit Tactical.make
         ~id:"Wp.choice"
         ~title:"Choice"
-        ~descr:"Select a Goal Alternative"
+        ~descr:"Select one alternative to be proved."
         ~params:[]
 
     method select _feedback (s : Tactical.selection) =
@@ -54,7 +54,7 @@ class absurd =
     inherit Tactical.make
         ~id:"Wp.absurd"
         ~title:"Absurd"
-        ~descr:"Contradict the Goal or an Hypothesis"
+        ~descr:"Contradict the selected clause."
         ~params:[]
 
     method select _feedback (s : Tactical.selection) =
@@ -88,7 +88,7 @@ class contrapose =
     inherit Tactical.make
         ~id:"Wp.contrapose"
         ~title:"Contrapose"
-        ~descr:"Swap and Negate Hypothesis with Conclusion"
+        ~descr:"Swap hypothesis with conclusion."
         ~params:[]
 
     method select _feedback (s : Tactical.selection) =
@@ -100,7 +100,7 @@ class contrapose =
           match s.condition with
           | Have p | When p | Core p | Init p | Type p ->
             let contrapose (hs,goal) =
-              let descr = "Contrapose" in
+              let descr = "Contrapose the goal." in
               let goal = F.p_not goal in
               let goal = Conditions.(step ~descr (Have goal)) in
               let hs = Conditions.replace ~at:s.id goal (hs , F.p_false) in
diff --git a/src/plugins/wp/TacClear.ml b/src/plugins/wp/TacClear.ml
index 99dcf83aea4e751b2f251d8286782c5db42459e0..111ab4645f771dfa7c9c5f06ba52e2baeef590b9 100644
--- a/src/plugins/wp/TacClear.ml
+++ b/src/plugins/wp/TacClear.ml
@@ -128,7 +128,7 @@ class clear =
   object(_)
     inherit Tactical.make ~id:"Wp.clear"
         ~title:"Clear"
-        ~descr:"Remove Hypothesis"
+        ~descr:"Remove the selected hypothesis."
         ~params:[]
 
     method select _feedback sel =
diff --git a/src/plugins/wp/TacCompound.ml b/src/plugins/wp/TacCompound.ml
index 5a60ac450f2fe5cd46c2bd6facac637bb0d8a018..5b0c1224e78a08d99e6d5bd2678a71557a8c8566 100644
--- a/src/plugins/wp/TacCompound.ml
+++ b/src/plugins/wp/TacCompound.ml
@@ -155,7 +155,7 @@ let process_expand (feedback : Tactical.feedback) ?at e =
   let pool = feedback#pool in
   let eq,cmp = get_compound_equality e in
   feedback#set_title "Compound (%s)" (name eq) ;
-  feedback#set_descr "Expand %s %s" (kind cmp) (equality eq) ;
+  feedback#set_descr "Expand %s %s." (kind cmp) (equality eq) ;
   let e' = (if eq then conj else disj) (clause ~pool cmp) in
   let cases = [feedback#get_title,F.p_true,e,F.e_prop e'] in
   Tactical.rewrite ?at cases
@@ -167,14 +167,14 @@ let process_have (feedback : Tactical.feedback) s =
   if eq then
     begin
       feedback#set_title "Compound (eq)" ;
-      feedback#set_descr "Expand %s equality" (kind cmp) ;
+      feedback#set_descr "Expand %s equality." (kind cmp) ;
       let cases = ["Compound (eq)",When (conj (clause ~pool cmp))] in
       Tactical.replace ~at:s.id cases
     end
   else
     begin
       feedback#set_title "Compound (split)" ;
-      feedback#set_descr "Split %s dis-equality" (kind cmp) ;
+      feedback#set_descr "Split %s dis-equality." (kind cmp) ;
       let cases = List.map negative (clause ~pool cmp) in
       Tactical.replace ~at:s.id cases
     end
@@ -185,13 +185,13 @@ let process_goal (feedback : Tactical.feedback) p =
   if eq then
     begin
       feedback#set_title "Compound (split)" ;
-      feedback#set_descr "Split %s equality" (kind cmp) ;
+      feedback#set_descr "Split %s equality." (kind cmp) ;
       Tactical.split (clause ~pool cmp) ;
     end
   else
     begin
       feedback#set_title "Compound (neq)" ;
-      feedback#set_descr "Expand compound dis-equality" ;
+      feedback#set_descr "Expand compound dis-equality." ;
       let cases = ["Compound (neq)",disj (clause ~pool cmp)] in
       Tactical.split cases
     end
@@ -200,7 +200,7 @@ class compound =
   object
     inherit Tactical.make ~id:"Wp.compound"
         ~title:"Compound"
-        ~descr:"Decompose compound equalities"
+        ~descr:"Decompose compound equalities."
         ~params:[]
 
     method select feedback (s : Tactical.selection) =
diff --git a/src/plugins/wp/TacCompute.ml b/src/plugins/wp/TacCompute.ml
index 07cbeb1d70043b2755ce29dcf33438576fa958cf..bc051433dd3f71553b90a7c7fd7f5f697f9ec862 100644
--- a/src/plugins/wp/TacCompute.ml
+++ b/src/plugins/wp/TacCompute.ml
@@ -57,18 +57,18 @@ let process (fd : Tactical.feedback) ?at e =
     let p = F.p_bool c in
     let q = F.p_not p in
     fd#set_title "Compute (split)" ;
-    fd#set_descr "Split on if-then-else condition" ;
+    fd#set_descr "Split on if-then-else condition." ;
     Applicable (Tactical.rewrite ?at ["If-Then",p,e,a;"If-Else",q,e,b])
   | Unfold(e,f,d) ->
     fd#set_title "Compute (def)" ;
-    fd#set_descr "Unfold definition of '%s'" (Lang.name_of_lfun f) ;
+    fd#set_descr "Unfold definition of '%s'." (Lang.name_of_lfun f) ;
     Applicable (Tactical.rewrite ?at ["Definition",F.p_true,e,d])
 
 class compute : Tactical.tactical =
   object
     inherit Tactical.make ~id:"Wp.compute"
         ~title:"Compute"
-        ~descr:"Unfold definitions and split on conditions"
+        ~descr:"Unfold definitions and split on conditions."
         ~params:[]
     method select feedback (s : Tactical.selection) =
       process feedback ?at:(Tactical.at s) (Tactical.selected s)
diff --git a/src/plugins/wp/TacCongruence.ml b/src/plugins/wp/TacCongruence.ml
index 1e140e1a7313bc0c1ef6e78778b1f6530f44c3d3..a8fbfdad47457597d2ecaf180c925241ace632da 100644
--- a/src/plugins/wp/TacCongruence.ml
+++ b/src/plugins/wp/TacCongruence.ml
@@ -186,7 +186,7 @@ class congruence =
     inherit Tactical.make
         ~id:"Wp.congruence"
         ~title:"Congruence"
-        ~descr:"Euclidian Comparisons"
+        ~descr:"Resolve congruences with euclidian divisions."
         ~params:[]
 
     method select _feedback = function
@@ -211,7 +211,7 @@ class autodiv =
 
     method id = "wp:congruence"
     method title = "Auto Congruence"
-    method descr = "Resolve Divisions and Multiplications"
+    method descr = "Resolve divisions and multiplications."
     method search push (seq : Conditions.sequent) =
       try
         let p = snd seq in
diff --git a/src/plugins/wp/TacCut.ml b/src/plugins/wp/TacCut.ml
index c0c6d2cd5c2494aa31ab6b846f61d73d55610b53..424340d4bb4dbd58414993fbb5f497dcbfdc5883 100644
--- a/src/plugins/wp/TacCut.ml
+++ b/src/plugins/wp/TacCut.ml
@@ -32,7 +32,7 @@ let fclause,pclause =
   Tactical.composer
     ~id:"clause"
     ~title:"Clause"
-    ~descr:"Clause to Cut with"
+    ~descr:"Clause to cut with."
     ~filter:F.is_prop
     ()
 
@@ -42,18 +42,22 @@ let fmode,pmode =
   Tactical.selector
     ~id:"case"
     ~title:"Mode"
-    ~descr:"Select How the Clause is Used"
+    ~descr:"Select how the clause is used."
     ~default:MODUS
     ~options:Tactical.[
-        { title="Case Analysis" ; descr="" ; vid="CASES" ; value=CASES } ;
-        { title="Modus Ponens" ; descr="" ; vid="MODUS" ; value=MODUS } ;
+        { title="Case Analysis" ;
+          descr="Consider P->Q and !P->Q." ;
+          vid="CASES" ; value=CASES } ;
+        { title="Modus Ponens" ;
+          descr="Consider P and P->Q." ;
+          vid="MODUS" ; value=MODUS } ;
       ] ()
 
 class cut =
   object(self)
     inherit Tactical.make ~id:"Wp.cut"
         ~title:"Cut"
-        ~descr:"Use Intermerdiate Hypothesis"
+        ~descr:"Use intermerdiate hypothesis."
         ~params:[pmode;pclause]
 
     method select feedback sel =
@@ -70,7 +74,7 @@ class cut =
       else
         match mode with
         | MODUS ->
-          feedback#set_descr "Prove then Insert the Clause" ;
+          feedback#set_descr "Prove then insert the clause." ;
           let clause = F.p_bool (Tactical.selected cut) in
           let step = Conditions.step ~descr:"Cut" (Have clause) in
           let at = Tactical.at sel in
@@ -81,7 +85,7 @@ class cut =
                 "Assume" , (fst assume,snd sequent) ]
             end
         | CASES ->
-          feedback#set_descr "Proof by Case in the Clause" ;
+          feedback#set_descr "Proof by case in the clause." ;
           let positive = F.p_bool (Tactical.selected cut) in
           let negative = F.p_not positive in
           Applicable
diff --git a/src/plugins/wp/TacFilter.ml b/src/plugins/wp/TacFilter.ml
index 19f2d51a940b9e7af67c9a89c782272c8217c206..3643f773c7301f96122c1131c88dbd40c823a172 100644
--- a/src/plugins/wp/TacFilter.ml
+++ b/src/plugins/wp/TacFilter.ml
@@ -29,14 +29,14 @@ open Tactical
 let vanti,panti =
   Tactical.checkbox ~id:"anti"
     ~title:"Absurd"
-    ~descr:"Find Contradiction in Side Hypotheses"
+    ~descr:"Find contradiction in extra hypotheses."
     ~default:false ()
 
 class filter =
   object(self)
     inherit Tactical.make ~id:"Wp.filter"
         ~title:"Filter"
-        ~descr:"Dependent Erasure of Hypotheses"
+        ~descr:"Eliminates extra hypotheses."
         ~params:[panti]
 
     method select feedback _sel =
diff --git a/src/plugins/wp/TacHavoc.ml b/src/plugins/wp/TacHavoc.ml
index 989769ccfbd39efbded43a3fa560b2f52b3c6c9f..412c49592ae8c9f2ff585d46d69f49c4f714c85f 100644
--- a/src/plugins/wp/TacHavoc.ml
+++ b/src/plugins/wp/TacHavoc.ml
@@ -45,7 +45,7 @@ class havoc =
   object
     inherit Tactical.make ~id:"Wp.havoc"
         ~title:"Havoc"
-        ~descr:"Go Through Assigns"
+        ~descr:"Go through assigns."
         ~params:[]
 
     method select _feedback sel =
@@ -93,7 +93,7 @@ class separated =
   object
     inherit Tactical.make ~id:"Wp.separated"
         ~title:"Separated"
-        ~descr:"Expand Separation Cases"
+        ~descr:"Split over separated and overlapping cases."
         ~params:[]
 
     method select _feedback sel =
@@ -176,7 +176,7 @@ class validity =
 
     inherit Tactical.make ~id:"Wp.valid"
         ~title:"Validity Range"
-        ~descr:"Unfold validity and range definitions"
+        ~descr:"Unfold validity and range definitions."
         ~params:[]
 
     method select _feedback (s : Tactical.selection) =
diff --git a/src/plugins/wp/TacInduction.ml b/src/plugins/wp/TacInduction.ml
index 1403da9adbac755c49caa03aabbbbf8f6dd87e0a..316d9f2db8bb9658da5af70f1ce290d95a51b7c4 100644
--- a/src/plugins/wp/TacInduction.ml
+++ b/src/plugins/wp/TacInduction.ml
@@ -84,14 +84,14 @@ let process value n0 sequent =
 (* -------------------------------------------------------------------------- *)
 
 let vbase,pbase = Tactical.composer ~id:"base"
-    ~title:"Base" ~descr:"Value of base case" ()
+    ~title:"Base" ~descr:"Value of base case." ()
 
 class induction =
   object(self)
     inherit Tactical.make
         ~id:"Wp.induction"
         ~title:"Induction"
-        ~descr:"Proof by integer induction"
+        ~descr:"Proof by integer induction."
         ~params:[pbase]
 
     method private get_base () =
diff --git a/src/plugins/wp/TacInstance.ml b/src/plugins/wp/TacInstance.ml
index a73274c60faa70efd4decfa5192bb901bd958797..f9099ad4e314de47fffd5f2eb6776f95746890d6 100644
--- a/src/plugins/wp/TacInstance.ml
+++ b/src/plugins/wp/TacInstance.ml
@@ -30,10 +30,10 @@ module L = Qed.Logic
 (* -------------------------------------------------------------------------- *)
 
 let descr = function
-  | 1 -> "First Parameter"
-  | 2 -> "Second Parameter"
-  | 3 -> "Third Parameter"
-  | n -> Printf.sprintf "%d-th Parameter" n
+  | 1 -> "First parameter."
+  | 2 -> "Second parameter."
+  | 3 -> "Third parameter."
+  | n -> Printf.sprintf "%d-th parameter." n
 
 let mkparam k =
   Tactical.composer
@@ -142,7 +142,7 @@ class instance =
   object(self)
     inherit Tactical.make ~id:"Wp.instance"
         ~title:"Instance"
-        ~descr:"Instantiate properties"
+        ~descr:"Instantiate properties."
         ~params
 
     method private wrap env lemma fields =
@@ -177,10 +177,10 @@ class instance =
           match cardinal 1000 bindings with
           | Some n ->
             if n > 1 then
-              feedback#set_descr "Generates %d instances" n ;
+              feedback#set_descr "Generates %d instances." n ;
             Applicable (bind ~side bindings (F.p_bool phi))
           | None ->
-            feedback#set_error "More than 1,000 instances" ;
+            feedback#set_error "More than 1,000 instances." ;
             Not_configured
         else Not_configured
       else
diff --git a/src/plugins/wp/TacLemma.ml b/src/plugins/wp/TacLemma.ml
index a304e9efe891837b4d8e0f4130edda56364e842a..929660ba310bf714e0f20431992e478ee8386864 100644
--- a/src/plugins/wp/TacLemma.ml
+++ b/src/plugins/wp/TacLemma.ml
@@ -86,7 +86,7 @@ type lemma = Definitions.dlemma Tactical.named
 let find thm = named (Definitions.find_name thm)
 
 let search,psearch =
-  Tactical.search ~id:"lemma" ~title:"Lemma" ~descr:"Lemma to Instantiate"
+  Tactical.search ~id:"lemma" ~title:"Lemma" ~descr:"Lemma to instantiate."
     ~browse ~find ()
 
 
@@ -99,7 +99,7 @@ class instance =
   object(self)
     inherit Tactical.make ~id:"Wp.lemma"
         ~title:"Lemma"
-        ~descr:"Search & Instantiate Lemma"
+        ~descr:"Search and instantiate lemmas."
         ~params:(psearch :: TacInstance.params)
 
     method private hide (feedback : Tactical.feedback) fields =
@@ -139,7 +139,7 @@ class instance =
             match TacInstance.cardinal 1000 bindings with
             | Some n ->
               if n > 1 then
-                feedback#set_descr "Generates %d instances" n ;
+                feedback#set_descr "Generates %d instances." n ;
               let at = Tactical.at selection in
               Applicable
                 (TacInstance.instance_have ~title ?at bindings lemma)
diff --git a/src/plugins/wp/TacModMask.ml b/src/plugins/wp/TacModMask.ml
index c2e7cd09ba2e358847157112f9845f147ca124c0..d6b86ee987063db35037e28ede8feae3c04c71e6 100644
--- a/src/plugins/wp/TacModMask.ml
+++ b/src/plugins/wp/TacModMask.ml
@@ -28,7 +28,7 @@ let title_revert = "Revert (current: m & a -> a % m+1)"
 let v_revert,p_revert =
   Tactical.checkbox ~id:"Wp.modmask.revert"
     ~title:title_no_revert
-    ~descr:"Changes operands for modulo"
+    ~descr:"Changes operands for modulo."
     ~default:false ()
 
 class modmask =
@@ -36,7 +36,7 @@ class modmask =
     inherit Tactical.make
         ~id:"Wp.modmask"
         ~title:"Mod-Mask"
-        ~descr:"Converts modulo from/to bitmask"
+        ~descr:"Converts modulo from/to bitmask."
         ~params:[p_revert]
 
     method select feedback selection =
diff --git a/src/plugins/wp/TacNormalForm.ml b/src/plugins/wp/TacNormalForm.ml
index f996f7fb5385a89e90876ec6a8cdb29a221f9ff2..dcddbdd06544a41192d364154e00fbc341cfc0aa 100644
--- a/src/plugins/wp/TacNormalForm.ml
+++ b/src/plugins/wp/TacNormalForm.ml
@@ -29,7 +29,7 @@ open Tactical
 let str_case = "Case"
 let sub_cases phi = function
   | []   -> List.map (fun t -> phi str_case (F.p_bool t))
-  | [descr]     -> List.map (fun t -> phi descr (F.p_bool t))
+  | [descr] -> List.map (fun t -> phi descr (F.p_bool t))
   | infos -> List.map2 (fun info t -> phi info (F.p_bool t)) infos
 
 (* split into n sequents: [hyps] |- [subcases]_i *)
@@ -79,7 +79,7 @@ class normal_form =
   object
     inherit Tactical.make ~id:"Wp.normal_form"
         ~title:"Intuition"
-        ~descr:"Decompose with Conjunctive/Disjunctive Normal Form"
+        ~descr:"Decompose into conjunctive or disjunctive normal form."
         ~params:[]
 
     method select feedback (s : Tactical.selection) =
diff --git a/src/plugins/wp/TacOverflow.ml b/src/plugins/wp/TacOverflow.ml
index c28c055e8728f1793bed1d114fd0d559d22429c7..8591c6d8f8b4b24a2e03795a71f6252ac15972db 100644
--- a/src/plugins/wp/TacOverflow.ml
+++ b/src/plugins/wp/TacOverflow.ml
@@ -28,7 +28,7 @@ class overflow =
     inherit Tactical.make
         ~id:"Wp.overflow"
         ~title:"Overflow"
-        ~descr:"Split integer overflow into in and out of range"
+        ~descr:"Split integer overflow into in and out of range."
         ~params:[]
 
     method select _feedback selection =
diff --git a/src/plugins/wp/TacRange.ml b/src/plugins/wp/TacRange.ml
index 0184d4e8ffd7b6620b2c8b0c4522194f9df6a3b1..6e4e8362682eca7b6d88e8fed658c4ccc0a13596 100644
--- a/src/plugins/wp/TacRange.ml
+++ b/src/plugins/wp/TacRange.ml
@@ -42,15 +42,15 @@ let enum ?at e a b sequent =
   Tactical.insert ?at cases sequent
 
 let vmin,pmin = Tactical.spinner ~id:"inf"
-    ~title:"Inf" ~descr:"Range Lower Bound (inclusive)" ()
+    ~title:"Inf" ~descr:"Range lower bound (inclusive)." ()
 let vmax,pmax = Tactical.spinner ~id:"sup"
-    ~title:"Sup" ~descr:"Range Upper Bound (inclusive)" ()
+    ~title:"Sup" ~descr:"Range upper bound (inclusive)." ()
 
 class range =
   object(self)
     inherit Tactical.make ~id:"Wp.range"
         ~title:"Range"
-        ~descr:"Enumerate a range of values for an integer term"
+        ~descr:"Enumerate a range of values for an integer term."
         ~params:[pmin;pmax]
 
     method select feedback (s : Tactical.selection) =
@@ -65,7 +65,7 @@ class range =
               Not_configured )
           else
             ( feedback#set_title "Range (%d-%d)" a b ;
-              feedback#set_descr "Enumerate (lower,%d-%d,upper)" a b ;
+              feedback#set_descr "Enumerate lower, range %d-%d and upper." a b ;
               Applicable(enum ?at e a b) )
         end
       else
diff --git a/src/plugins/wp/TacRewrite.ml b/src/plugins/wp/TacRewrite.ml
index 2395be03dc095ccce711e52b3830ca98465da712..51c52f4efda6904736ec3791f7b4b585465753d6 100644
--- a/src/plugins/wp/TacRewrite.ml
+++ b/src/plugins/wp/TacRewrite.ml
@@ -53,7 +53,7 @@ class rewrite dir =
   let title = if dir then "Rewrite (<-)" else "Rewrite (->)" in
   object
     inherit Tactical.make
-        ~id ~title ~descr:"Rewrite from equality" ~params:[]
+        ~id ~title ~descr:"Rewrite from equality." ~params:[]
 
     method select _feedback select =
       try
@@ -106,7 +106,7 @@ class auto_rewrite =
   object
     method id = "wp:replace"
     method title = "Auto Replace"
-    method descr = "Lookup for equalities to Rewrite"
+    method descr = "Lookup for equalities to rewrite with."
     method search (push : Strategy.strategy -> unit) (hyps,goal) =
       Conditions.iter
         (fun s ->
diff --git a/src/plugins/wp/TacSequence.ml b/src/plugins/wp/TacSequence.ml
index e9899747565a61d46c6f4b3634ef1048ee5080c2..09a4144379129f026c3570a6e90638a215028621 100644
--- a/src/plugins/wp/TacSequence.ml
+++ b/src/plugins/wp/TacSequence.ml
@@ -35,14 +35,14 @@ let sum n = match F.repr n with
 (* -------------------------------------------------------------------------- *)
 
 let vmode,pmode =
-  Tactical.selector ~id:"seq.side" ~title:"Mode" ~descr:"Unrolling mode"
+  Tactical.selector ~id:"seq.side" ~title:"Mode" ~descr:"Unrolling mode."
     ~options:[
       { vid = "left" ; title = "Unroll left" ;
-        descr = "Transform (A^n) into (A.A^n-1)" ; value = `Left } ;
+        descr = "Transform (A^n) into (A.A^n-1)." ; value = `Left } ;
       { vid = "right" ; title = "Unroll right" ;
-        descr = "Transform (A^n) into (A^n-1.A)" ; value = `Right } ;
+        descr = "Transform (A^n) into (A^n-1.A)." ; value = `Right } ;
       { vid = "sum" ; title = "Concat sum" ;
-        descr = "Transform A^(p+q) into (A^p.A^q)" ; value = `Sum }
+        descr = "Transform A^(p+q) into (A^p.A^q)." ; value = `Sum }
     ] ()
 
 class sequence =
@@ -50,7 +50,7 @@ class sequence =
     inherit Tactical.make
         ~id:"Wp.sequence"
         ~title:"Sequence"
-        ~descr:"Unroll repeat-sequence operator"
+        ~descr:"Unroll repeat-sequence operator."
         ~params:[pmode]
 
     method select feedback (s : Tactical.selection) =
@@ -67,7 +67,7 @@ class sequence =
               match sum n with
               | [ s ] ->
                 feedback#set_descr
-                  "Cannot unroll with selected option, '%a' is not a sum"
+                  "Cannot unroll: '%a' is not a sum."
                   F.pp_term s ;
                 Tactical.Not_configured
               | ns ->
@@ -76,7 +76,7 @@ class sequence =
                 let cat =
                   concat ~result (List.map (repeat ~result a) ns) in
                 feedback#set_descr
-                  "Unroll repeat-sequence: unroll sum" ;
+                  "Unroll repeat-sequence: unroll sum." ;
                 Tactical.Applicable (
                   Tactical.condition "Positive" pos @@
                   Tactical.rewrite ?at [ "Unroll" , pos , value , cat ])
@@ -87,7 +87,7 @@ class sequence =
             let p = F.e_add n F.e_minus_one in
             let unroll = concat ~result [a ; repeat ~result a p] in
             feedback#set_descr
-              "Unroll repeat-sequence: unroll first element" ;
+              "Unroll repeat-sequence: unroll first element." ;
             Tactical.Applicable(
               Tactical.rewrite ?at [
                 "Nil", negative n , value , concat ~result [] ;
@@ -99,7 +99,7 @@ class sequence =
             let p = F.e_add n F.e_minus_one in
             let unroll = concat ~result [repeat ~result a p ; a] in
             feedback#set_descr
-              "Unroll repeat-sequence: unroll last element" ;
+              "Unroll repeat-sequence: unroll last element." ;
             Tactical.Applicable(
               Tactical.rewrite ?at [
                 "Nil", negative n , value , concat ~result [] ;
diff --git a/src/plugins/wp/TacShift.ml b/src/plugins/wp/TacShift.ml
index dc0bda06d57698ec26922046127da26b0b9f92b5..d35bf081ae86e53c821807d57c502db95fe2e839 100644
--- a/src/plugins/wp/TacShift.ml
+++ b/src/plugins/wp/TacShift.ml
@@ -53,7 +53,7 @@ class shift =
     inherit Tactical.make
         ~id:"Wp.shift"
         ~title:"Logical Shift"
-        ~descr:"Transform Shifts into Div/Mult"
+        ~descr:"Transform logical shifts into divisions and multiplications."
         ~params:[]
 
     method select feedback selection =
@@ -104,7 +104,7 @@ class autoshift =
 
     method id = "wp:bitshift"
     method title = "Auto Bit-Shift"
-    method descr = "Apply Bit-Shift in Goal"
+    method descr = "Decompose logical shifts from current goal."
 
     method search push (seq : Conditions.sequent) =
       let goal = snd seq in
diff --git a/src/plugins/wp/TacSplit.ml b/src/plugins/wp/TacSplit.ml
index 0e1e31f1e1e64650d38e9585f6be8390aa834e96..023633e34a2f7711cf3948eb23075ead12106d20 100644
--- a/src/plugins/wp/TacSplit.ml
+++ b/src/plugins/wp/TacSplit.ml
@@ -164,14 +164,14 @@ class split =
     inherit Tactical.make
         ~id:"Wp.split"
         ~title:"Split"
-        ~descr:"Decompose Logical Connectives and Conditionals"
+        ~descr:"Decompose logical connectives and conditionals."
         ~params:[]
 
     method select feedback (s : Tactical.selection) =
       let split_cmp at title x y =
         feedback#set_title title ;
         feedback#set_descr
-          "Decompose into three comparisons (lt, eq, gt)" ;
+          "Decompose into three comparisons (lt, eq, gt)." ;
         let cases = [
           "Lt",F.p_bool (e_lt x y);
           "Eq",F.p_bool (e_eq x y);
@@ -199,7 +199,7 @@ class split =
           | Neq(x,y) when not (is_prop x || is_prop y) -> split_neq at x y
           | _ when F.is_prop e ->
             feedback#set_title "Split (true,false)" ;
-            feedback#set_descr "Decompose between True and False values" ;
+            feedback#set_descr "Decompose between boolean values." ;
             let cases = ["True",F.p_bool e;"False",F.p_not (F.p_bool e)] in
             let at = Tactical.at s in
             Applicable (Tactical.insert ?at cases)
@@ -218,7 +218,7 @@ class split =
                     (* unbound condition: proceed by case *)
                     feedback#set_title "Split (exists if)" ;
                     feedback#set_descr
-                      "Split unbound Condition into Branches" ;
+                      "Split unbound condition into branches." ;
                     let p = F.e_imply [c] (bind Exists ~vars p) in
                     let q = F.e_imply [e_not c] (bind Exists ~vars q) in
                     Applicable (Tactical.split [
@@ -230,8 +230,8 @@ class split =
                   begin
                     feedback#set_title "Split (rewrite exists if)" ;
                     feedback#set_descr
-                      "Rewrite the Conditional in a Disjunction \
-                       and Distribute the Quantifier under" ;
+                      "Rewrite into a disjunction \
+                       and distribute the quantifier under." ;
                     let p = bind Exists ~vars (F.e_and [c;p]) in
                     let q = bind Exists ~vars (F.e_and [(e_not c); q]) in
                     let cases = [ "Split" , F.p_bool (F.e_or [p;q]) ] in
@@ -240,14 +240,14 @@ class split =
               | Or es ->
                 feedback#set_title "Split (exists or)" ;
                 feedback#set_descr
-                  "Distributes the Quantifier under the Disjunction" ;
+                  "Distributes the quantifier under the disjunction." ;
                 let p = F.e_or (List.map (bind Exists ~vars) es) in
                 let cases = [ "Split" , F.p_bool p ] in
                 Applicable (Tactical.split cases)
               | Imply (es, p) ->
                 feedback#set_title "Split (exists imply)" ;
                 feedback#set_descr
-                  "Distributes the Quantifier under the Imply" ;
+                  "Distributes the quantifier under the implication." ;
                 let p = F.e_imply (List.map (bind Forall ~vars) es)
                     (bind Exists ~vars p) in
                 let cases = [ "Split" , F.p_bool p ] in
@@ -258,7 +258,7 @@ class split =
                 else begin
                   feedback#set_title "Split (exists and)" ;
                   feedback#set_descr
-                    "Decompose the Quantifier into %d Blocks" nb_parts ;
+                    "Decompose the quantifier into %d parts." nb_parts ;
                   let bind es =
                     bind Exists ~vars (F.e_and (F.Tset.elements es)) in
                   let goal i n es =
@@ -271,26 +271,26 @@ class split =
             let n = List.length es in
             feedback#set_title "Split (and)" ;
             feedback#set_descr
-              "Decompose between the %d parts of the Conjunction" n ;
+              "Decompose between the %d parts of the conjunction." n ;
             let goal i n e = Printf.sprintf "Goal %d/%d" i n , F.p_bool e in
             Applicable (Tactical.split (Tactical.mapi goal es))
           | Eq(x,y) when (F.is_prop x) && (F.is_prop y) ->
             feedback#set_title "Split (iff)" ;
-            feedback#set_descr "Turn Equivalence into Implications" ;
+            feedback#set_descr "Turn equivalence into implications." ;
             let p = F.p_bool (F.e_imply [x] y) in
             let q = F.p_bool (F.e_imply [y] x) in
             let cases = [ "Necessity" , p ; "Sufficiency" , q ] in
             Applicable (Tactical.split cases)
           | Neq(x,y) when (F.is_prop x) && (F.is_prop y) ->
             feedback#set_title "Split (xor)" ;
-            feedback#set_descr "Turn Dis-Equivalence into Implications" ;
+            feedback#set_descr "Turn dis-equivalence into implications" ;
             let p = F.p_bool (F.e_imply [x] (e_not y)) in
             let q = F.p_bool (F.e_imply [y] (e_not x)) in
             let cases = [ "Necessity" , p ; "Sufficiency" , q ] in
             Applicable (Tactical.split cases)
           | If(c,p,q) -> (* Split + intro *)
             feedback#set_title "Split (if)" ;
-            feedback#set_descr "Decompose Conditional into Branches" ;
+            feedback#set_descr "Decompose conditional into branches." ;
             let p = F.p_bool (F.e_imply [c] p) in
             let q = F.p_bool (F.e_imply [e_not c] q) in
             let cases = [ "Then" , p ; "Else" , q ] in
@@ -304,13 +304,13 @@ class split =
           | State _ | Probe _ -> Not_applicable
           | Branch(p,_,_) ->
             feedback#set_title "Split (branch)" ;
-            feedback#set_descr "Decompose Conditional into Branches" ;
+            feedback#set_descr "Decompose conditional into branches." ;
             let cases = [ "Then" , p ; "Else" , p_not p ] in
             Applicable (Tactical.insert ~at:step.id cases)
           | Either seqs ->
             let n = List.length seqs in
             feedback#set_title "Split (switch)" ;
-            feedback#set_descr "Decompose each %d Cases" n ;
+            feedback#set_descr "Decompose each %d cases." n ;
             let either i n s = Printf.sprintf "Case %d/%d" i n , Either [s] in
             let cases = Tactical.mapi either seqs in
             Applicable (Tactical.replace ~at:step.id cases)
@@ -325,7 +325,7 @@ class split =
                     if F.Vars.is_empty (F.Vars.inter (F.vars c) vars) then
                       begin (* unbound condition: so, the If is considered as a disjunction *)
                         feedback#set_title "Split (forall if)" ;
-                        feedback#set_descr "Decompose unbound conditional into Branches" ;
+                        feedback#set_descr "Decompose unbound conditional into branches." ;
                         let p = F.p_bool (F.e_and [c; (bind Exists ~vars p)]) in
                         let q = F.p_bool (F.e_and [(e_not c); (bind Exists ~vars q)]) in
                         let cases = [ "Then" , When p ; "Else" , When q ] in
@@ -334,7 +334,7 @@ class split =
                     else
                       begin
                         feedback#set_title "Split (rewrite forall if)" ;
-                        feedback#set_descr "Rewrite the Conditional in a Conjunction and Distributes the Quantifier under the Conjunction" ;
+                        feedback#set_descr "Rewrite the conjunction and distribute the quantifier." ;
                         let p = bind Exists ~vars (F.e_imply [c] p) in
                         let q = bind Exists ~vars (F.e_imply [e_not c] q) in
                         let cases = [ "Split (rewrite exists if)" , When (F.p_bool (F.e_and [p;q])) ] in
@@ -342,7 +342,7 @@ class split =
                       end
                   | And es ->
                     feedback#set_title "Split (forall and)" ;
-                    feedback#set_descr "Distributes the Quantifier under the Conjunction" ;
+                    feedback#set_descr "Distributes the quantifier under the conjunction." ;
                     let p = F.p_bool (F.e_and (List.map (bind Forall ~vars) es)) in
                     let cases = [ "Split (distrib forall and)" , When p ] in
                     Applicable (Tactical.replace ~at:step.id cases)
@@ -351,7 +351,7 @@ class split =
                     if nb_parts=1 then Not_applicable
                     else begin
                       feedback#set_title "Split (forall or)" ;
-                      feedback#set_descr "Decompose the Quantifier between %d parts of the Disjunction" nb_parts ;
+                      feedback#set_descr "Decompose the quantifier between %d parts of the disjunction." nb_parts ;
                       let bind es = bind Forall ~vars (F.e_or (F.Tset.elements es)) in
                       let goal i n es = Printf.sprintf "Goal %d/%d" i n , When (F.p_bool (bind es)) in
                       let cases = Tactical.mapi goal parts in
@@ -362,13 +362,13 @@ class split =
               | Or xs ->
                 let n = List.length xs in
                 feedback#set_title "Split (or)" ;
-                feedback#set_descr "Distinguish the %d parts of the Disjunction" n ;
+                feedback#set_descr "Distinguish the %d parts of the disjunction." n ;
                 let hyp i n e = Printf.sprintf "Case %d/%d" i n , When (F.p_bool e) in
                 let cases = Tactical.mapi hyp xs in
                 Applicable (Tactical.replace ~at:step.id cases)
               | Eq(x,y) when (F.is_prop x)&&(F.is_prop y) ->
                 feedback#set_title "Split (iff)";
-                feedback#set_descr "Decompose Equivalence into both True/False" ;
+                feedback#set_descr "Decompose equivalence into booleans." ;
                 let p = F.p_bool x in
                 let q = F.p_bool y in
                 let cases = [
@@ -378,7 +378,7 @@ class split =
                 Applicable (Tactical.replace ~at:step.id cases)
               | Neq(x,y) when (F.is_prop x)&&(F.is_prop y) ->
                 feedback#set_title "Split (xor)";
-                feedback#set_descr "Decompose Dis-Equivalence into alternated True/False" ;
+                feedback#set_descr "Decompose Dis-equivalence into booleans." ;
                 let p = F.p_bool x in
                 let q = F.p_bool y in
                 let cases = [
@@ -396,7 +396,7 @@ class split =
                 split_leq (Some step.id) x y
               | If(c,p,q) ->
                 feedback#set_title "Split (if)" ;
-                feedback#set_descr "Split Conditional into Branches" ;
+                feedback#set_descr "Split conditional into branches." ;
                 let p = F.p_bool (F.e_and [c;p]) in
                 let q = F.p_bool (F.e_and [e_not c;q]) in
                 let cases = [ "Then" , When p ; "Else" , When q ] in
@@ -412,7 +412,7 @@ class split =
                   | _ -> assert false (* see above pattern matching *)
                 in
                 feedback#set_title "Split (conjunction)" ;
-                feedback#set_descr "Split conjunction into steps" ;
+                feedback#set_descr "Split conjunction into different steps." ;
                 let ps = List.map (fun p -> cond @@ p_bool p) ps in
                 Applicable (Tactical.replace_step ~at:step.id ps)
               | _ ->
diff --git a/src/plugins/wp/TacUnfold.ml b/src/plugins/wp/TacUnfold.ml
index 588d32aaa93d789c2a6f5de7e8619fd75b485378..ccc1bf0eb15c3c4fb78359e1e3c2f2a3995160f7 100644
--- a/src/plugins/wp/TacUnfold.ml
+++ b/src/plugins/wp/TacUnfold.ml
@@ -171,7 +171,7 @@ class unfold =
   object
     inherit Tactical.make ~id:"Wp.unfold"
         ~title:"Definition"
-        ~descr:"Unfold predicate and logic function definition"
+        ~descr:"Unfold definitions of predicates and functions."
         ~params:[]
 
     method select _feedback (s : Tactical.selection) =
diff --git a/src/plugins/wp/VC.ml b/src/plugins/wp/VC.ml
index 59be9f01aad647c7b2da65501c65788dd767f30f..4cbd4b35863037c79ba2a0fbd6be9d942101f581 100644
--- a/src/plugins/wp/VC.ml
+++ b/src/plugins/wp/VC.ml
@@ -36,9 +36,9 @@ let get_description = Wpo.get_label
 let get_property = Wpo.get_property
 let get_sequent w = snd (Wpo.compute w)
 let get_result = Wpo.get_result
-let get_results = Wpo.get_results
+let get_results = ProofEngine.results
 let is_trivial = Wpo.is_trivial
-let is_valid = Wpo.is_valid
+let is_valid = Wpo.is_fully_valid
 let is_passed = Wpo.is_passed
 let has_unknown = Wpo.has_unknown
 
diff --git a/src/plugins/wp/VCS.ml b/src/plugins/wp/VCS.ml
index 1fef10ad99975088ac10a8bc0a17ff13f3108b1b..88ed31052135da2d5017e5e7a46fe4231c64e771 100644
--- a/src/plugins/wp/VCS.ml
+++ b/src/plugins/wp/VCS.ml
@@ -117,19 +117,21 @@ let is_prover = function
   | Qed | Why3 _ -> true
   | Tactical -> false
 
+let is_extern = function
+  | Qed | Tactical -> false
+  | Why3 _ -> true
+
 let is_auto = function
   | Qed -> true
   | Tactical -> false
-  | Why3 p ->
-    match p.prover_name with
-    | "Alt-Ergo" | "CVC4" | "Z3" -> true
-    | "Coq" -> false
-    | _ ->
-      let config = Why3Provers.config () in
-      try
-        let prover_config = Why3.Whyconf.get_prover_config config p in
-        not prover_config.interactive
-      with Not_found -> true
+  | Why3 p -> Why3Provers.is_auto p
+
+let eq_prover p q =
+  match p,q with
+  | Qed,Qed -> true
+  | Tactical,Tactical -> true
+  | Why3 p, Why3 q -> Why3Provers.compare p q = 0
+  | (Why3 _ | Qed | Tactical) , _ -> false
 
 let has_counter_examples = function
   | Qed | Tactical -> false
@@ -232,12 +234,12 @@ type result = {
 let is_result = function
   | Valid | Invalid | Unknown | Timeout | Stepout | Failed -> true
   | NoResult | Computing _ -> false
-
 let is_verdict r = is_result r.verdict
 let is_valid = function { verdict = Valid } -> true | _ -> false
 let is_trivial r = is_valid r && r.prover_time = 0.0
 let is_not_valid r = is_verdict r && not (is_valid r)
 let is_computing = function { verdict=Computing _ } -> true | _ -> false
+let is_none = function { verdict=NoResult } -> true | _ -> false
 let is_proved ~smoke = function
   | NoResult | Computing _ | Failed -> false
   | Valid -> not smoke
@@ -337,8 +339,9 @@ let pp_perf_shell fmt r =
   if not (Wp_parameters.has_dkey dkey_shell) then
     pp_perf_forced fmt r
 
-let name_of_verdict = function
-  | NoResult | Computing _ -> "none"
+let name_of_verdict ?(computing=false) = function
+  | NoResult -> "none"
+  | Computing _ -> if computing then "computing" else "none"
   | Valid -> "valid"
   | Invalid -> "invalid"
   | Failed -> "failed"
@@ -400,7 +403,6 @@ let pp_model fmt model =
     ) model
 
 let vrank = function
-  | Computing _ -> 0
   | NoResult -> 1
   | Failed -> 2
   | Unknown -> 3
@@ -408,13 +410,17 @@ let vrank = function
   | Stepout -> 5
   | Valid -> 6
   | Invalid -> 7
+  | Computing _ -> 8
 
 let conjunction a b =
-  match a,b with
-  | Valid,Valid -> Valid
-  | Valid, r -> r
-  | r , Valid -> r
-  | _ -> if vrank b > vrank a then b else a
+  if a == b then a else
+    match a,b with
+    | Computing p , Computing q -> Computing (fun () -> p () ; q ())
+    | Computing _ , _ -> a
+    | _ , Computing _ -> b
+    | Valid,Valid -> Valid
+    | Valid,r | r,Valid -> r
+    | _ -> if vrank b > vrank a then b else a
 
 let msize m = Probe.Map.fold (fun _ _ n -> succ n) m 0
 
diff --git a/src/plugins/wp/VCS.mli b/src/plugins/wp/VCS.mli
index 62a7f6b5d91a1546eccaaaba409ee49884e0f22e..d8751c8fa69115c4cc1447424afffff009c162dd 100644
--- a/src/plugins/wp/VCS.mli
+++ b/src/plugins/wp/VCS.mli
@@ -55,6 +55,7 @@ val parse_prover : string -> prover option
 val pp_prover : Format.formatter -> prover -> unit
 val pp_mode : Format.formatter -> mode -> unit
 
+val eq_prover : prover -> prover -> bool
 val cmp_prover : prover -> prover -> int
 
 (* -------------------------------------------------------------------------- *)
@@ -124,7 +125,9 @@ val result : ?model:model -> ?cached:bool ->
 val is_auto : prover -> bool
 val has_counter_examples : prover -> bool
 val is_prover : prover -> bool
+val is_extern : prover -> bool
 val is_result : verdict -> bool
+val is_none : result -> bool
 val is_verdict : result -> bool
 val is_valid: result -> bool
 val is_trivial: result -> bool
@@ -135,7 +138,7 @@ val is_proved: smoke:bool -> verdict -> bool
 val configure : result -> config
 val autofit : result -> bool (** Result that fits the default configuration *)
 
-val name_of_verdict : verdict -> string
+val name_of_verdict : ?computing:bool -> verdict -> string
 
 val pp_result : Format.formatter -> result -> unit
 val pp_model : Format.formatter -> model -> unit
diff --git a/src/plugins/wp/Why3Provers.ml b/src/plugins/wp/Why3Provers.ml
index 068faf90639e77bfab3b14a73f96c956dd044db0..6bf0e1ee14dd77c16f3e667dd3752f6a02603cae 100644
--- a/src/plugins/wp/Why3Provers.ml
+++ b/src/plugins/wp/Why3Provers.ml
@@ -32,7 +32,7 @@ let cfg = lazy
       Wp_parameters.abort "%a" Why3.Exn_printer.exn_printer exn
   end
 
-let version = Why3.Config.version
+let why3_version = Why3.Config.version
 let config () = Lazy.force cfg
 
 let set_procs = Why3.Controller_itp.set_session_max_tasks
@@ -106,7 +106,20 @@ let title ?(version=true) p =
   if version then Pretty_utils.to_string Why3.Whyconf.print_prover p
   else p.Why3.Whyconf.prover_name
 let compare = Why3.Whyconf.Prover.compare
+let name p = p.Why3.Whyconf.prover_name
+let version p = p.Why3.Whyconf.prover_version
+let altern p = p.Why3.Whyconf.prover_altern
 let is_mainstream p = p.Why3.Whyconf.prover_altern = ""
+let is_auto (p : t) =
+  match p.prover_name with
+  | "Coq" | "Isabelle" -> false
+  | "Alt-Ergo" | "Z3" | "CVC4" | "CVC5" | "Colibri2" -> true
+  | _ ->
+    let config = config () in
+    try
+      let prover_config = Why3.Whyconf.get_prover_config config p in
+      not prover_config.interactive
+    with Not_found -> true
 let has_counter_examples p =
   List.mem "counterexamples" @@
   String.split_on_char '+' p.Why3.Whyconf.prover_altern
diff --git a/src/plugins/wp/Why3Provers.mli b/src/plugins/wp/Why3Provers.mli
index 7992e2b140e31a5bcb2875896f5be6fca71d9924..0142173be48e4b1e529858398f9aebcd2c679bbf 100644
--- a/src/plugins/wp/Why3Provers.mli
+++ b/src/plugins/wp/Why3Provers.mli
@@ -20,7 +20,7 @@
 (*                                                                        *)
 (**************************************************************************)
 
-val version : string
+val why3_version : string
 val config : unit -> Why3.Whyconf.config
 val configure : unit -> unit
 val set_procs : int -> unit
@@ -35,10 +35,14 @@ val find_fallback : string -> fallback
 val ident_why3 : t -> string
 val ident_wp : t -> string
 val title : ?version:bool -> t -> string
+val name : t -> string
+val version : t -> string
+val altern : t -> string
 val compare : t -> t -> int
 
 val provers : unit -> t list
 val provers_set : unit -> Why3.Whyconf.Sprover.t
+val is_auto : t -> bool
 val is_available : t -> bool
 val is_mainstream : t -> bool
 val has_counter_examples : t -> bool
diff --git a/src/plugins/wp/gui/GuiComposer.ml b/src/plugins/wp/gui/GuiComposer.ml
index 629091a8ffd4be16babd3e5345dfd97b79874f00..c8ed0c1c2d5bf1db5876f961bd121a4b3ccf5afc 100644
--- a/src/plugins/wp/gui/GuiComposer.ml
+++ b/src/plugins/wp/gui/GuiComposer.ml
@@ -179,7 +179,7 @@ class composer (focused : GuiSequent.focused) =
 
     method print (cc : GuiTactic.composer) ~quit fmt =
       begin
-        focused#set_target Tactical.Empty ;
+        focused#highlight Tactical.Empty ;
         Format.fprintf fmt "@{<bf>Selection:@} @{<ul>%s@} %t%t@\n"
           cc#title
           (focused#button ~title:"Help" ~callback:self#openhelp)
@@ -266,7 +266,7 @@ class browser (focused : GuiSequent.focused) =
 
     method print ( cc : GuiTactic.browser ) ~quit fmt =
       begin
-        focused#set_target cc#target ;
+        focused#highlight cc#target ;
         let ptitle = if paging = 10 then "5/page" else "10/page" in
         let ctitle () = paging <- if paging = 10 then 5 else 10 ; update () in
         Format.fprintf fmt "@{<bf>Selection for %s:@} %t%t@\n@\n"
diff --git a/src/plugins/wp/gui/GuiGoal.ml b/src/plugins/wp/gui/GuiGoal.ml
index d16760abc2e6427d6fe042034b0ceb232dcf9d8c..e05f708a0e5af765fb1783d3551ee4baabdbe5a9 100644
--- a/src/plugins/wp/gui/GuiGoal.ml
+++ b/src/plugins/wp/gui/GuiGoal.ml
@@ -161,8 +161,8 @@ class pane (gprovers : GuiConfig.provers) =
         scripter#on_click self#goto ;
         scripter#on_backtrack self#backtrack ;
         gprovers#connect self#register_provers ;
-        delete#connect (fun () -> self#interrupt ProofEngine.reset) ;
-        cancel#connect (fun () -> self#interrupt ProofEngine.cancel) ;
+        delete#connect (fun () -> self#interrupt ProofEngine.cancel_current_node) ;
+        cancel#connect (fun () -> self#interrupt ProofEngine.cancel_parent_tactic) ;
         forward#connect (fun () -> self#forward) ;
         next#connect (fun () -> self#navigate succ) ;
         prev#connect (fun () -> self#navigate pred) ;
@@ -279,7 +279,7 @@ class pane (gprovers : GuiConfig.provers) =
     method private play_script =
       match state with
       | Proof p ->
-        ProofEngine.reset p ;
+        ProofEngine.clear_tree p ;
         ProverScript.spawn
           ~provers:self#provers
           ~result:
@@ -382,7 +382,7 @@ class pane (gprovers : GuiConfig.provers) =
 
     method private update_tactics = function
       | None ->
-        printer#set_target Tactical.Empty ;
+        printer#highlight Tactical.Empty ;
         autosearch#connect None ;
         strategies#connect None ;
         List.iter (fun tactic -> tactic#clear) tactics
@@ -403,7 +403,7 @@ class pane (gprovers : GuiConfig.provers) =
                 tactic#select ~process ~composer ~browser ~tree sel
               ) tactics ;
             (* target selection feedback *)
-            printer#set_target
+            printer#highlight
               (if List.exists (fun tactics -> tactics#targeted) tactics
                then sel else Tactical.Empty)
           end ()
@@ -561,7 +561,7 @@ class pane (gprovers : GuiConfig.provers) =
             text#hrule ;
             scripter#tree proof ;
             text#hrule ;
-            text#printf "%a@." printer#pp_goal (ProofEngine.head_goal proof) ;
+            text#printf "@\n%a@." printer#pp_goal (ProofEngine.head_goal proof) ;
             text#printf "@{<bf>Goal id:@}  %s@." main.po_gid ;
             text#printf "@{<bf>Short id:@} %s@." main.po_sid ;
             text#hrule ;
@@ -577,7 +577,7 @@ class pane (gprovers : GuiConfig.provers) =
               self#update in
             text#printf "%t@." (composer#print cc ~quit) ;
             text#hrule ;
-            text#printf "%a@." printer#pp_goal (ProofEngine.head_goal proof) ;
+            text#printf "@\n%a@." printer#pp_goal (ProofEngine.head_goal proof) ;
           end ()
       | Browser(proof,cc,tgt) ->
         on_proof_context proof
@@ -589,7 +589,7 @@ class pane (gprovers : GuiConfig.provers) =
               self#update in
             text#printf "%t@." (browser#print cc ~quit) ;
             text#hrule ;
-            text#printf "%a@." printer#pp_goal (ProofEngine.head_goal proof) ;
+            text#printf "@\n%a@." printer#pp_goal (ProofEngine.head_goal proof) ;
           end ()
       | Forking _ -> ()
 
diff --git a/src/plugins/wp/gui/GuiNavigator.ml b/src/plugins/wp/gui/GuiNavigator.ml
index 3a1b08139c846a72deae247b8619c57214ab1703..20891f104b667857c6f733f41fcd0857fa788a9b 100644
--- a/src/plugins/wp/gui/GuiNavigator.ml
+++ b/src/plugins/wp/gui/GuiNavigator.ml
@@ -128,7 +128,7 @@ class behavior
         list#reload ;
         let to_prove g =
           not (Wpo.is_smoke_test g) &&
-          not (Wpo.is_valid g || Wpo.reduce g) in
+          not (Wpo.is_fully_valid g || Wpo.reduce g) in
         let has_proof g =
           match ProofEngine.get g with
           | `None -> false
@@ -298,7 +298,7 @@ class behavior
 
     method private popup_delete_script () =
       match popup_target with
-      | Some(w,_) -> ProofEngine.clear w ; ProofSession.remove w
+      | Some(w,_) -> ProofEngine.clear_goal w ; ProofSession.remove w
       | None -> ()
 
     method private popup_run mode () =
diff --git a/src/plugins/wp/gui/GuiProof.ml b/src/plugins/wp/gui/GuiProof.ml
index 390f2a7e2759f93d4e69ca0affb209e7d954e2fd..a7fda271b84c90c7511a93f11539b3a7204cd343 100644
--- a/src/plugins/wp/gui/GuiProof.ml
+++ b/src/plugins/wp/gui/GuiProof.ml
@@ -193,7 +193,7 @@ class printer (text : Wtext.text) =
       | `Internal here | `Leaf(_,here) ->
         begin
           let root,path = rootchain here [here] in
-          let qed = if Wpo.is_valid (ProofEngine.main tree) then "Qed" else "End"
+          let qed = if Wpo.is_fully_valid (ProofEngine.main tree) then "Qed" else "End"
           in text#printf "@[<hv 0>@{<bf>Proof@}:%a@\n@{<bf>%s@}.@]@."
             (self#proofstep ~prefix:"  " ~direct:"  " ~path ~here) root qed ;
         end
diff --git a/src/plugins/wp/ptip.ml b/src/plugins/wp/ptip.ml
index 76c0393e81500d32f4836c81862b01646a846117..2a4a5acd1da2c2568ec01cb666e65ee9a2eb1c95 100644
--- a/src/plugins/wp/ptip.ml
+++ b/src/plugins/wp/ptip.ml
@@ -488,7 +488,7 @@ class pseq
 
     method selected =
       begin
-        self#set_target self#selection ;
+        self#highlight self#selection ;
         List.iter (fun f -> f ()) demon ;
       end
 
@@ -534,7 +534,22 @@ class pseq
         in if selected then self#selected
       end
 
-    method set_target tgt =
+    method set_selection sel =
+      let current = self#selection in
+      if not @@ Tactical.equal current sel then
+        let target =
+          match sel with
+          | Tactical.Empty | Tactical.Compose _ | Tactical.Multi _ ->
+            Term, None
+          | Tactical.Clause(Goal p) -> Goal, Some (F.e_prop p)
+          | Tactical.Clause(Step s as clause) ->
+            Step s, Some (F.e_prop @@ Tactical.head clause)
+          | Tactical.Inside(Goal _,t) -> Goal, Some t
+          | Tactical.Inside(Step s,t) -> Step s, Some t
+        in
+        self#restore ~focus:`Focus target
+
+    method highlight tgt =
       match tgt with
       | Tactical.Empty | Tactical.Compose _ | Tactical.Multi _ ->
         begin
@@ -584,7 +599,7 @@ class pseq
       pcond#pp_esequent env fmt s
 
     method pp_goal fmt w =
-      Format.fprintf fmt "@\n@{<wp:clause>Goal@} %a:@\n" Wpo.pp_title w ;
+      Format.fprintf fmt "@{<wp:clause>Goal@} %a:@\n" Wpo.pp_title w ;
       let _,sequent = Wpo.compute w in
       self#pp_sequent fmt sequent
 
diff --git a/src/plugins/wp/ptip.mli b/src/plugins/wp/ptip.mli
index 38ac0c1bba86edff43d7b5e5dbc63a82c2ef1724..2616b91eca0dc59723bc9fe95d919d0e700fb260 100644
--- a/src/plugins/wp/ptip.mli
+++ b/src/plugins/wp/ptip.mli
@@ -149,7 +149,8 @@ class pseq :
 
     method sequent : Conditions.sequent
     method selection : Tactical.selection
-    method set_target : Tactical.selection -> unit
+    method set_selection : Tactical.selection -> unit
+    method highlight : Tactical.selection -> unit
 
     method pp_term : F.term printer
     method pp_pred : F.pred printer
diff --git a/src/plugins/wp/register.ml b/src/plugins/wp/register.ml
index aa5f216834730e120fd970205bda9514730b3d83..f4beb94b109d5d22fa3f753bb5d7bfaeb344c3f2 100644
--- a/src/plugins/wp/register.ml
+++ b/src/plugins/wp/register.ml
@@ -76,7 +76,7 @@ let do_print_current fmt tree =
   | `Internal node | `Leaf(_,node) -> do_print_parents fmt node
 
 let do_print_goal_status fmt (g : Wpo.t) =
-  if not (Wpo.is_valid g || Wpo.is_smoke_test g) then
+  if not (Wpo.is_fully_valid g || Wpo.is_smoke_test g) then
     begin
       do_print_index fmt g.po_idx ;
       Wpo.pp_goal fmt g ;
@@ -405,7 +405,7 @@ let do_wpo_success ~shell ~cache goal success =
       Wp_parameters.feedback ~ontty:`Silent
         "[Generated] Goal %s (%a)" (Wpo.get_gid goal) VCS.pp_prover prover
   else
-    let gui = Frama_c_very_first.Gui_init.is_gui in
+    let gui = Wp_parameters.is_interactive () in
     let smoke = Wpo.is_smoke_test goal in
     let cstats = ProofEngine.consolidated goal in
     let success = Wpo.is_passed goal in
@@ -890,7 +890,7 @@ let () = Cmdline.run_after_setting_files
 let () = Cmdline.run_after_configuring_stage Why3Provers.configure
 
 let do_prover_detect () =
-  if not Fc_config.is_gui && Wp_parameters.Detect.get () then
+  if Wp_parameters.Detect.get () && not @@ Wp_parameters.is_interactive () then
     let provers = Why3Provers.provers () in
     if provers = [] then
       Wp_parameters.result "No Why3 provers detected."
diff --git a/src/plugins/wp/wpApi.ml b/src/plugins/wp/wpApi.ml
index fb612438d63376095cbad85324c2ff2f114cdbd3..71e7be36bbfd89d60f199b51726344c16e3e367e 100644
--- a/src/plugins/wp/wpApi.ml
+++ b/src/plugins/wp/wpApi.ml
@@ -82,6 +82,106 @@ struct
     | None -> D.failure "Unknown prover name"
 end
 
+module Provers = D.Jlist(Prover)
+
+let signal = ref None
+let provers = ref None
+
+let getProvers () =
+  match !provers with
+  | Some prvs -> prvs
+  | None ->
+    let cmdline =
+      match Wp_parameters.Provers.get () with
+      | [] -> [ "alt-ergo" ]
+      | prvs -> prvs in
+    let parse s =
+      match VCS.parse_prover s with
+      | None -> None
+      | Some (Qed | Tactical) -> None
+      | Some prv as result -> if VCS.is_auto prv then result else None in
+    let selection = List.filter_map parse cmdline in
+    provers := Some selection ; selection
+
+let updProvers prv = provers := Some prv
+let setProvers prv = updProvers prv ; Option.iter (fun s -> R.emit s) !signal
+
+let () =
+  let s =
+    S.register_state ~package ~name:"provers"
+      ~descr:(Md.plain "Selected Provers")
+      ~data:(module Provers)
+      ~get:getProvers
+      ~set:updProvers ()
+  in signal := Some s
+
+(* -------------------------------------------------------------------------- *)
+(* --- Server Processes                                                   --- *)
+(* -------------------------------------------------------------------------- *)
+
+let _ =
+  S.register_state ~package
+    ~name:"process"
+    ~descr:(Md.plain "Server Processes")
+    ~data:(module D.Jint)
+    ~get:Wp_parameters.Procs.get
+    ~set:(fun procs ->
+        Wp_parameters.Procs.set procs ;
+        ignore @@ ProverTask.server ~procs ())
+    ~add_hook:Wp_parameters.Procs.add_hook_on_update ()
+
+(* -------------------------------------------------------------------------- *)
+(* --- Provers Timeout                                                    --- *)
+(* -------------------------------------------------------------------------- *)
+
+let _ =
+  S.register_state ~package
+    ~name:"timeout"
+    ~descr:(Md.plain "Prover's Timeout")
+    ~data:(module D.Jint)
+    ~get:Wp_parameters.Timeout.get
+    ~set:Wp_parameters.Timeout.set
+    ~add_hook:Wp_parameters.Timeout.add_hook_on_update ()
+
+(* -------------------------------------------------------------------------- *)
+(* --- Available Provers                                                  --- *)
+(* -------------------------------------------------------------------------- *)
+
+let get_name = function
+  | VCS.Qed -> "Qed"
+  | VCS.Tactical -> "Script"
+  | VCS.Why3 p -> Why3Provers.name p
+
+let get_version = function
+  | VCS.Qed | Tactical -> Fc_config.version_and_codename
+  | Why3 p -> Why3Provers.version p
+
+let iter_provers fn =
+  List.iter
+    (fun p ->
+       if Why3Provers.is_auto p && Why3Provers.is_mainstream p then
+         fn (VCS.Why3 p))
+  @@ Why3Provers.provers ()
+
+let _ : VCS.prover S.array =
+  let model = S.model () in
+  S.column ~name:"name" ~descr:(Md.plain "Prover Name")
+    ~data:(module D.Jalpha) ~get:get_name model ;
+  S.column ~name:"version" ~descr:(Md.plain "Prover Version")
+    ~data:(module D.Jalpha) ~get:get_version model ;
+  S.column ~name:"descr" ~descr:(Md.plain "Prover Full Name (description)")
+    ~data:(module D.Jalpha) ~get:(VCS.title_of_prover ~version:true) model ;
+  S.register_array ~package
+    ~name:"ProverInfos" ~descr:(Md.plain "Available Provers")
+    ~key:VCS.name_of_prover
+    ~keyName:"prover"
+    ~keyType:Prover.jtype
+    ~iter:iter_provers model
+
+(* -------------------------------------------------------------------------- *)
+(* --- Results and Stats                                                  --- *)
+(* -------------------------------------------------------------------------- *)
+
 module Result =
 struct
   type t = VCS.result
@@ -99,7 +199,7 @@ struct
   let to_json (r : VCS.result) = `Assoc [
       "descr", `String (Pretty_utils.to_string VCS.pp_result r) ;
       "cached", `Bool r.cached ;
-      "verdict", `String (VCS.name_of_verdict r.verdict) ;
+      "verdict", `String (VCS.name_of_verdict ~computing:true r.verdict) ;
       "solverTime", `Float r.solver_time ;
       "proverTime", `Float r.prover_time ;
       "proverSteps", `Int r.prover_steps ;
@@ -157,14 +257,6 @@ struct
     ]
 end
 
-let () = R.register ~package ~kind:`GET ~name:"getAvailableProvers"
-    ~descr:(Md.plain "Returns the list of configured provers from why3")
-    ~input:(module D.Junit) ~output:(module D.Jlist(Prover))
-    (fun () ->
-       List.map (fun p -> VCS.Why3 p) @@
-       List.filter Why3Provers.is_mainstream @@
-       Why3Provers.provers ())
-
 (* -------------------------------------------------------------------------- *)
 (* --- Goal Array                                                         --- *)
 (* -------------------------------------------------------------------------- *)
@@ -250,6 +342,11 @@ let () = S.column gmodel ~name:"stats"
     ~descr:(Md.plain "Prover Stats Summary")
     ~data:(module STATS) ~get:ProofEngine.consolidated
 
+let () = S.column gmodel ~name:"proof"
+    ~descr:(Md.plain "Proof Tree")
+    ~data:(module D.Jbool)
+    ~get:ProofEngine.has_proof
+
 let () = S.option gmodel ~name:"script"
     ~descr:(Md.plain "Script File")
     ~data:(module D.Jstring)
@@ -263,15 +360,25 @@ let () = S.column gmodel ~name:"saved"
     ~data:(module D.Jbool)
     ~get:(fun wpo -> ProofEngine.get wpo = `Saved)
 
-let _ = S.register_array ~package ~name:"goals"
+let filter hook fn = hook (fun g -> if not @@ Wpo.is_tactic g then fn g)
+let (++) h1 h2 fn = h1 fn ; h2 fn
+
+let goals =
+  let add_remove_hook =
+    filter Wpo.add_removed_hook in
+  let add_update_hook =
+    filter Wpo.add_modified_hook ++ ProofEngine.add_goal_hook in
+  let add_reload_hook = Wpo.add_cleared_hook in
+  S.register_array ~package ~name:"goals"
     ~descr:(Md.plain "Generated Goals")
     ~key:indexGoal
     ~keyName:"wpo"
     ~keyType:Goal.jtype
-    ~iter:Wpo.iter_on_goals
-    ~add_update_hook:Wpo.add_modified_hook
-    ~add_remove_hook:Wpo.add_removed_hook
-    ~add_reload_hook:Wpo.add_cleared_hook
+    ~iter:(filter Wpo.iter_on_goals)
+    ~preload:ProofEngine.consolidate
+    ~add_remove_hook
+    ~add_update_hook
+    ~add_reload_hook
     gmodel
 
 (* -------------------------------------------------------------------------- *)
diff --git a/src/plugins/wp/wpApi.mli b/src/plugins/wp/wpApi.mli
index a2323ddac91d5b7e4a088deab6246f466fad8f67..89db11c333a78558ff655cd9aeaf86a355665dfb 100644
--- a/src/plugins/wp/wpApi.mli
+++ b/src/plugins/wp/wpApi.mli
@@ -27,7 +27,12 @@
 val package : Server.Package.package
 
 module Prover : Server.Data.S with type t = VCS.prover
+module Provers : Server.Data.S with type t = VCS.prover list
 module Result : Server.Data.S with type t = VCS.result
 module Goal : Server.Data.S with type t = Wpo.t
 
+val goals : Wpo.t Server.States.array
+val getProvers : unit -> VCS.prover list
+val setProvers : VCS.prover list -> unit
+
 (* -------------------------------------------------------------------------- *)
diff --git a/src/plugins/wp/wpContext.ml b/src/plugins/wp/wpContext.ml
index 238d6c163ac643dac8ed5d664298ac9d988eb6c0..b940a579c95cdcc6921da6a0edbfc270b591129c 100644
--- a/src/plugins/wp/wpContext.ml
+++ b/src/plugins/wp/wpContext.ml
@@ -173,8 +173,8 @@ sig
 
   val id : basename:string -> key -> string
   val mem : key -> bool
-  val find : key -> data
   val get : key -> data option
+  val find : key -> data
   val clear : unit -> unit
   val remove : key -> unit
   val define : key -> data -> unit
@@ -493,6 +493,7 @@ sig
   val mem : key -> bool
   val get : key -> data
   val set : key -> data -> unit
+  val find : key -> data
   val remove : key -> unit
   val clear : unit -> unit
 end
@@ -509,6 +510,7 @@ struct
   type key = D.key
   type data = D.data
   let get = G.memoize D.compile
+  let find = G.find
   let set = G.update
   let mem = G.mem
   let clear = G.clear
@@ -529,6 +531,7 @@ struct
   let get = G.memoize D.compile
   let set = G.update
   let mem = G.mem
+  let find = G.find
   let clear = G.clear
   let remove = G.remove
 end
@@ -547,6 +550,7 @@ struct
   let get = G.memoize (fun k -> D.compile k (G.id ~basename:(D.basename k) k))
   let set = G.update
   let mem = G.mem
+  let find = G.find
   let clear = G.clear
   let remove = G.remove
 end
@@ -565,6 +569,7 @@ struct
   let get = G.memoize (fun k -> D.compile k (G.id ~basename:(D.basename k) k))
   let set = G.update
   let mem = G.mem
+  let find = G.find
   let clear = G.clear
   let remove = G.remove
 end
diff --git a/src/plugins/wp/wpContext.mli b/src/plugins/wp/wpContext.mli
index c1cd19d4c0f58db0c274a19707e3d2dcdd3e415f..bba749f0a9e4932e38ee560228bf7b09a1b70cf0 100644
--- a/src/plugins/wp/wpContext.mli
+++ b/src/plugins/wp/wpContext.mli
@@ -108,8 +108,8 @@ sig
 
   val id : basename:string -> key -> string
   val mem : key -> bool
-  val find : key -> data
   val get : key -> data option
+  val find : key -> data
   val clear : unit -> unit
   val remove : key -> unit
   val define : key -> data -> unit
@@ -167,6 +167,7 @@ sig
   val mem : key -> bool
   val get : key -> data
   val set : key -> data -> unit
+  val find : key -> data
   val remove : key -> unit
   val clear : unit -> unit
 end
diff --git a/src/plugins/wp/wpTacApi.ml b/src/plugins/wp/wpTacApi.ml
index 8aec7bffc6a821cfcd523ab067e4bc09417a9c80..fc8fed00054113b2e975393eccc114fd4a23f081 100644
--- a/src/plugins/wp/wpTacApi.ml
+++ b/src/plugins/wp/wpTacApi.ml
@@ -21,7 +21,7 @@
 (**************************************************************************)
 
 (* -------------------------------------------------------------------------- *)
-(* --- Server API for WP                                                  --- *)
+(* --- Server API for Tactics                                             --- *)
 (* -------------------------------------------------------------------------- *)
 
 module P = Server.Package
@@ -30,22 +30,11 @@ module R = Server.Request
 module S = Server.States
 module Md = Markdown
 module AST = Server.Kernel_ast
+module Jtactic = WpTipApi.Tactic
 
 let package = P.package ~plugin:"wp" ~name:"tac"
     ~title:"WP Tactics" ()
 
-(* -------------------------------------------------------------------------- *)
-(* --- Tacticals                                                          --- *)
-(* -------------------------------------------------------------------------- *)
-
-module Jtactic : D.S with type t = Tactical.t =
-struct
-  type t = Tactical.t
-  let jtype = P.Jkey "tactic"
-  let to_json (t : Tactical.t) = `String t#id
-  let of_json (js : Json.t) = Tactical.lookup ~id:(js |> Json.string)
-end
-
 (* -------------------------------------------------------------------------- *)
 (* --- Tactical Kind                                                      --- *)
 (* -------------------------------------------------------------------------- *)
@@ -405,6 +394,7 @@ class configurator (tactic : Tactical.tactical) =
         let jtactic = ProofScript.jtactic tactic target in
         let fork = ProofEngine.fork tree ~anchor:node jtactic process in
         let children = snd @@ ProofEngine.commit fork in
+        ProofEngine.forward tree ;
         List.map snd children
       with exn ->
         local <- None ;
@@ -465,20 +455,35 @@ let tactics =
 (* --- Tactical Target Configuration                                      --- *)
 (* -------------------------------------------------------------------------- *)
 
+let configure jtactic node =
+  let sequent = snd @@ Wpo.compute @@ ProofEngine.goal node in
+  match ProofScript.configure jtactic sequent with
+  | None -> ()
+  | Some(tactical,selection) ->
+    begin
+      let console = (configurator tactical :> Tactical.feedback) in
+      console#set_title "%s" tactical#title ;
+      WpTipApi.setSelection node selection ;
+    end
+
 let () =
-  let configureTactics = R.signature ~output:(module D.Junit) () in
-  let get_node = R.param configureTactics ~name:"node"
-      ~descr:(Md.plain "Proof node target") (module WpTipApi.Node) in
-  R.register_sig ~package ~kind:`EXEC
+  R.register ~package
+    ~kind:`EXEC
     ~name:"configureTactics"
     ~descr:(Md.plain "Configure all tactics")
+    ~input:(module WpTipApi.Node)
+    ~output:(module D.Junit)
     ~signals:[WpTipApi.printStatus]
-    configureTactics
-    begin fun rq () ->
-      let node = get_node rq in
-      let selection = WpTipApi.selection node in
-      iter (fun cfg -> cfg#configure node selection) ;
-      S.reload tactics ;
+    begin fun node ->
+      match ProofEngine.tactical node with
+      | None ->
+        let selection = WpTipApi.selection node in
+        iter (fun cfg -> cfg#configure node selection) ;
+        S.reload tactics ;
+      | Some jtactic ->
+        let ctxt = ProofEngine.node_context node in
+        WpContext.on_context ctxt (configure jtactic) node ;
+        S.reload tactics ;
     end
 
 (* -------------------------------------------------------------------------- *)
@@ -528,4 +533,19 @@ let () = R.register
       children
     end
 
+let () = R.register
+    ~package ~kind:`EXEC
+    ~name:"applyTacticAndProve"
+    ~descr:(Md.plain "Applies tactic and run provers on children")
+    ~input:(module Jtactic)
+    ~output:(module D.Jlist(WpTipApi.Node))
+    begin fun tactic ->
+      let cfg = configurator tactic in
+      let children = cfg#apply in
+      S.update tactics cfg ;
+      List.iter WpTipApi.runProvers children ;
+      children
+    end
+
+
 (* -------------------------------------------------------------------------- *)
diff --git a/src/plugins/wp/wpTipApi.ml b/src/plugins/wp/wpTipApi.ml
index 256d87982b99c0a9ffe972dd30d9db65fcd2e377..6a686258ec2e012071a4a33250bcf377a89569af 100644
--- a/src/plugins/wp/wpTipApi.ml
+++ b/src/plugins/wp/wpTipApi.ml
@@ -24,7 +24,7 @@
 (* --- Server API for WP                                                  --- *)
 (* -------------------------------------------------------------------------- *)
 
-open WpApi
+module W = WpApi
 module P = Server.Package
 module D = Server.Data
 module R = Server.Request
@@ -36,12 +36,19 @@ let package = P.package ~plugin:"wp" ~name:"tip"
     ~title:"WP Interactive Prover" ()
 
 (* -------------------------------------------------------------------------- *)
-(* --- Proof Node                                                         --- *)
+(* --- Signals                                                            --- *)
 (* -------------------------------------------------------------------------- *)
 
 let proofStatus = R.signal ~package ~name:"proofStatus"
     ~descr:(Md.plain "Proof Status has changed")
 
+let printStatus = R.signal ~package ~name:"printStatus"
+    ~descr:(Md.plain "Updated TIP printer")
+
+(* -------------------------------------------------------------------------- *)
+(* --- Proof Node                                                         --- *)
+(* -------------------------------------------------------------------------- *)
+
 module Node = D.Index
     (Map.Make(ProofEngine.Node))
     (struct
@@ -50,9 +57,30 @@ module Node = D.Index
       let descr = Md.plain "Proof Node index"
     end)
 
+let () = Server.Main.once
+    begin fun () ->
+      ProofEngine.add_remove_hook Node.remove ;
+      ProofEngine.add_clear_hook (fun _ -> Node.clear ()) ;
+      let signal _ = R.emit proofStatus in
+      ProofEngine.add_update_hook signal ;
+      ProofEngine.add_goal_hook signal ;
+      Wpo.add_modified_hook signal ;
+    end
+
+module Tactic : D.S with type t = Tactical.t =
+struct
+  type t = Tactical.t
+  let jtype = D.declare ~package ~name:"tactic"
+      ~descr:(Md.plain "Tactic identifier") @@ P.Jkey "tactic"
+  let to_json (t : Tactical.t) = `String t#id
+  let of_json (js : Json.t) = Tactical.lookup ~id:(js |> Json.string)
+end
+
+module Path = D.Jlist(Node)
+
 let () =
   let inode = R.signature ~input:(module Node) () in
-  let set_title = R.result inode ~name:"result"
+  let set_title = R.result inode ~name:"title"
       ~descr:(Md.plain "Proof node title") (module D.Jstring) in
   let set_proved = R.result inode ~name:"proved"
       ~descr:(Md.plain "Proof node complete") (module D.Jbool) in
@@ -64,65 +92,109 @@ let () =
       ~descr:(Md.plain "Node statistics") (module D.Jstring) in
   let set_results = R.result inode ~name:"results"
       ~descr:(Md.plain "Prover results for current node")
-      (module D.Jlist(D.Jpair(Prover)(Result))) in
-  let set_tactic = R.result inode ~name:"tactic"
-      ~descr:(Md.plain "Proof node tactic header (if any)")
+      (module D.Jlist(D.Jpair(W.Prover)(W.Result))) in
+  let set_tactic = R.result_opt inode ~name:"tactic"
+      ~descr:(Md.plain "Applied tactic (if any)")
+      (module Tactic) in
+  let set_header = R.result_opt inode ~name:"header"
+      ~descr:(Md.plain "Proof node tactic label (if any)")
       (module D.Jstring) in
+  let set_child = R.result_opt inode ~name:"child"
+      ~descr:(Md.plain "Proof node child label (from parent, if any)")
+      (module D.Jstring) in
+  let set_path = R.result inode ~name:"path"
+      ~descr:(Md.plain "Proof node path from goal")
+      (module Path) in
   let set_children = R.result inode ~name:"children"
       ~descr:(Md.plain "Proof node tactic children (id any)")
-      (module D.Jlist(D.Jpair(D.Jstring)(Node))) in
-  R.register_sig ~package ~kind:`GET ~name:"getNodeInfos"
-    ~descr:(Md.plain "Proof node information") inode
+      (module Path) in
+  R.register_sig inode ~package ~kind:`GET ~name:"getNodeInfos"
+    ~descr:(Md.plain "Proof node information")
     ~signals:[proofStatus]
     begin fun rq node ->
-      set_title rq (ProofEngine.title node) ;
-      set_proved rq (ProofEngine.proved node) ;
-      set_pending rq (ProofEngine.pending node) ;
+      set_title rq (ProofEngine.title node);
+      set_proved rq (ProofEngine.fully_proved node);
+      set_pending rq (ProofEngine.pending node);
       let s = ProofEngine.stats node in
-      let tactic =
-        match ProofEngine.tactical node with
-        | None -> ""
-        | Some { header } -> header in
-      set_size rq (Stats.subgoals s) ;
-      set_stats rq (Pretty_utils.to_string Stats.pretty s) ;
-      set_results rq (Wpo.get_results (ProofEngine.goal node)) ;
-      set_tactic rq tactic ;
-      set_children rq (ProofEngine.children node) ;
+      set_size rq (Stats.subgoals @@ s);
+      set_stats rq (Pretty_utils.to_string Stats.pretty s);
+      set_results rq (Wpo.get_results @@ ProofEngine.goal node);
+      set_tactic rq (ProofEngine.tactic node);
+      set_header rq (ProofEngine.tactic_label node);
+      set_child rq (ProofEngine.child_label node);
+      set_path rq (ProofEngine.path node);
+      set_children rq (ProofEngine.subgoals node);
+    end
+
+let () =
+  let iresult = R.signature ~output:(module W.Result) () in
+  let get_node = R.param iresult ~name:"node"
+      ~descr:(Md.plain "Proof node") (module Node) in
+  let get_prover = R.param iresult ~name:"prover"
+      ~descr:(Md.plain "Prover") (module W.Prover) in
+  R.register_sig iresult ~package ~kind:`GET ~name:"getResult"
+    ~descr:(Md.plain "Result for specified node and prover")
+    ~signals:[proofStatus]
+    begin fun rq () ->
+      let node = get_node rq in
+      let prover = get_prover rq in
+      Wpo.get_result (ProofEngine.goal node) prover
     end
 
 (* -------------------------------------------------------------------------- *)
-(* --- Proof Tree                                                         --- *)
+(* --- Proof Cursor                                                       --- *)
 (* -------------------------------------------------------------------------- *)
 
 let () =
-  let state = R.signature ~input:(module Goal) () in
-  let set_current = R.result state ~name:"current"
-      ~descr:(Md.plain "Current proof node") (module Node) in
-  let set_path = R.result state ~name:"path"
-      ~descr:(Md.plain "Proof node parents") (module D.Jlist(Node)) in
+  let state = R.signature ~input:(module W.Goal) () in
   let set_index = R.result state ~name:"index"
       ~descr:(Md.plain "Current node index among pending nodes (else -1)")
       (module D.Jint) in
   let set_pending = R.result state ~name:"pending"
       ~descr:(Md.plain "Pending proof nodes") (module D.Jint) in
+  let set_current = R.result state ~name:"current"
+      ~descr:(Md.plain "Current proof node") (module Node) in
+  let set_above = R.result state ~name:"above"
+      ~descr:(Md.plain "Above nodes (up to current when internal)")
+      (module Path) in
+  let set_below = R.result state ~name:"below"
+      ~descr:(Md.plain "Below nodes (including current when pending)")
+      (module Path) in
+  let set_tactic = R.result_opt state ~name:"tactic"
+      ~descr:(Md.plain "Applied tactic (if any)")
+      (module Tactic) in
   R.register_sig ~package
-    ~kind:`GET ~name:"getProofState"
+    ~kind:`GET ~name:"getProofStatus"
     ~descr:(Md.plain "Current Proof Status of a Goal") state
     ~signals:[proofStatus]
     begin fun rq wpo ->
-      let tree = ProofEngine.proof ~main:wpo in
-      let current,index =
+      let tree = ProofEngine.proof  ~main:wpo in
+      let root = ProofEngine.root tree in
+      set_pending rq (ProofEngine.pending root) ;
+      let current, index =
         match ProofEngine.current tree with
-        | `Main -> ProofEngine.root tree,-1
-        | `Internal node -> node,-1
-        | `Leaf(idx,node) -> node,idx in
-      let rec path node = match ProofEngine.parent node with
-        | None -> []
-        | Some p -> p::path p in
-      set_current rq current ;
-      set_path rq (path current) ;
+        | `Main -> root, -1
+        | `Internal node -> node, -1
+        | `Leaf(idx,node) -> node, idx
+      in
       set_index rq index ;
-      set_pending rq (ProofEngine.pending current) ;
+      set_current rq current ;
+      set_tactic rq @@ ProofEngine.tactic current ;
+      let above = ProofEngine.path current in
+      let below = ProofEngine.subgoals current in
+      if below = [] then
+        match above with
+        | [] ->
+          set_above rq [] ;
+          set_below rq [] ;
+        | p::_ ->
+          set_above rq above ;
+          set_below rq (ProofEngine.subgoals p) ;
+      else
+        begin
+          set_above rq (current::above) ;
+          set_below rq below ;
+        end
     end
 
 (* -------------------------------------------------------------------------- *)
@@ -131,29 +203,26 @@ let () =
 
 let () = R.register ~package ~kind:`SET ~name:"goForward"
     ~descr:(Md.plain "Go to to first pending node, or root if none")
-    ~input:(module Goal) ~output:(module D.Junit)
+    ~input:(module W.Goal) ~output:(module D.Junit)
     begin fun goal ->
       let tree = ProofEngine.proof ~main:goal in
       ProofEngine.forward tree ;
-      R.emit proofStatus ;
     end
 
 let () = R.register ~package ~kind:`SET ~name:"goToRoot"
     ~descr:(Md.plain "Go to root of proof tree")
-    ~input:(module Goal) ~output:(module D.Junit)
+    ~input:(module W.Goal) ~output:(module D.Junit)
     begin fun goal ->
       let tree = ProofEngine.proof ~main:goal in
       ProofEngine.goto tree `Main ;
-      R.emit proofStatus ;
     end
 
 let () = R.register ~package ~kind:`SET ~name:"goToIndex"
     ~descr:(Md.plain "Go to k-th pending node of proof tree")
-    ~input:(module D.Jpair(Goal)(D.Jint)) ~output:(module D.Junit)
+    ~input:(module D.Jpair(W.Goal)(D.Jint)) ~output:(module D.Junit)
     begin fun (goal,index) ->
       let tree = ProofEngine.proof ~main:goal in
       ProofEngine.goto tree (`Leaf index) ;
-      R.emit proofStatus ;
     end
 
 let () = R.register ~package ~kind:`SET ~name:"goToNode"
@@ -162,16 +231,38 @@ let () = R.register ~package ~kind:`SET ~name:"goToNode"
     begin fun node ->
       let tree = ProofEngine.tree node in
       ProofEngine.goto tree (`Node node) ;
-      R.emit proofStatus ;
     end
 
-let () = R.register ~package ~kind:`SET ~name:"removeNode"
-    ~descr:(Md.plain "Remove node from tree and go to parent")
+let () = R.register ~package ~kind:`SET ~name:"clearNode"
+    ~descr:(Md.plain "Cancel all node results and sub-tree (if any)")
+    ~input:(module Node) ~output:(module D.Junit)
+    begin fun node ->
+      let tree = ProofEngine.tree node in
+      ProofEngine.clear_node tree node ;
+    end
+
+let () = R.register ~package ~kind:`SET ~name:"clearNodeTactic"
+    ~descr:(Md.plain "Cancel node current tactic")
     ~input:(module Node) ~output:(module D.Junit)
     begin fun node ->
       let tree = ProofEngine.tree node in
-      ProofEngine.remove tree node ;
-      R.emit proofStatus ;
+      ProofEngine.clear_node_tactic tree node ;
+    end
+
+let () = R.register ~package ~kind:`SET ~name:"clearParentTactic"
+    ~descr:(Md.plain "Cancel parent node tactic")
+    ~input:(module Node) ~output:(module D.Junit)
+    begin fun node ->
+      let tree = ProofEngine.tree node in
+      ProofEngine.clear_parent_tactic tree node ;
+    end
+
+let () = R.register ~package ~kind:`SET ~name:"clearGoal"
+    ~descr:(Md.plain "Remove the complete goal proof tree")
+    ~input:(module W.Goal) ~output:(module D.Junit)
+    begin fun goal ->
+      let tree = ProofEngine.proof ~main:goal in
+      ProofEngine.clear_tree tree ;
     end
 
 (* -------------------------------------------------------------------------- *)
@@ -259,9 +350,6 @@ class printer () : Ptip.pseq =
 (* --- Printer Registry                                                   --- *)
 (* -------------------------------------------------------------------------- *)
 
-let printStatus = R.signal ~package ~name:"printStatus"
-    ~descr:(Md.plain "Updated TIP printer")
-
 module PRINTER = State_builder.Ref
     (Datatype.Make
        (struct
@@ -287,17 +375,17 @@ let () = Wpo.add_cleared_hook
        let registry = PRINTER.get () in
        Hashtbl.clear registry)
 
-let lookup_printer (node : ProofEngine.node) : printer =
+let lookup (node : ProofEngine.node) : printer =
   let tree = ProofEngine.tree node in
   let wpo = ProofEngine.main tree in
   let registry = PRINTER.get () in
   try Hashtbl.find registry wpo.po_gid with Not_found ->
     let pp = new printer () in
+    pp#on_selection (fun () -> R.emit printStatus) ;
     Hashtbl.add registry wpo.po_gid pp ; pp
 
-let selection node =
-  let pp = lookup_printer node in
-  pp#selection
+let selection node = (lookup node)#selection
+let setSelection node = (lookup node)#set_selection
 
 (* -------------------------------------------------------------------------- *)
 (* --- PrintSequent Request                                               --- *)
@@ -344,7 +432,7 @@ let () =
       match get_node rq with
       | None -> D.jtext ""
       | Some node ->
-        let pp = lookup_printer node in
+        let pp = lookup node in
         let indent = get_indent rq in
         let margin = get_margin rq in
         Option.iter pp#set_iformat (get_iformat rq) ;
@@ -366,10 +454,9 @@ let () =
     ~input:(module Node)
     ~output:(module D.Junit)
     begin fun node ->
-      let pp = lookup_printer node in
+      let pp = lookup node in
       pp#reset ;
       pp#selected ;
-      R.emit printStatus
     end
 
 let () =
@@ -393,10 +480,9 @@ let () =
       let part = get_part rq in
       let term = get_term rq in
       let extend = get_extend rq in
-      let pp = lookup_printer node in
+      let pp = lookup node in
       let part = to_part (fst pp#sequent) part in
       pp#restore ~focus:(if extend then `Extend else `Focus) (part,term) ;
-      R.emit printStatus
     end
 
 let () =
@@ -409,12 +495,193 @@ let () =
     ~kind:`GET
     ~name:"getSelection"
     ~descr:(Md.plain "Get current selection in proof node")
-    ~signals:[printStatus]
+    ~signals:[printStatus;proofStatus]
     getSelection
     begin fun rq node ->
-      let (part,term) = (lookup_printer node)#target in
+      let (part,term) = (lookup node)#target in
       set_part rq (if part <> Term then Some (of_part part) else None);
       set_term rq term;
     end
 
 (* -------------------------------------------------------------------------- *)
+(* --- Prover Scheduling                                                  --- *)
+(* -------------------------------------------------------------------------- *)
+
+let runProvers ?timeout ?provers node =
+  let wpo = ProofEngine.goal node in
+  let provers = match provers with
+    | Some ps -> ps
+    | None -> WpApi.getProvers () in
+  let timeout = match timeout with
+    | Some t -> t
+    | None -> Wp_parameters.Timeout.get () in
+  let config =
+    let cfg = VCS.current () in
+    { cfg with timeout = Some (float timeout) } in
+  if not @@ Wpo.is_trivial wpo then
+    List.iter
+      (fun prv ->
+         let backup = Wpo.get_result wpo prv in
+         let result _ p r =
+           if p = VCS.Qed && VCS.is_valid r then
+             Wpo.set_result wpo prv VCS.no_result
+           else if not @@ VCS.is_verdict r then
+             Wpo.set_result wpo prv backup in
+         let process () = Prover.prove ~config ~result wpo prv in
+         let thread = Task.thread @@ Task.later process () in
+         let status = VCS.computing (fun () -> Task.cancel thread) in
+         Wpo.set_result wpo prv status ;
+         let server = ProverTask.server () in
+         Task.spawn server thread ;
+         Task.launch server ;
+      ) provers
+
+let () =
+  let iRunProvers = R.signature ~output:(module D.Junit) () in
+  let get_node = R.param iRunProvers (module Node)
+      ~name:"node" ~descr:(Md.plain "Proof node") in
+  let get_timeout = R.param_opt iRunProvers (module D.Jint)
+      ~name:"timeout"
+      ~descr:(Md.plain "Prover timeout (in seconds, default: current)") in
+  let get_provers = R.param_opt iRunProvers (module WpApi.Provers)
+      ~name:"provers"
+      ~descr:(Md.plain "Prover selection (default: current") in
+  R.register_sig iRunProvers ~package ~kind:`SET
+    ~name:"runProvers"
+    ~descr:(Md.plain "Schedule provers on proof node")
+    begin fun rq () ->
+      let node = get_node rq in
+      let provers = get_provers rq in
+      let timeout = get_timeout rq in
+      runProvers ?timeout ?provers node
+    end
+
+let killProvers ?provers node =
+  let wpo = ProofEngine.goal node in
+  let filter =
+    match provers with
+    | None | Some [] -> fun _ -> true
+    | Some prvs -> fun p -> List.exists (VCS.eq_prover p) prvs
+  in
+  List.iter
+    (fun (prv,res) ->
+       match res.VCS.verdict with
+       | Computing kill when filter prv -> kill ()
+       | _ -> ()
+    ) @@ Wpo.get_results wpo
+
+let () =
+  let iKillProvers = R.signature ~output:(module D.Junit) () in
+  let get_node = R.param iKillProvers (module Node)
+      ~name:"node" ~descr:(Md.plain "Proof node") in
+  let get_provers = R.param_opt iKillProvers (module WpApi.Provers)
+      ~name:"provers"
+      ~descr:(Md.plain "Prover selection (default: all running provers") in
+  R.register_sig iKillProvers ~package ~kind:`SET
+    ~name:"killProvers"
+    ~descr:(Md.plain "Interrupt running provers on proof node")
+    begin fun rq () ->
+      let node = get_node rq in
+      let provers = get_provers rq in
+      killProvers ?provers node
+    end
+
+let clearProvers ?provers node =
+  let wpo = ProofEngine.goal node in
+  let clear p = if VCS.is_extern p then Wpo.set_result wpo p VCS.no_result in
+  begin
+    match provers with
+    | None -> List.iter (fun (prv,_) -> clear prv) @@ Wpo.get_results wpo ;
+    | Some prvs -> List.iter clear prvs
+  end
+
+let () =
+  let iClearProvers = R.signature ~output:(module D.Junit) () in
+  let get_node = R.param iClearProvers (module Node)
+      ~name:"node" ~descr:(Md.plain "Proof node") in
+  let get_provers = R.param_opt iClearProvers (module WpApi.Provers)
+      ~name:"provers"
+      ~descr:(Md.plain "Prover selection (default: all results") in
+  R.register_sig iClearProvers ~package ~kind:`SET
+    ~name:"clearProvers"
+    ~descr:(Md.plain "Remove prover results from proof node")
+    begin fun rq () ->
+      let node = get_node rq in
+      let provers = get_provers rq in
+      clearProvers ?provers node
+    end
+
+(* -------------------------------------------------------------------------- *)
+(* --- Script Management                                                  --- *)
+(* -------------------------------------------------------------------------- *)
+
+let () =
+  let script = R.signature ~input:(module WpApi.Goal) () in
+  let set_proof = R.result script (module D.Jbool)
+      ~name:"proof" ~descr:(Md.plain "Some Proof Tree can be Saved") in
+  let set_script = R.result_opt script (module D.Jstring)
+      ~name:"script" ~descr:(Md.plain "Script File (if any)") in
+  let set_saved = R.result script (module D.Jbool)
+      ~name:"saved" ~descr:(Md.plain "Current Proof Script has been Saved") in
+  R.register_sig script ~package ~kind:`GET
+    ~name:"getScriptStatus"
+    ~descr:(Md.plain "Script Status for a given Goal")
+    ~signals:[proofStatus]
+    begin fun rq goal ->
+      let tree = ProofEngine.proof ~main:goal in
+      set_saved rq (ProofEngine.saved tree) ;
+      set_proof rq (ProofEngine.has_script tree) ;
+      set_script rq
+        begin
+          match ProofSession.get goal with
+          | NoScript -> None | Script file | Deprecated file ->
+            Some (file :> string)
+        end ;
+    end
+
+let () =
+  R.register ~package ~kind:`SET
+    ~name:"saveScript" ~descr:(Md.plain "Save Script for the Goal")
+    ~input:(module WpApi.Goal)
+    ~output:(module D.Junit)
+    begin fun goal ->
+      let tree = ProofEngine.proof ~main:goal in
+      let json = ProofScript.encode (ProofEngine.script tree) in
+      ProofSession.save ~stdout:false goal json ;
+      ProofEngine.set_saved tree true ;
+    end
+
+let () =
+  R.register ~package ~kind:`SET
+    ~name:"runScript"
+    ~descr:(Md.plain "Replay Saved Script for the Goal (if any)")
+    ~input:(module WpApi.Goal)
+    ~output:(module D.Junit)
+    begin fun goal ->
+      let tree = ProofEngine.proof ~main:goal in
+      ProofEngine.clear_tree tree ;
+      ProverScript.spawn
+        ~provers:(WpApi.getProvers())
+        ~success:(fun _ _ -> ProofEngine.forward tree)
+        goal ;
+      let server = ProverTask.server () in
+      Task.launch server ;
+    end
+
+(* -------------------------------------------------------------------------- *)
+(* --- Full Trash                                                         --- *)
+(* -------------------------------------------------------------------------- *)
+
+let () =
+  R.register ~package ~kind:`SET
+    ~name:"clearProofScript"
+    ~descr:(Md.plain "Clear Proof and Remove any Saved Script for the Goal")
+    ~input:(module WpApi.Goal)
+    ~output:(module D.Junit)
+    begin fun goal ->
+      let tree = ProofEngine.proof ~main:goal in
+      ProofEngine.clear_tree tree ;
+      ProofSession.remove goal ;
+    end
+
+(* -------------------------------------------------------------------------- *)
diff --git a/src/plugins/wp/wpTipApi.mli b/src/plugins/wp/wpTipApi.mli
index 7ffb03f69460e1812662c498ac389f19d700aa87..89461a68543d9ccbf65c4ff49b2732c151266140 100644
--- a/src/plugins/wp/wpTipApi.mli
+++ b/src/plugins/wp/wpTipApi.mli
@@ -25,8 +25,24 @@
 (* -------------------------------------------------------------------------- *)
 
 module Node : Server.Data.S with type t = ProofEngine.node
+module Tactic : Server.Data.S with type t = Tactical.t
 
+val proofStatus : Server.Request.signal
 val printStatus : Server.Request.signal
 val selection : ProofEngine.node -> Tactical.selection
+val setSelection : ProofEngine.node -> Tactical.selection -> unit
+
+val runProvers :
+  ?timeout:int ->
+  ?provers:VCS.prover list ->
+  ProofEngine.node -> unit
+
+val killProvers :
+  ?provers:VCS.prover list ->
+  ProofEngine.node -> unit
+
+val clearProvers :
+  ?provers:VCS.prover list ->
+  ProofEngine.node -> unit
 
 (* -------------------------------------------------------------------------- *)
diff --git a/src/plugins/wp/wp_parameters.ml b/src/plugins/wp/wp_parameters.ml
index 8eab81d849685d385cb46cb915e8370ae914f8b5..88cdfd069baf8bd39d04844d7c9a4d934d747f87 100644
--- a/src/plugins/wp/wp_parameters.ml
+++ b/src/plugins/wp/wp_parameters.ml
@@ -45,6 +45,10 @@ let on_reset f = resetdemon := f :: !resetdemon
 let reset () = List.iter (fun f -> f ()) !resetdemon
 let has_dkey (k:category) = is_debug_key_enabled k
 
+let server = ref false
+let () = Server.Main.once (fun () -> server := true)
+let is_interactive () = Fc_config.is_gui || !server
+
 (* ------------------------------------------------------------------------ *)
 (* ---  WP Generation                                                   --- *)
 (* ------------------------------------------------------------------------ *)
@@ -1220,17 +1224,13 @@ let base_output () =
     let output =
       let dir = OutputDir.get () in
       if Fc_Filepath.Normalized.is_empty dir then
-        if Fc_config.is_gui
+        if is_interactive ()
         then make_gui_dir ()
         else make_tmp_dir ()
-      else begin
-        make_output_dir dir ;
-        dir
-      end
-    in
+      else
+        ( make_output_dir dir ; dir ) in
     base_output := Some output;
-    Fc_Filepath.(add_symbolic_dir "WPOUT" output) ;
-    output
+    Fc_Filepath.add_symbolic_dir "WPOUT" output ; output
   | Some output -> output
 
 let get_output () =
diff --git a/src/plugins/wp/wp_parameters.mli b/src/plugins/wp/wp_parameters.mli
index 02ddb77250bba27096a59804668363c1282a48a6..9a00ddf2b3acfb3a39ce18fcd051c43865cebb37 100644
--- a/src/plugins/wp/wp_parameters.mli
+++ b/src/plugins/wp/wp_parameters.mli
@@ -24,6 +24,7 @@ include Plugin.S
 
 val reset : unit -> unit
 val hypothesis : 'a Log.pretty_printer
+val is_interactive : unit -> bool
 
 (** {2 Function Selection} *)
 
diff --git a/src/plugins/wp/wpo.ml b/src/plugins/wp/wpo.ml
index 7dc808882fd5efbe61ff6ccc72cef48d287013f5..a213a36292b037cdd639a3723327b8a64bf6e65a 100644
--- a/src/plugins/wp/wpo.ml
+++ b/src/plugins/wp/wpo.ml
@@ -453,27 +453,28 @@ struct
     mutable dps : result Pmap.t ;
   }
 
-  let not_computing _ r =
-    match r.verdict with VCS.Computing _ -> false | _ -> true
-
   let create () = { dps = Pmap.empty }
 
   let get w p =
     Pmap.find p w.dps
 
-  let clear w = w.dps <- Pmap.empty
+  let clear w =
+    Pmap.iter (fun _ r ->
+        match r.verdict with
+        | VCS.Computing kill -> kill ()
+        | _ -> ()
+      ) w.dps ;
+    w.dps <- Pmap.empty
 
   let replace w p r =
     begin
       if p = Qed then
-        begin
-          w.dps <- Pmap.filter not_computing w.dps ;
-        end ;
+        (w.dps <- Pmap.filter (fun _ r -> VCS.is_verdict r) w.dps) ;
       w.dps <- Pmap.add p r w.dps
     end
 
   let list w =
-    List.filter (fun (_,r) -> VCS.is_verdict r) @@ Pmap.bindings w.dps
+    List.filter (fun (_,r) -> not @@ VCS.is_none r) @@ Pmap.bindings w.dps
 
 end
 
@@ -725,9 +726,12 @@ let get_result g p : VCS.result =
 
 let get_results g =
   let system = SYSTEM.get () in
-  try Results.list (WPOmap.find g system.results)
+  try Results.list @@ WPOmap.find g system.results
   with Not_found -> []
 
+let get_prover_results g =
+  List.filter (fun (p,_) -> VCS.is_prover p) @@ get_results g
+
 let is_trivial g =
   VC_Annot.is_trivial g.po_formula
 
@@ -754,23 +758,28 @@ let compute g =
   let seq = WpContext.on_context ctxt (GOAL.compute_descr ~pid) goal in
   if not qed then modified g ; seq
 
-let is_valid g =
-  is_trivial g || List.exists (fun (_,r) -> VCS.is_valid r) (get_results g)
+let is_fully_valid g =
+  is_trivial g ||
+  List.exists (fun (_,r) -> VCS.is_valid r) @@ get_results g
+
+let is_locally_valid g =
+  is_trivial g ||
+  List.exists (fun (p,r) -> VCS.is_prover p && VCS.is_valid r) @@ get_results g
 
 let all_not_valid g =
   not (is_trivial g) &&
-  List.for_all (fun (_,r) -> VCS.is_not_valid r) (get_results g)
+  List.for_all (fun (_,r) -> VCS.is_not_valid r) @@ get_results g
 
 let is_passed g =
   if is_smoke_test g then
     all_not_valid g
   else
-    is_valid g
+    is_fully_valid g
 
 let has_unknown g =
-  not (is_valid g) &&
+  not (is_fully_valid g) &&
   List.exists
-    (fun (_,r) -> VCS.is_verdict r && not (VCS.is_valid r))
+    (fun (p,r) -> VCS.is_prover p && VCS.is_verdict r && not (VCS.is_valid r))
     (get_results g)
 
 (* -------------------------------------------------------------------------- *)
diff --git a/src/plugins/wp/wpo.mli b/src/plugins/wp/wpo.mli
index 6838763a3c5c734aff5bff71246636ddc1b80c0c..b781e4e72cf9a9c400078ba918f70d81e5a0b9b1 100644
--- a/src/plugins/wp/wpo.mli
+++ b/src/plugins/wp/wpo.mli
@@ -150,9 +150,12 @@ val has_verdict : t -> prover -> bool
 (** Raw prover result (without any respect to smoke tests) *)
 val get_result : t -> prover -> result
 
-(** All raw prover results (without any respect to smoke tests) *)
+(** Return all results (without any respect to smoke tests). *)
 val get_results : t -> (prover * result) list
 
+(** Return all prover results (without any respect to smoke tests). *)
+val get_prover_results : t -> (prover * result) list
+
 (** Consolidated wrt to associated property and smoke test. *)
 val get_proof : t -> [`Passed|`Failed|`Unknown] * Property.t
 
@@ -162,8 +165,11 @@ val get_target : t -> Property.t
 val is_trivial : t -> bool
 (** Currently trivial sequent (no forced simplification) *)
 
-val is_valid : t -> bool
-(** Checks for some prover with valid verdict (no forced simplification) *)
+val is_fully_valid : t -> bool
+(** Checks for some prover or script with valid verdict (no forced qed) *)
+
+val is_locally_valid : t -> bool
+(** Checks for some prover (no tactic) with valid verdict (no forced qed) *)
 
 val all_not_valid : t -> bool
 (** Checks for all provers to give a non-valid, computed verdict *)