diff --git a/ivette/.eslintrc.js b/ivette/.eslintrc.js
index 9f7e0ae0e26599d4b5b578352e3047e59457669c..322375d876f3c51d95ac3358b423fc19b4f04e30 100644
--- a/ivette/.eslintrc.js
+++ b/ivette/.eslintrc.js
@@ -24,6 +24,8 @@ module.exports = {
     "react/display-name": "off",
     // Do not enforce component methods order
     "react/sort-comp": "off",
+    // We do not use propTypes
+    "react/require-default-props": "off",
     // Be more strict on usage of useMemo and useRef
     "react-hooks/exhaustive-deps": "error",
     // Allow type any, even if it should be avoided
diff --git a/ivette/api/generated/plugins/eva/values/index.ts b/ivette/api/generated/plugins/eva/values/index.ts
index 0484105632ff59c5958c2eac001e19b40798331e..bc7353fe239426e399d04330a865ab4b616facad 100644
--- a/ivette/api/generated/plugins/eva/values/index.ts
+++ b/ivette/api/generated/plugins/eva/values/index.ts
@@ -24,131 +24,132 @@ import { jMarkerSafe } from 'frama-c/api/kernel/ast';
 //@ts-ignore
 import { marker } from 'frama-c/api/kernel/ast';
 
-/** CallStack */
-export interface callstack {
-  /** Callstack id */
-  id: number;
-  /** Short name for the callstack */
-  short: string;
-  /** Full name for the callstack */
-  full: string;
-}
+/** Emitted when EVA results has changed */
+export const changed: Server.Signal = {
+  name: 'plugins.eva.values.changed',
+};
+
+export type callstack = Json.index<'#eva-callstack-id'>;
 
 /** Loose decoder for `callstack` */
 export const jCallstack: Json.Loose<callstack> =
-  Json.jObject({
-    id: Json.jFail(Json.jNumber,'Number expected'),
-    short: Json.jFail(Json.jString,'String expected'),
-    full: Json.jFail(Json.jString,'String expected'),
-  });
+  Json.jIndex<'#eva-callstack-id'>('#eva-callstack-id');
 
 /** Safe decoder for `callstack` */
 export const jCallstackSafe: Json.Safe<callstack> =
-  Json.jFail(jCallstack,'Callstack expected');
+  Json.jFail(Json.jIndex<'#eva-callstack-id'>('#eva-callstack-id'),
+    '#eva-callstack-id expected');
 
 /** Natural order for `callstack` */
-export const byCallstack: Compare.Order<callstack> =
-  Compare.byFields
-    <{ id: number, short: string, full: string }>({
-    id: Compare.number,
-    short: Compare.string,
-    full: Compare.string,
-  });
-
-/** Data for array rows [`values`](#values)  */
-export interface valuesData {
-  /** Entry identifier. */
-  key: Json.key<'#values'>;
-  /** CallStack */
-  callstack: callstack;
-  /** Value inferred just before the selected point */
-  value_before: string;
-  /** Did the evaluation led to an alarm? */
-  alarm: boolean;
-  /** Value inferred just after the selected point */
-  value_after?: string;
-}
-
-/** Loose decoder for `valuesData` */
-export const jValuesData: Json.Loose<valuesData> =
-  Json.jObject({
-    key: Json.jFail(Json.jKey<'#values'>('#values'),'#values expected'),
-    callstack: jCallstackSafe,
-    value_before: Json.jFail(Json.jString,'String expected'),
-    alarm: Json.jFail(Json.jBoolean,'Boolean expected'),
-    value_after: Json.jString,
-  });
-
-/** Safe decoder for `valuesData` */
-export const jValuesDataSafe: Json.Safe<valuesData> =
-  Json.jFail(jValuesData,'ValuesData expected');
-
-/** Natural order for `valuesData` */
-export const byValuesData: Compare.Order<valuesData> =
-  Compare.byFields
-    <{ key: Json.key<'#values'>, callstack: callstack, value_before: string,
-       alarm: boolean, value_after?: string }>({
-    key: Compare.string,
-    callstack: byCallstack,
-    value_before: Compare.string,
-    alarm: Compare.boolean,
-    value_after: Compare.defined(Compare.string),
-  });
+export const byCallstack: Compare.Order<callstack> = Compare.number;
 
-/** Signal for array [`values`](#values)  */
-export const signalValues: Server.Signal = {
-  name: 'plugins.eva.values.signalValues',
+const getCallstacks_internal: Server.GetRequest<
+  Json.key<'#stmt'>,
+  callstack[]
+  > = {
+  kind: Server.RqKind.GET,
+  name:   'plugins.eva.values.getCallstacks',
+  input:  Json.jKey<'#stmt'>('#stmt'),
+  output: Json.jList(jCallstack),
 };
+/** Callstacks for markers */
+export const getCallstacks: Server.GetRequest<Json.key<'#stmt'>,callstack[]>= getCallstacks_internal;
 
-const reloadValues_internal: Server.GetRequest<null,null> = {
+const getCallstackInfo_internal: Server.GetRequest<
+  callstack,
+  { callee: Json.key<'#fct'>, caller?: Json.key<'#fct'>,
+    stmt?: Json.key<'#stmt'>, rank?: number }[]
+  > = {
   kind: Server.RqKind.GET,
-  name:   'plugins.eva.values.reloadValues',
-  input:  Json.jNull,
-  output: Json.jNull,
+  name:   'plugins.eva.values.getCallstackInfo',
+  input:  jCallstack,
+  output: Json.jList(
+            Json.jObject({
+              callee: Json.jFail(Json.jKey<'#fct'>('#fct'),'#fct expected'),
+              caller: Json.jKey<'#fct'>('#fct'),
+              stmt: Json.jKey<'#stmt'>('#stmt'),
+              rank: Json.jNumber,
+            })),
 };
-/** Force full reload for array [`values`](#values)  */
-export const reloadValues: Server.GetRequest<null,null>= reloadValues_internal;
-
-const fetchValues_internal: Server.GetRequest<
-  number,
-  { pending: number, updated: valuesData[], removed: Json.key<'#values'>[],
-    reload: boolean }
+/** Callstack Description */
+export const getCallstackInfo: Server.GetRequest<
+  callstack,
+  { callee: Json.key<'#fct'>, caller?: Json.key<'#fct'>,
+    stmt?: Json.key<'#stmt'>, rank?: number }[]
+  >= getCallstackInfo_internal;
+
+const getStmtInfo_internal: Server.GetRequest<
+  Json.key<'#stmt'>,
+  { rank: number, fct: Json.key<'#fct'> }
   > = {
   kind: Server.RqKind.GET,
-  name:   'plugins.eva.values.fetchValues',
-  input:  Json.jNumber,
+  name:   'plugins.eva.values.getStmtInfo',
+  input:  Json.jKey<'#stmt'>('#stmt'),
   output: Json.jObject({
-            pending: Json.jFail(Json.jNumber,'Number expected'),
-            updated: Json.jList(jValuesData),
-            removed: Json.jList(Json.jKey<'#values'>('#values')),
-            reload: Json.jFail(Json.jBoolean,'Boolean expected'),
+            rank: Json.jFail(Json.jNumber,'Number expected'),
+            fct: Json.jFail(Json.jKey<'#fct'>('#fct'),'#fct expected'),
           }),
 };
-/** Data fetcher for array [`values`](#values)  */
-export const fetchValues: Server.GetRequest<
-  number,
-  { pending: number, updated: valuesData[], removed: Json.key<'#values'>[],
-    reload: boolean }
-  >= fetchValues_internal;
-
-const values_internal: State.Array<Json.key<'#values'>,valuesData> = {
-  name: 'plugins.eva.values.values',
-  getkey: ((d:valuesData) => d.key),
-  signal: signalValues,
-  fetch: fetchValues,
-  reload: reloadValues,
-  order: byValuesData,
+/** Stmt Information */
+export const getStmtInfo: Server.GetRequest<
+  Json.key<'#stmt'>,
+  { rank: number, fct: Json.key<'#fct'> }
+  >= getStmtInfo_internal;
+
+const getProbeInfo_internal: Server.GetRequest<
+  marker,
+  { condition: boolean, effects: boolean, rank: number,
+    stmt?: Json.key<'#stmt'>, code?: string }
+  > = {
+  kind: Server.RqKind.GET,
+  name:   'plugins.eva.values.getProbeInfo',
+  input:  jMarker,
+  output: Json.jObject({
+            condition: Json.jFail(Json.jBoolean,'Boolean expected'),
+            effects: Json.jFail(Json.jBoolean,'Boolean expected'),
+            rank: Json.jFail(Json.jNumber,'Number expected'),
+            stmt: Json.jKey<'#stmt'>('#stmt'),
+            code: Json.jString,
+          }),
 };
-/** Abstract values inferred by the Eva analysis */
-export const values: State.Array<Json.key<'#values'>,valuesData> = values_internal;
-
-const getValues_internal: Server.GetRequest<marker,null> = {
+/** Probe informations */
+export const getProbeInfo: Server.GetRequest<
+  marker,
+  { condition: boolean, effects: boolean, rank: number,
+    stmt?: Json.key<'#stmt'>, code?: string }
+  >= getProbeInfo_internal;
+
+const getValues_internal: Server.GetRequest<
+  { callstack?: callstack, target: marker },
+  { v_else?: string, v_then?: string, v_after?: string, values?: string,
+    alarms: [ "True" | "False" | "Unknown", string ][] }
+  > = {
   kind: Server.RqKind.GET,
   name:   'plugins.eva.values.getValues',
-  input:  jMarker,
-  output: Json.jNull,
+  input:  Json.jObject({ callstack: jCallstack, target: jMarkerSafe,}),
+  output: Json.jObject({
+            v_else: Json.jString,
+            v_then: Json.jString,
+            v_after: Json.jString,
+            values: Json.jString,
+            alarms: Json.jList(
+                      Json.jTry(
+                        Json.jPair(
+                          Json.jFail(
+                            Json.jUnion<"True" | "False" | "Unknown">(
+                              Json.jTag("True"),
+                              Json.jTag("False"),
+                              Json.jTag("Unknown"),
+                            ),'Union expected'),
+                          Json.jFail(Json.jString,'String expected'),
+                        ))),
+          }),
 };
-/** Get the abstract values computed for an expression or lvalue */
-export const getValues: Server.GetRequest<marker,null>= getValues_internal;
+/** Abstract values for the given marker */
+export const getValues: Server.GetRequest<
+  { callstack?: callstack, target: marker },
+  { v_else?: string, v_then?: string, v_after?: string, values?: string,
+    alarms: [ "True" | "False" | "Unknown", string ][] }
+  >= getValues_internal;
 
 /* ------------------------------------- */
diff --git a/ivette/package.json b/ivette/package.json
index c3e90e667c268ca1470579e8626799a9fa3d7db8..4accc25c115927804617213092dd1a8ebf2aead9 100644
--- a/ivette/package.json
+++ b/ivette/package.json
@@ -55,6 +55,8 @@
   "dependencies": {
     "@babel/runtime": "^7.9.2",
     "@fortawesome/fontawesome-free": "^5.13.1",
+    "@types/diff": "^4.0.2",
+    "@types/react-window": "^1.8.2",
     "codemirror": "^5.52.2",
     "cytoscape": "^3.15.1",
     "cytoscape-cola": "^2.3.1",
@@ -64,6 +66,7 @@
     "cytoscape-klay": "^3.1.3",
     "cytoscape-panzoom": "^2.5.3",
     "cytoscape-popper": "^1.0.7",
+    "diff": "^5.0.0",
     "immutable": "^4.0.0-rc.12",
     "lodash": "^4.17.15",
     "react": "^16.8",
@@ -72,6 +75,7 @@
     "react-draggable": "^4.2.0",
     "react-fast-compare": "^3.2.0",
     "react-virtualized": "^9.21.2",
+    "react-window": "^1.8.6",
     "source-map-support": "^0.5.16",
     "tippy.js": "5.2.1",
     "zeromq": "^6.0.0-beta.5"
diff --git a/ivette/src/dome/src/renderer/controls/buttons.tsx b/ivette/src/dome/src/renderer/controls/buttons.tsx
index 8404310393afa56f12408a72e708647d6593e8ac..871c37c1221660b797f502e089f64f6fe8b632e4 100644
--- a/ivette/src/dome/src/renderer/controls/buttons.tsx
+++ b/ivette/src/dome/src/renderer/controls/buttons.tsx
@@ -239,7 +239,7 @@ export const CircButton = (props: ButtonProps) => {
 // --------------------------------------------------------------------------
 
 export type IconButtonKind =
-  undefined | 'default' | 'negative' | 'positive' | 'warning';
+  undefined | 'selected' | 'default' | 'negative' | 'positive' | 'warning';
 
 export interface IconButtonProps {
   /** Icon identifier. Displayed on the left side of the label. */
@@ -266,6 +266,7 @@ export interface IconButtonProps {
   display?: boolean;
   /** Styled bytton:
      - `'default'`: normal button;
+     - `'selected'`: selection button, in blue;
      - `'warning'`: warning button, in orange;
      - `'positive'`: positive button, in green;
      - `'negative'`: negative button, in red.
diff --git a/ivette/src/dome/src/renderer/controls/style.css b/ivette/src/dome/src/renderer/controls/style.css
index 17e54575b1f8be877d33f17a444bd07f1a1ebbfb..ffbf1cabcf280cee2b4a4a612cc1b7f35e096128 100644
--- a/ivette/src/dome/src/renderer/controls/style.css
+++ b/ivette/src/dome/src/renderer/controls/style.css
@@ -237,7 +237,7 @@
 }
 
 .dome-xButton-led-positive {
-    background: radial-gradient( circle at center , #00ff0091 , #00ff00 );
+    background: radial-gradient( circle at center , #50c140, #279c16 );
 }
 
 .dome-xButton-led-negative {
@@ -288,7 +288,7 @@
 .dome-xIconButton-selected:not(:hover) { fill: #449bef ; }
 .dome-xIconButton-selected:hover { fill: #c54dae ; }
 
-.dome-xIconButton-positive { fill: #00d000 ; }
+.dome-xIconButton-positive { fill: #279c16 ; }
 
 .dome-xIconButton-negative { fill: #fb3832 ; }
 
diff --git a/ivette/src/dome/src/renderer/dome.tsx b/ivette/src/dome/src/renderer/dome.tsx
index b1e1d879afc1defc21759cf0ba17a932a7f41fca..12606069a35c248072e1994daa4a1350f415bdf9 100644
--- a/ivette/src/dome/src/renderer/dome.tsx
+++ b/ivette/src/dome/src/renderer/dome.tsx
@@ -490,12 +490,10 @@ export function useForceUpdate() {
 export function useUpdate(...events: Event<any>[]) {
   const fn = useForceUpdate();
   React.useEffect(() => {
-    const trigger = () => setImmediate(fn);
-    if (events.length === 0) events.push(update);
-    events.forEach((evt) => evt.on(trigger));
-    return () => events.forEach((evt) => evt.off(trigger));
-  }, [fn, ...events]); // eslint-disable-line react-hooks/exhaustive-deps
-  // The rule signals events is missing, probably because of « … »
+    const theEvents = events ? events.slice() : [update];
+    theEvents.forEach((evt) => evt.on(fn));
+    return () => theEvents.forEach((evt) => evt.off(fn));
+  });
 }
 
 // --------------------------------------------------------------------------
diff --git a/ivette/src/dome/src/renderer/frame/toolbars.tsx b/ivette/src/dome/src/renderer/frame/toolbars.tsx
index 93d5c78ccc323d1ed613fd8350d8466248e5accf..74696bf0d3f0e4d436eb316d036871a7c0781a33 100644
--- a/ivette/src/dome/src/renderer/frame/toolbars.tsx
+++ b/ivette/src/dome/src/renderer/frame/toolbars.tsx
@@ -91,6 +91,10 @@ export interface ButtonProps<A> {
   title?: string;
   /** Button kind. */
   kind?: ButtonKind;
+  /** Button is displayed (default `true`). */
+  visible?: boolean;
+  /** Button is hidden (default `false`). */
+  hidden?: boolean;
   /** Enabled State (default `true`). */
   enabled?: boolean;
   /** Disabled State (default `false`). */
@@ -107,8 +111,10 @@ export interface ButtonProps<A> {
 
 /** Toolbar Button. */
 export function Button<A = undefined>(props: ButtonProps<A>) {
-  const { selected, value, selection, onClick } = props;
+  const { visible = true, hidden = false } = props;
+  if (!visible || hidden) return null;
   const { enabled = true, disabled = false } = props;
+  const { selected, value, selection, onClick } = props;
   const isSelected = selected !== undefined
     ? selected
     : (value !== undefined && value === selection);
@@ -147,13 +153,18 @@ export interface SelectionProps<A> {
 // --- ToolBar Button Group
 // --------------------------------------------------------------------------
 
+export interface ButtonGroupProps<A> extends SelectionProps<A> {
+  className?: string;
+  style?: React.CSSProperties;
+}
+
 /**
    Toolbar Button Group.
 
    Properties of the button group are passed down the buttons of the group
    as appropriate defaults.
  */
-export function ButtonGroup<A>(props: SelectionProps<A>) {
+export function ButtonGroup<A>(props: ButtonGroupProps<A>) {
   const { children, value, onChange, enabled, disabled } = props;
   const baseProps: ButtonProps<A> = {
     enabled,
@@ -161,8 +172,9 @@ export function ButtonGroup<A>(props: SelectionProps<A>) {
     selection: value,
     onClick: onChange,
   };
+  const className = classes('dome-xToolBar-group', props.className);
   return (
-    <div className="dome-xToolBar-group">
+    <div className={className} style={props.style}>
       {React.Children.map(children, (elt) => React.cloneElement(
         elt,
         { ...baseProps, ...elt.props },
diff --git a/ivette/src/frama-c/eva/Values.tsx b/ivette/src/frama-c/eva/Values.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6bdf15345c8728ade58b0881b2e451cca04f9264
--- /dev/null
+++ b/ivette/src/frama-c/eva/Values.tsx
@@ -0,0 +1,480 @@
+// --------------------------------------------------------------------------
+// --- Eva Values
+// --------------------------------------------------------------------------
+
+// React & Dome
+import React from 'react';
+import * as Dome from 'dome';
+import { classes } from 'dome/misc/utils';
+import { VariableSizeList } from 'react-window';
+import { Vfill, Hpack, Vpack, Filler } from 'dome/layout/boxes';
+import { Icon } from 'dome/controls/icons';
+import { Label, Code } from 'dome/controls/labels';
+import { IconButton } from 'dome/controls/buttons';
+import { ButtonGroup, Button } from 'dome/frame/toolbars';
+
+// External Libs
+import { AutoSizer } from 'react-virtualized';
+
+// Frama-C
+import { Component, TitleBar } from 'frama-c/LabViews';
+import * as States from 'frama-c/states';
+
+// Plugins
+import * as Ast from 'frama-c/api/kernel/ast';
+
+// Locals
+import { SizedArea, HSIZER, WSIZER } from './sized';
+import { Diff } from './diffed';
+import { sizeof, valueAt, diffAfter, diffThen, diffElse, EvaAlarm }
+  from './cells';
+import { Row } from './layout';
+import { Probe } from './probes';
+import { Callsite } from './stacks';
+import { Model, getModelInstance } from './model';
+import './style.css';
+
+// --------------------------------------------------------------------------
+// --- Use Model
+// --------------------------------------------------------------------------
+
+function useModel(): Model {
+  const model = getModelInstance();
+  Dome.useUpdate(model.changed, model.laidout);
+  return model;
+}
+
+// --------------------------------------------------------------------------
+// --- Stmt Printer
+// --------------------------------------------------------------------------
+
+interface StmtProps {
+  stmt?: string;
+  rank?: number;
+}
+
+function Stmt(props: StmtProps) {
+  const { rank, stmt } = props;
+  if (rank === undefined || !stmt) return null;
+  const title = `Stmt id ${stmt} at rank ${rank}`;
+  return (
+    <span className="dome-text-code eva-stmt" title={title}>
+      @S{rank}
+    </span>
+  );
+}
+
+// --------------------------------------------------------------------------
+// --- Probe Panel
+// --------------------------------------------------------------------------
+
+function ProbeInfos() {
+  const model = useModel();
+  const probe = model.getFocused();
+  const transient = probe?.transient ?? false;
+  const label = probe?.label;
+  const fct = probe?.fct;
+  const code = probe?.code;
+  const stmt = probe?.stmt;
+  const rank = probe?.rank;
+  const byCS = probe?.byCallstacks;
+  const stacks = model.getStacks(probe).length;
+  const stackable = byCS || stacks > 1;
+  const { cols, rows } = sizeof(code);
+  const width = WSIZER.dimension(cols) + 4;
+  const height = HSIZER.dimension(rows) + 3;
+  const visible = !!code;
+  const zoomed = probe?.zoomed;
+  const zoomable = probe?.zoomable;
+  const visibility = visible ? 'visible' : 'hidden';
+  const effects = probe?.effects;
+  const condition = probe?.condition;
+  return (
+    <Hpack style={{ visibility }} className="eva-probeinfo">
+      <Label className="eva-probeinfo-label">{label && `${label}:`}</Label>
+      <div style={{ minWidth: width, height }} className="eva-probeinfo-code">
+        <SizedArea cols={cols} rows={rows}>{code}</SizedArea>
+      </div>
+      <Code>{fct}<Stmt stmt={stmt} rank={rank} /></Code>
+      <IconButton
+        className="eva-probeinfo-button"
+        display={stackable}
+        selected={byCS}
+        icon="ITEMS.LIST"
+        title={`Details by callstack (${stacks})`}
+        onClick={() => { if (probe) probe.setByCallstacks(!byCS); }}
+      />
+      <IconButton
+        className="eva-probeinfo-button"
+        display={zoomable}
+        selected={zoomed}
+        icon="SEARCH"
+        onClick={() => { if (probe) probe.setZoomed(!zoomed); }}
+      />
+      <IconButton
+        className="eva-probeinfo-button"
+        kind={transient ? 'selected' : 'warning'}
+        icon={transient ? 'CIRC.CHECK' : 'CIRC.CLOSE'}
+        title={transient ? 'Make the probe persistent' : 'Release the probe'}
+        onClick={() => { if (probe) probe.setTransient(!transient); }}
+      />
+      <Filler />
+      <ButtonGroup
+        enabled={!!probe}
+        value={probe?.vstate}
+        onChange={(s) => { if (probe) probe.setState(s); }}
+        className="eva-probeinfo-state"
+      >
+        <Button
+          visible={effects || condition}
+          label="H"
+          value="Here"
+        />
+        <Button
+          visible={condition}
+          label="T"
+          value="Then"
+        />
+        <Button
+          visible={condition}
+          label="E"
+          value="Else"
+        />
+        <Button
+          visible={effects}
+          label="A"
+          value="After"
+        />
+      </ButtonGroup>
+    </Hpack>
+  );
+}
+
+// --------------------------------------------------------------------------
+// --- Alarms Panel
+// --------------------------------------------------------------------------
+
+function AlarmsInfo() {
+  const model = useModel();
+  const probe = model.getFocused();
+  if (probe) {
+    const callstack = model.getCallstack();
+    const domain =
+      model.values.getValues(probe.marker, probe.stmt, callstack);
+    const alarms = domain?.alarms ?? [];
+    if (alarms.length > 0) {
+      // console.log('ALARMS', alarms);
+      const renderAlarm = ([status, alarm]: EvaAlarm) => {
+        const className = `eva-alarm-info eva-alarm-${status}`;
+        return (
+          <Code className={className} icon="WARNING">{alarm}</Code>
+        );
+      };
+      return (
+        <Vpack className="eva-info">
+          {alarms.map(renderAlarm)}
+        </Vpack>
+      );
+    }
+  }
+  return null;
+}
+
+// --------------------------------------------------------------------------
+// --- Stack Panel
+// --------------------------------------------------------------------------
+
+function StackInfo() {
+  const model = useModel();
+  const [, setSelection] = States.useSelection();
+  const callstack = model.getCalls();
+  if (callstack.length <= 1) return null;
+  const makeCallsite = ({ caller, stmt, rank }: Callsite) => {
+    if (!caller || !stmt) return null;
+    const key = `${caller}@${stmt}`;
+    const onClick = () => {
+      const location = { function: caller, marker: stmt };
+      setSelection({ location });
+    };
+    return (
+      <Code
+        key={key}
+        icon="TRIANGLE.LEFT"
+        className="eva-callsite"
+        onClick={onClick}
+      >
+        {caller}
+        <Stmt stmt={stmt} rank={rank} />
+      </Code>
+    );
+  };
+  return (
+    <Hpack className="eva-info">
+      {callstack.map(makeCallsite)}
+    </Hpack>
+  );
+}
+
+// --------------------------------------------------------------------------
+// --- Table Cell
+// --------------------------------------------------------------------------
+
+interface TableCellProps {
+  probe: Probe;
+  row: Row;
+}
+
+const CELLPADDING = 12;
+
+function TableCell(props: TableCellProps) {
+  const model = useModel();
+  const [, setSelection] = States.useSelection();
+  const { probe, row } = props;
+  const { kind, callstack } = row;
+  const minWidth = CELLPADDING + WSIZER.dimension(probe.minCols);
+  const maxWidth = CELLPADDING + WSIZER.dimension(probe.maxCols);
+  const style = { width: minWidth, maxWidth };
+  let contents: React.ReactNode = props.probe.marker;
+  const { transient } = probe;
+
+  switch (kind) {
+
+    // ---- Probe Contents
+    case 'probes':
+      if (transient) {
+        contents = <span className="dome-text-label">« Probe »</span>;
+      } else {
+        const { stmt, rank, code, label } = probe;
+        const textClass = label ? 'dome-text-label' : 'dome-text-code';
+        contents = (
+          <>
+            <span className={textClass}>{label ?? code}</span>
+            <Stmt stmt={stmt} rank={rank} />
+          </>
+        );
+      }
+      break;
+
+    // ---- Values Contents
+    case 'values':
+    case 'callstack':
+      {
+        const domain = model.values.getValues(
+          probe.marker,
+          probe.stmt,
+          callstack,
+        );
+        const { alarms = [] } = domain;
+        const { vstate: s, effects, condition } = probe;
+        const text = valueAt(domain, s) ?? '';
+        const diff = diffAfter(domain, effects, s);
+        const diffA = diffThen(domain, condition, s);
+        const diffB = diffElse(domain, condition, s);
+        const { cols, rows } = sizeof(text);
+        contents = (
+          <>
+            {alarms.length > 0 && (
+              <Icon className="eva-cell-alarms" size={10} id="WARNING" />
+            )}
+            <SizedArea cols={cols} rows={rows}>
+              <span className={`eva-state-${s}`}>
+                <Diff text={text} diff={diff} diffA={diffA} diffB={diffB} />
+              </span>
+            </SizedArea>
+          </>
+        );
+      }
+      break;
+
+  }
+
+  // --- Cell Packing
+  const isFocused = model.getFocused() === probe;
+  const className = classes(
+    'eva-cell',
+    transient && 'eva-transient',
+    isFocused && 'eva-focused',
+  );
+  const onClick = () => {
+    if (probe) {
+      const location = { function: probe.fct, marker: probe.marker };
+      setSelection({ location });
+      model.setSelectedRow(row);
+    }
+  };
+  const onDoubleClick = () => {
+    if (probe) {
+      if (probe.transient) probe.setTransient(false);
+      if (probe.zoomable) probe.setZoomed(!probe.zoomed);
+    }
+  };
+  return (
+    <div
+      className={className}
+      style={style}
+      onClick={onClick}
+      onDoubleClick={onDoubleClick}
+    >
+      {contents}
+    </div>
+  );
+}
+
+// --------------------------------------------------------------------------
+// --- Table Row
+// --------------------------------------------------------------------------
+
+interface TableRowProps {
+  style: React.CSSProperties;
+  index: number;
+}
+
+function TableRow(props: TableRowProps) {
+  const model = useModel();
+  const row = model.getRow(props.index);
+  if (!row) return null;
+  const { kind, probes } = row;
+  const sk = row.stackIndex;
+  const rowKind = `eva-${kind}`;
+  const rowParity = sk !== undefined && (sk % 2 === 0);
+  const rowStyle =
+    model.isSelectedRow(row) ? 'eva-row-selected' :
+      rowParity ? 'eva-row-odd' : 'eva-row-even';
+  const className = classes(
+    rowKind,
+    rowStyle,
+  );
+  const header = row.stacks && (
+    <div className="eva-cell eva-stack">
+      {sk === undefined ? '#' : `${1 + sk}`}
+    </div>
+  );
+  const style: React.CSSProperties = { left: row.stacks ? 0 : 12 };
+  const contents = probes.map((probe) => (
+    <TableCell
+      key={probe.marker}
+      probe={probe}
+      row={row}
+    />
+  ));
+  return (
+    <Hpack className={className} style={props.style}>
+      <div style={style} className="eva-row">
+        {header}
+        {contents}
+      </div>
+      <Filler />
+    </Hpack>
+  );
+}
+
+// --------------------------------------------------------------------------
+// --- Values Panel
+// --------------------------------------------------------------------------
+
+interface Dimension {
+  width: number;
+  height: number;
+}
+
+interface ValuesPanelProps extends Dimension {
+  zoom: number;
+}
+
+function ValuesPanel(props: ValuesPanelProps) {
+  const model = useModel();
+  const { zoom, width, height } = props;
+  // --- reset line cache
+  const listRef = React.useRef<VariableSizeList>(null);
+  Dome.useEvent(model.laidout, () => {
+    setImmediate(() => {
+      const vlist = listRef.current;
+      if (vlist) vlist.resetAfterIndex(0, true);
+    });
+  });
+  // --- compute line height
+  const getRowHeight = React.useCallback(
+    (k: number) => HSIZER.dimension(model.getRowLines(k)),
+    [model],
+  );
+  // --- compute layout
+  const margin = WSIZER.capacity(width);
+  const rowHeight = HSIZER.dimension(1);
+  const [selection] = States.useSelection();
+  React.useEffect(() => {
+    const curr = selection?.current;
+    const fct = curr?.function;
+    const marker = Ast.jMarker(curr?.marker);
+    model.setLayout({ zoom, margin, fct, marker });
+  });
+  // --- render list
+  return (
+    <VariableSizeList
+      ref={listRef}
+      itemCount={model.getRowCount()}
+      itemKey={model.getRowKey}
+      itemSize={getRowHeight}
+      estimatedItemSize={rowHeight}
+      width={width}
+      height={height}
+      itemData={model}
+    >
+      {TableRow}
+    </VariableSizeList>
+  );
+}
+
+// --------------------------------------------------------------------------
+// --- Values Component
+// --------------------------------------------------------------------------
+
+function ValuesComponent() {
+  const [zoom, setZoom] = Dome.useNumberSettings('eva-zoom-factor', 0);
+  return (
+    <>
+      <TitleBar>
+        <IconButton
+          enabled={zoom > 0}
+          icon="ZOOM.OUT"
+          onClick={() => setZoom(zoom - 1)}
+        />
+        <IconButton
+          enabled={zoom < 20}
+          icon="ZOOM.IN"
+          onClick={() => setZoom(zoom + 1)}
+        />
+      </TitleBar>
+      <Vfill>
+        <ProbeInfos />
+        <Vfill>
+          <AutoSizer>
+            {(dim: Dimension) => (
+              <ValuesPanel
+                zoom={zoom}
+                {...dim}
+              />
+            )}
+          </AutoSizer>
+        </Vfill>
+        <AlarmsInfo />
+        <StackInfo />
+      </Vfill>
+    </>
+  );
+}
+
+// --------------------------------------------------------------------------
+// --- Export Component
+// --------------------------------------------------------------------------
+
+export default () => (
+  <Component
+    id="frama-c.values"
+    label="Eva Values"
+    title="Values inferred by the Eva analysis"
+  >
+    <ValuesComponent />
+  </Component>
+);
+
+// --------------------------------------------------------------------------
diff --git a/ivette/src/frama-c/eva/cells.ts b/ivette/src/frama-c/eva/cells.ts
new file mode 100644
index 0000000000000000000000000000000000000000..acb3e9ea05432b1ef0952432b9cb098a0059e90f
--- /dev/null
+++ b/ivette/src/frama-c/eva/cells.ts
@@ -0,0 +1,250 @@
+// --------------------------------------------------------------------------
+// --- Cells
+// --------------------------------------------------------------------------
+
+// Frama-C
+import * as Server from 'frama-c/server';
+import * as Ast from 'frama-c/api/kernel/ast';
+import * as Values from 'frama-c/api/plugins/eva/values';
+
+// --------------------------------------------------------------------------
+// --- Cell Utilities
+// --------------------------------------------------------------------------
+
+export type callback = () => void;
+
+export interface ModelCallbacks {
+  forceUpdate: callback;
+  forceLayout: callback;
+}
+
+export interface Size { cols: number; rows: number }
+
+export const EMPTY: Size = { cols: 0, rows: 0 };
+
+export function sizeof(text?: string): Size {
+  if (!text) return EMPTY;
+  const lines = text.split('\n');
+  return {
+    rows: lines.length,
+    cols: lines.reduce((w, l) => Math.max(w, l.length), 0),
+  };
+}
+
+export function merge(a: Size, b: Size): Size {
+  return {
+    cols: Math.max(a.cols, b.cols),
+    rows: Math.max(a.rows, b.rows),
+  };
+}
+
+export function addH(a: Size, b: Size, padding = 0): Size {
+  return {
+    cols: a.cols + b.cols + padding,
+    rows: Math.max(a.rows, b.rows),
+  };
+}
+
+export function addV(a: Size, b: Size, padding = 0): Size {
+  return {
+    cols: Math.max(a.cols, b.cols),
+    rows: a.rows + b.rows + padding,
+  };
+}
+
+export function addS(s: Size, t: string | undefined): Size {
+  return t ? merge(s, sizeof(t)) : s;
+}
+
+export function lt(a: Size, b: Size): boolean {
+  return a.rows < b.rows && a.cols < b.cols;
+}
+
+export function leq(a: Size, b: Size): boolean {
+  return a.rows <= b.rows && a.cols <= b.cols;
+}
+
+// --------------------------------------------------------------------------
+// --- Data
+// --------------------------------------------------------------------------
+
+export type EvaStatus = 'True' | 'False' | 'Unknown';
+export type EvaAlarm = [EvaStatus, string];
+export type EvaState = 'Here' | 'After' | 'Then' | 'Else';
+
+export interface EvaValues {
+  errors?: string;
+  values?: string;
+  v_after?: string;
+  v_then?: string;
+  v_else?: string;
+  alarms?: EvaAlarm[];
+  size: Size;
+}
+
+export function valueAt(v: EvaValues, st: EvaState): string | undefined {
+  switch (st) {
+    case 'Here': return v.values;
+    case 'After': return v.v_after;
+    case 'Then': return v.v_then;
+    case 'Else': return v.v_else;
+  }
+}
+
+export function diffAfter(
+  v: EvaValues,
+  effects: boolean,
+  st: EvaState,
+): string | undefined {
+  if (!effects) return undefined;
+  switch (st) {
+    case 'Here': return v.v_after;
+    default: return v.values;
+  }
+}
+
+export function diffThen(
+  v: EvaValues,
+  condition: boolean,
+  st: EvaState,
+): string | undefined {
+  if (!condition) return undefined;
+  switch (st) {
+    case 'Here': return v.v_then;
+    default: return v.values;
+  }
+}
+
+export function diffElse(
+  v: EvaValues,
+  condition: boolean,
+  st: EvaState,
+): string | undefined {
+  if (!condition) return undefined;
+  switch (st) {
+    case 'Here': return v.v_else;
+    default: return v.values;
+  }
+}
+
+// --------------------------------------------------------------------------
+// --- Value Cache
+// --------------------------------------------------------------------------
+
+export class ValueCache {
+
+  private readonly state: ModelCallbacks;
+  private readonly probes = new Map<Ast.marker, Size>(); // Marker -> max in column
+  private readonly stacks = new Map<string, Size>(); // Callstack -> max in row
+  private readonly vcache = new Map<string, EvaValues>(); // '<Marker><@Callstack>?' -> value
+  private smax = EMPTY; // max cell size
+
+  constructor(state: ModelCallbacks) {
+    this.state = state;
+  }
+
+  clear() {
+    this.smax = EMPTY;
+    this.probes.clear();
+    this.stacks.clear();
+    this.vcache.clear();
+    this.state.forceLayout();
+  }
+
+  // --- Cached Measures
+
+  getMaxSize() { return this.smax; }
+
+  getProbeSize(target: Ast.marker) {
+    return this.probes.get(target) ?? EMPTY;
+  }
+
+  private static stackKey(stmt: string, callstack: Values.callstack) {
+    return `${stmt}::${callstack}`;
+  }
+
+  getStackSize(stmt: string, callstack: Values.callstack) {
+    const key = ValueCache.stackKey(stmt, callstack);
+    return this.stacks.get(key) ?? EMPTY;
+  }
+
+  // --- Cached Values & Request Update
+
+  getValues(
+    target: Ast.marker,
+    stmt: string | undefined,
+    callstack: Values.callstack | undefined,
+  ): EvaValues {
+    const key = callstack !== undefined ? `${target}@${callstack}` : target;
+    const cache = this.vcache;
+    const cached = cache.get(key);
+    if (cached) return cached;
+    const newValue: EvaValues = { values: '', size: EMPTY };
+    if (callstack !== undefined && stmt === undefined)
+      return newValue;
+    // callstack !== undefined ==> stmt !== undefined)
+    cache.set(key, newValue);
+    Server
+      .send(Values.getValues, { target, callstack })
+      .then((r) => {
+        newValue.errors = undefined;
+        newValue.values = r.values;
+        newValue.v_after = r.v_after;
+        newValue.v_then = r.v_then;
+        newValue.v_else = r.v_else;
+        newValue.alarms = r.alarms;
+        if (this.updateLayout(target, stmt, callstack, newValue))
+          this.state.forceLayout();
+        else
+          this.state.forceUpdate();
+      })
+      .catch((err) => {
+        newValue.errors = `$Error: ${err}`;
+        this.state.forceUpdate();
+      });
+    return newValue;
+  }
+
+  // --- Updating Measures
+
+  private updateLayout(
+    target: Ast.marker,
+    stmt: string | undefined,
+    callstack: Values.callstack | undefined,
+    v: EvaValues,
+  ): boolean {
+    // measuring cell
+    let s = sizeof(v.values);
+    s = addS(s, v.v_after);
+    s = addS(s, v.v_then);
+    s = addS(s, v.v_else);
+    v.size = s;
+    // max cell size
+    const { smax } = this;
+    let small = leq(s, smax);
+    if (!small) this.smax = merge(s, smax);
+    // max size for probe column
+    const ps = this.getProbeSize(target);
+    if (!leq(s, ps)) {
+      this.probes.set(target, merge(ps, s));
+      small = false;
+    }
+    // max size for stack row
+    if (callstack !== undefined) {
+      if (stmt === undefined) small = false;
+      else {
+        const key = ValueCache.stackKey(stmt, callstack);
+        const cs = this.stacks.get(key) ?? EMPTY;
+        if (!leq(s, cs)) {
+          this.stacks.set(key, merge(cs, s));
+          small = false;
+        }
+      }
+    }
+    // request new layout if not small enough
+    return !small;
+  }
+
+}
+
+// --------------------------------------------------------------------------
diff --git a/ivette/src/frama-c/eva/diffed.tsx b/ivette/src/frama-c/eva/diffed.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9edf57ee95f6edd03c49de4f3935ad4dbb70156a
--- /dev/null
+++ b/ivette/src/frama-c/eva/diffed.tsx
@@ -0,0 +1,160 @@
+// --------------------------------------------------------------------------
+// --- Diff Text Rendering
+// --------------------------------------------------------------------------
+
+import React from 'react';
+import { Change, diffChars } from 'diff';
+
+const MODIFIED = 'eva-diff eva-diff-modified';
+const REMOVED = 'eva-diff eva-diff-removed';
+const ADDED = 'eva-diff eva-diff-added';
+const SHADOW = 'eva-diff eva-diff-shadow';
+
+export class DiffBuffer {
+
+  private added = false;
+  private removed = false;
+  private value = '';
+  private scratch = '';
+  private contents: React.ReactNode[] = [];
+
+  constructor() {
+    this.push = this.push.bind(this);
+  }
+
+  clear() {
+    this.added = false;
+    this.removed = false;
+    this.value = '';
+    this.scratch = '';
+    this.contents = [];
+  }
+
+  push(c: Change) {
+    if (!c.added && !c.removed) {
+      this.flush();
+      this.value += c.value;
+      this.flush();
+    } else {
+      if (c.added) this.added = true;
+      if (c.removed) {
+        this.removed = true;
+        this.value += c.value;
+      }
+    }
+  }
+
+  private flush() {
+    const { value, added, removed } = this;
+    if (added && removed) {
+      if (value) {
+        this.scratch += '\0'.repeat(value.length);
+        this.contents.push(
+          <span className={MODIFIED} title="Modified"> {value}</span>,
+        );
+      }
+    } else if (removed) {
+      if (value) {
+        this.contents.push(
+          <span className={REMOVED} title="Removed">{value}</span>,
+        );
+      }
+    } else if (added) {
+      this.contents.push(
+        <span className={ADDED}><span className={SHADOW} /></span>,
+      );
+    } else if (value) {
+      this.scratch += value;
+      this.contents.push(value);
+    }
+    this.value = '';
+    this.added = false;
+    this.removed = false;
+  }
+
+  getContents(): React.ReactNode {
+    this.flush();
+    return <>{React.Children.toArray(this.contents)}</>;
+  }
+
+  getScratch() {
+    this.flush();
+    return this.scratch;
+  }
+
+}
+
+/* --------------------------------------------------------------------------*/
+/* --- Diff2 Component                                                    ---*/
+/* --------------------------------------------------------------------------*/
+
+export interface Diff2Props {
+  text: string;
+  diff: string;
+}
+
+export function Diff2(props: Diff2Props): JSX.Element {
+  const { text, diff } = props;
+  const contents = React.useMemo(() => {
+    if (text === diff) return text;
+    const buffer = new DiffBuffer();
+    const chunks = diffChars(text, diff);
+    // console.log('DIFF', text, diff, chunks);
+    chunks.forEach(buffer.push);
+    return buffer.getContents();
+  }, [text, diff]);
+  return contents as JSX.Element;
+}
+
+/* --------------------------------------------------------------------------*/
+/* --- Diff3 Component                                                    ---*/
+/* --------------------------------------------------------------------------*/
+
+export interface Diff3Props {
+  text: string;
+  diffA: string;
+  diffB: string;
+}
+
+export function Diff3(props: Diff3Props): JSX.Element {
+  const { text, diffA, diffB } = props;
+  const contents = React.useMemo(() => {
+    if (text === diffA && text === diffB) return text;
+    const buffer = new DiffBuffer();
+    diffChars(diffA, diffB).forEach(buffer.push);
+    const scratch = buffer.getScratch();
+    buffer.clear();
+    diffChars(text, scratch).forEach(buffer.push);
+    return buffer.getContents();
+  }, [text, diffA, diffB]);
+  return contents as JSX.Element;
+}
+
+/* --------------------------------------------------------------------------*/
+/* --- Diff Component                                                     ---*/
+/* --------------------------------------------------------------------------*/
+
+export interface DiffProps {
+  text: string;
+  diff?: string;
+  diffA?: string;
+  diffB?: string;
+}
+
+export function Diff(props: DiffProps): JSX.Element {
+  const { text, diff, diffA, diffB } = props;
+  if (text === diff) return <>{text}</>;
+  if (diff !== undefined)
+    return <Diff2 text={text} diff={diff} />;
+  if (diffA === undefined) {
+    if (diffB === undefined) return <>{text}</>;
+    return <Diff2 text={text} diff={diffB} />;
+  } if (diffB === undefined) {
+    if (diffA === undefined) return <>{text}</>;
+    return <Diff2 text={text} diff={diffA} />;
+  }
+  return <Diff3 text={text} diffA={diffA} diffB={diffB} />;
+
+}
+
+// --------------------------------------------------------------------------
diff --git a/ivette/src/frama-c/eva/layout.ts b/ivette/src/frama-c/eva/layout.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c416fa7c8945753a5ac0ff2490a9618ac73bc430
--- /dev/null
+++ b/ivette/src/frama-c/eva/layout.ts
@@ -0,0 +1,146 @@
+/* --------------------------------------------------------------------------*/
+/* --- Layout                                                             ---*/
+/* --------------------------------------------------------------------------*/
+
+import { callstack } from 'frama-c/api/plugins/eva/values';
+import { Probe } from './probes';
+import { StacksCache } from './stacks';
+import { Size, EMPTY, leq, addH, ValueCache } from './cells';
+
+export interface LayoutProps {
+  zoom?: number;
+  margin: number;
+}
+
+export type RowKind = 'probes' | 'values' | 'callstack';
+
+export interface Row {
+  key: string;
+  kind: RowKind;
+  probes: Probe[];
+  headstack?: string;
+  stacks?: number;
+  stackIndex?: number;
+  callstack?: callstack;
+  hlines: number;
+}
+
+/* --------------------------------------------------------------------------*/
+/* --- Layout Enfine                                                      ---*/
+/* --------------------------------------------------------------------------*/
+
+const PADDING = 2;
+const INSET = 1;
+const HCROP = 18;
+const VCROP = 1;
+
+export class LayoutEngine {
+
+  // --- Setup
+
+  private readonly values: ValueCache;
+  private readonly stacks: StacksCache;
+  private readonly hcrop: number;
+  private readonly vcrop: number;
+  private readonly margin: number;
+
+  constructor(
+    props: undefined | LayoutProps,
+    values: ValueCache,
+    stacks: StacksCache,
+  ) {
+    this.values = values;
+    this.stacks = stacks;
+    const zoom = Math.max(0, props?.zoom ?? 0);
+    this.vcrop = VCROP + 2 * zoom;
+    this.hcrop = HCROP + zoom;
+    this.margin = props?.margin ?? 80;
+    this.push = this.push.bind(this);
+  }
+
+  // --- Probe Buffer
+  private byStacks?: string; // stmt
+  private rowSize: Size = EMPTY;
+  private buffer: Probe[] = [];
+  private rows: Row[] = [];
+
+  crop(zoomed: boolean, s: Size): Size {
+    const sCols = s.cols + INSET;
+    const cols = zoomed ? sCols : Math.min(sCols, this.hcrop);
+    const rows = zoomed ? s.rows : Math.min(s.rows, this.vcrop);
+    return {
+      cols: Math.max(HCROP, cols),
+      rows: Math.max(VCROP, rows),
+    };
+  }
+
+  push(p: Probe) {
+    const probeSize = this.values.getProbeSize(p.marker);
+    const s = this.crop(p.zoomed, probeSize);
+    p.zoomable = p.zoomed || !leq(probeSize, s);
+    p.minCols = s.cols;
+    p.maxCols = Math.max(p.minCols, probeSize.cols);
+    const stmt = p.byCallstacks ? p.stmt : undefined;
+    if (stmt !== this.byStacks) {
+      this.flush();
+      this.byStacks = stmt;
+    }
+    if (!stmt && s.cols + this.rowSize.cols > this.margin)
+      this.flush();
+    this.rowSize = addH(this.rowSize, s);
+    this.rowSize.cols += PADDING;
+    this.buffer.push(p);
+  }
+
+  // --- Flush Rows
+
+  flush(): Row[] {
+    const ps = this.buffer;
+    const rs = this.rows;
+    if (ps.length > 0) {
+      const stmt = this.byStacks;
+      if (stmt) {
+        // --- by callstacks
+        const wcs = this.stacks.getStacks(stmt);
+        rs.push({
+          key: `P${stmt}`,
+          kind: 'probes',
+          probes: ps,
+          stacks: wcs.length,
+          hlines: 1,
+        });
+        wcs.forEach((cs, k) => {
+          rs.push({
+            key: `C${stmt}::${cs}`,
+            kind: 'callstack',
+            probes: ps,
+            stackIndex: k,
+            stacks: wcs.length,
+            callstack: cs,
+            hlines: this.values.getStackSize(stmt, cs).rows,
+          });
+        });
+      } else {
+        // --- by callstacks
+        const n = rs.length;
+        rs.push({
+          key: `P${n}`,
+          kind: 'probes',
+          probes: ps,
+          hlines: 1,
+        }, {
+          key: `V${n}`,
+          kind: 'values',
+          probes: ps,
+          hlines: this.rowSize.rows,
+        });
+      }
+    }
+    this.buffer = [];
+    this.rowSize = EMPTY;
+    return rs;
+  }
+
+}
+
+/* --------------------------------------------------------------------------*/
diff --git a/ivette/src/frama-c/eva/model.ts b/ivette/src/frama-c/eva/model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6f24c809c84d2360cc6a461d60e7c09ef7dcd0d0
--- /dev/null
+++ b/ivette/src/frama-c/eva/model.ts
@@ -0,0 +1,212 @@
+// --------------------------------------------------------------------------
+// --- Eva Values
+// --------------------------------------------------------------------------
+
+// External Libs
+import { throttle } from 'lodash';
+import equal from 'react-fast-compare';
+import * as Dome from 'dome';
+
+import * as Server from 'frama-c/server';
+import * as Values from 'frama-c/api/plugins/eva/values';
+import * as Ast from 'frama-c/api/kernel/ast';
+
+// Model
+import { Probe } from './probes';
+import { StacksCache, Callsite } from './stacks';
+import { ModelCallbacks, ValueCache } from './cells';
+import { LayoutProps, LayoutEngine, Row } from './layout';
+
+export interface ModelLayout extends LayoutProps {
+  fct?: string;
+  marker?: Ast.marker;
+}
+
+/* --------------------------------------------------------------------------*/
+/* --- EVA Values Model                                                   ---*/
+/* --------------------------------------------------------------------------*/
+
+export class Model implements ModelCallbacks {
+
+  constructor() {
+    this.forceUpdate = this.forceUpdate.bind(this);
+    this.forceLayout = this.forceLayout.bind(this);
+    this.forceReload = this.forceReload.bind(this);
+    this.computeLayout = this.computeLayout.bind(this);
+    this.setLayout = throttle(this.setLayout.bind(this), 300);
+    this.getRowKey = this.getRowKey.bind(this);
+    this.getRowCount = this.getRowCount.bind(this);
+    this.getRowLines = this.getRowLines.bind(this);
+    Server.onSignal(Values.changed, this.forceReload);
+  }
+
+  // --- Probes
+
+  private selected?: Probe;
+  private focused?: Probe;
+  private callstack?: Values.callstack;
+  private remanent?: Probe; // last transient
+  private probes = new Map<string, Probe>();
+
+  getFocused() { return this.focused; }
+  isFocused(p: Probe | undefined) { return this.focused === p; }
+  isRemanent(p: Probe | undefined) { return this.remanent === p; }
+
+  getProbe(fct: string, m: Ast.marker): Probe {
+    let p = this.probes.get(m);
+    if (!p) {
+      p = new Probe(this, fct, m);
+      this.probes.set(m, p);
+      p.requestProbeInfo();
+    }
+    return p;
+  }
+
+  getStacks(p: Probe | undefined): Values.callstack[] {
+    const stmt = p?.stmt;
+    return stmt ? this.stacks.getStacks(stmt) : [];
+  }
+
+  // --- Caches
+
+  readonly stacks = new StacksCache(this);
+  readonly values = new ValueCache(this);
+
+  // --- Rows
+
+  private lock = false;
+  private layout: ModelLayout = { margin: 80 };
+  private rows: Row[] = [];
+
+  getRow(index: number): Row | undefined {
+    return this.rows[index];
+  }
+
+  getRowCount() {
+    return this.rows.length;
+  }
+
+  getRowKey(index: number): string {
+    const row = this.rows[index];
+    return row ? row.key : `#${index}`;
+  }
+
+  getRowLines(index: number): number {
+    const row = this.rows[index];
+    return row ? row.hlines : 0;
+  }
+
+  setSelectedRow(row: Row) {
+    const cs = row.callstack;
+    if (cs !== this.callstack) {
+      this.callstack = cs;
+      this.forceUpdate();
+    }
+  }
+
+  isSelectedRow(row: Row): boolean {
+    const cs = this.callstack;
+    return cs !== undefined ? cs === row.callstack : false;
+  }
+
+  getCallstack(): Values.callstack | undefined {
+    return this.callstack;
+  }
+
+  getCalls(): Callsite[] {
+    const c = this.callstack;
+    return c === undefined ? [] : this.stacks.getCalls(c);
+  }
+
+  // --- Throttled
+  setLayout(ly: ModelLayout) {
+    if (!equal(this.layout, ly)) {
+      this.layout = ly;
+      const { fct, marker } = ly;
+      this.selected =
+        fct && marker ? this.getProbe(fct, marker) : undefined;
+      this.forceLayout();
+    }
+  }
+
+  // --- Recompute Layout
+
+  private computeLayout() {
+    if (this.lock) return;
+    this.lock = true;
+    const s = this.selected;
+    if (!s) {
+      this.focused = undefined;
+      this.callstack = undefined;
+      this.remanent = undefined;
+    } else if (!s.loading) {
+      this.focused = s;
+      const stacks = this.getStacks(s);
+      if (s.byCallstacks) {
+        const cs0 = this.callstack;
+        if (cs0) this.callstack = stacks.find((cs) => cs === cs0);
+      } else {
+        this.callstack = stacks.length === 1 ? stacks[0] : undefined;
+      }
+      if (s.code && s.transient) {
+        this.remanent = s;
+      } else {
+        this.remanent = undefined;
+      }
+    }
+    const toLayout: Probe[] = [];
+    this.probes.forEach((p) => {
+      if (p.code && (!p.transient || p === this.remanent)) {
+        toLayout.push(p);
+      }
+    });
+    const engine = new LayoutEngine(
+      this.layout,
+      this.values,
+      this.stacks,
+    );
+    toLayout.sort(Probe.order).forEach(engine.push);
+    this.rows = engine.flush();
+    this.laidout.emit();
+    this.lock = false;
+  }
+
+  // --- Force Reload (empty caches)
+  forceReload() {
+    this.focused = undefined;
+    this.remanent = undefined;
+    this.selected = undefined;
+    this.callstack = undefined;
+    this.probes.clear();
+    this.stacks.clear();
+    this.values.clear();
+    this.forceLayout();
+    this.forceUpdate();
+  }
+
+  // --- Events
+  readonly changed = new Dome.Event('eva-changed');
+  readonly laidout = new Dome.Event('eva-laidout');
+
+  // --- Force Layout
+  forceLayout() {
+    setImmediate(this.computeLayout);
+  }
+
+  // --- Foce Update
+  forceUpdate() { this.changed.emit(); }
+
+}
+
+// --------------------------------------------------------------------------
+// --- EVA Model
+// --------------------------------------------------------------------------
+
+let MODEL: Model | undefined;
+
+export function getModelInstance(): Model {
+  if (!MODEL) MODEL = new Model();
+  return MODEL;
+}
+
+// --------------------------------------------------------------------------
diff --git a/ivette/src/frama-c/eva/probes.ts b/ivette/src/frama-c/eva/probes.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c920d71d0068667623c4e98973e82a291bad2313
--- /dev/null
+++ b/ivette/src/frama-c/eva/probes.ts
@@ -0,0 +1,151 @@
+/* --------------------------------------------------------------------------*/
+/* --- Probes                                                             ---*/
+/* --------------------------------------------------------------------------*/
+
+// Frama-C
+import * as Server from 'frama-c/server';
+import * as Values from 'frama-c/api/plugins/eva/values';
+import * as Ast from 'frama-c/api/kernel/ast';
+
+// Model
+import { ModelCallbacks, EvaState } from './cells';
+
+/* --------------------------------------------------------------------------*/
+/* --- Probe Labelling                                                    ---*/
+/* --------------------------------------------------------------------------*/
+
+const Ka = 'A'.charCodeAt(0);
+const Kz = 'Z'.charCodeAt(0);
+const LabelRing: string[] = [];
+const LabelSize = 12;
+let La = Ka;
+let Lk = 0;
+
+function newLabel() {
+  let lbl = LabelRing.shift();
+  if (lbl) return lbl;
+  const a = La;
+  const k = Lk;
+  lbl = String.fromCharCode(a);
+  if (a < Kz) {
+    La++;
+  } else {
+    La = Ka;
+    Lk++;
+  }
+  return k > 0 ? lbl + k : lbl;
+}
+
+/* --------------------------------------------------------------------------*/
+/* --- Probe State                                                        ---*/
+/* --------------------------------------------------------------------------*/
+
+export class Probe {
+
+  // properties
+  readonly fct: string;
+  readonly marker: Ast.marker;
+  readonly model: ModelCallbacks;
+  transient = true;
+  loading = true;
+  label?: string;
+  code?: string;
+  stmt?: string;
+  rank?: number;
+  minCols: number = LabelSize;
+  maxCols: number = LabelSize;
+  byCallstacks = false;
+  zoomed = false;
+  zoomable = false;
+  vstate: EvaState = 'Here';
+  effects = false;
+  condition = false;
+
+  constructor(state: ModelCallbacks, fct: string, marker: Ast.marker) {
+    this.fct = fct;
+    this.marker = marker;
+    this.model = state;
+    this.requestProbeInfo = this.requestProbeInfo.bind(this);
+    this.setTransient = this.setTransient.bind(this);
+  }
+
+  requestProbeInfo() {
+    this.loading = true;
+    Server
+      .send(Values.getProbeInfo, this.marker)
+      .then(({ code, stmt, rank, effects, condition }) => {
+        this.code = code;
+        this.stmt = stmt;
+        this.rank = rank;
+        this.effects = effects;
+        this.condition = condition;
+        this.vstate = effects ? 'After' : 'Here';
+        this.loading = false;
+      })
+      .catch(() => {
+        this.code = '(error)';
+        this.stmt = undefined;
+        this.rank = undefined;
+        this.loading = false;
+      })
+      .finally(this.model.forceLayout);
+  }
+
+  // --------------------------------------------------------------------------
+  // --- Internal State
+  // --------------------------------------------------------------------------
+
+  setTransient(tr: boolean) {
+    if (this.transient !== tr) {
+      this.transient = tr;
+      if (tr && this.label) {
+        LabelRing.push(this.label);
+        this.label = undefined;
+      }
+      if (!tr && !this.label && this.code && this.code.length > LabelSize) {
+        this.label = newLabel();
+      }
+      this.model.forceLayout();
+    }
+  }
+
+  setByCallstacks(byCS: boolean) {
+    if (byCS !== this.byCallstacks) {
+      this.byCallstacks = byCS;
+      this.model.forceLayout();
+    }
+  }
+
+  setZoomed(zoomed: boolean) {
+    if (zoomed !== this.zoomed) {
+      this.zoomed = zoomed;
+      this.model.forceLayout();
+    }
+  }
+
+  setState(s: EvaState | undefined) {
+    this.vstate = s ?? 'Here';
+    this.model.forceUpdate();
+  }
+
+  // --------------------------------------------------------------------------
+  // --- Ordering
+  // --------------------------------------------------------------------------
+
+  static order(p: Probe, q: Probe): number {
+    const rp = p.rank ?? 0;
+    const rq = q.rank ?? 0;
+    if (rp < rq) return (-1);
+    if (rp > rq) return (+1);
+    const cp = p.byCallstacks;
+    const cq = q.byCallstacks;
+    if (!cp && cq) return (-1);
+    if (cp && !cq) return (+1);
+    if (p.marker < q.marker) return (-1);
+    if (p.marker > q.marker) return (+1);
+    return 0;
+  }
+
+}
+
+/* --------------------------------------------------------------------------*/
diff --git a/ivette/src/frama-c/eva/sized.tsx b/ivette/src/frama-c/eva/sized.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e092f088c71ab39547c1d626951aa93648049807
--- /dev/null
+++ b/ivette/src/frama-c/eva/sized.tsx
@@ -0,0 +1,112 @@
+// --------------------------------------------------------------------------
+// --- Sized Cell
+// --------------------------------------------------------------------------
+
+import React from 'react';
+
+// --------------------------------------------------------------------------
+// --- Measurer
+// --------------------------------------------------------------------------
+
+export class Streamer {
+  private readonly v0: number;
+  private readonly vs: number[] = [];
+  private v?: number;
+  constructor(v0: number) {
+    this.v0 = v0;
+  }
+
+  push(v: number) {
+    const { vs } = this;
+    vs.push(Math.round(v));
+    if (vs.length > 200) vs.shift();
+  }
+
+  mean(): number {
+    if (this.v === undefined) {
+      const { vs } = this;
+      const n = vs.length;
+      if (n > 0) {
+        const m = vs.reduce((s, v) => s + v, 0) / n;
+        this.v = Math.round(m + 0.5);
+      } else {
+        this.v = this.v0;
+      }
+    }
+    return this.v;
+  }
+}
+
+export class FontSizer {
+  a = 0;
+  b = 0;
+  k: Streamer;
+  p: Streamer;
+
+  constructor(k: number, p: number) {
+    this.k = new Streamer(k);
+    this.p = new Streamer(p);
+  }
+
+  push(x: number, y: number) {
+    const a0 = this.a;
+    const b0 = this.b;
+    if (x !== a0 && a0 !== 0) {
+      const k = (y - b0) / (x - a0);
+      const p = y - k * x;
+      this.k.push(k);
+      this.p.push(p);
+    }
+    this.a = x;
+    this.b = y;
+  }
+
+  capacity(y: number) {
+    const k = this.k.mean();
+    const p = this.p.mean();
+    return Math.round(0.5 + (y - p) / k);
+  }
+
+  dimension(n: number) {
+    const k = this.k.mean();
+    const p = this.p.mean();
+    return p + n * k;
+  }
+
+}
+
+/* --------------------------------------------------------------------------*/
+/* ---  Sizing Component                                                  ---*/
+/* --------------------------------------------------------------------------*/
+
+export const WSIZER = new FontSizer(7, 6);
+export const HSIZER = new FontSizer(14, 6);
+
+export interface SizedAreaProps {
+  cols: number;
+  rows: number;
+  children?: React.ReactNode;
+}
+
+export function SizedArea(props: SizedAreaProps) {
+  const { rows, cols, children } = props;
+  const refSizer = React.useCallback(
+    (ref: null | HTMLDivElement) => {
+      if (ref) {
+        const r = ref.getBoundingClientRect();
+        WSIZER.push(cols, r.width);
+        HSIZER.push(rows, r.height);
+      }
+    }, [rows, cols],
+  );
+  return (
+    <div
+      ref={refSizer}
+      className="eva-sized-area dome-text-code"
+    >
+      {children}
+    </div>
+  );
+}
+
+/* --------------------------------------------------------------------------*/
diff --git a/ivette/src/frama-c/eva/stacks.ts b/ivette/src/frama-c/eva/stacks.ts
new file mode 100644
index 0000000000000000000000000000000000000000..013638a1630db36810df46b65f3b8cc011226801
--- /dev/null
+++ b/ivette/src/frama-c/eva/stacks.ts
@@ -0,0 +1,88 @@
+// --------------------------------------------------------------------------
+// --- CallStacks
+// --------------------------------------------------------------------------
+
+import * as Server from 'frama-c/server';
+import * as Values from 'frama-c/api/plugins/eva/values';
+
+import { ModelCallbacks } from './cells';
+
+// --------------------------------------------------------------------------
+// --- Callstack infos
+// --------------------------------------------------------------------------
+
+export type callstacks = Values.callstack[];
+export interface Callsite {
+  callee: string;
+  caller?: string;
+  stmt?: string;
+  rank?: number;
+}
+
+// --------------------------------------------------------------------------
+// --- CallStacks Cache
+// --------------------------------------------------------------------------
+
+export class StacksCache {
+
+  private readonly model: ModelCallbacks;
+  private readonly stacks = new Map<string, callstacks>();
+  private readonly calls = new Map<Values.callstack, Callsite[]>();
+
+  // --------------------------------------------------------------------------
+  // --- LifeCycle
+  // --------------------------------------------------------------------------
+
+  constructor(state: ModelCallbacks) {
+    this.model = state;
+  }
+
+  clear() {
+    this.stacks.clear();
+  }
+
+  // --------------------------------------------------------------------------
+  // --- Getters
+  // --------------------------------------------------------------------------
+
+  getStacks(stmt: string): callstacks {
+    const cs = this.stacks.get(stmt);
+    if (cs !== undefined) return cs;
+    this.stacks.set(stmt, []);
+    this.requestCallstacks(stmt);
+    return [];
+  }
+
+  getCalls(cs: Values.callstack): Callsite[] {
+    const fs = this.calls.get(cs);
+    if (fs !== undefined) return fs;
+    this.calls.set(cs, []);
+    this.requestCalls(cs);
+    return [];
+  }
+
+  // --------------------------------------------------------------------------
+  // --- Fetchers
+  // --------------------------------------------------------------------------
+
+  private requestCallstacks(stmt: string) {
+    Server
+      .send(Values.getCallstacks, stmt)
+      .then((cs: callstacks) => {
+        this.stacks.set(stmt, cs);
+        this.model.forceLayout();
+      });
+  }
+
+  private requestCalls(cs: Values.callstack) {
+    Server
+      .send(Values.getCallstackInfo, cs)
+      .then((calls) => {
+        this.calls.set(cs, calls);
+        this.model.forceUpdate();
+      });
+  }
+
+}
+
+// --------------------------------------------------------------------------
diff --git a/ivette/src/frama-c/eva/style.css b/ivette/src/frama-c/eva/style.css
new file mode 100644
index 0000000000000000000000000000000000000000..12ff6e4916e8c18aff58de792b27a599606f26d2
--- /dev/null
+++ b/ivette/src/frama-c/eva/style.css
@@ -0,0 +1,225 @@
+/* -------------------------------------------------------------------------- */
+/* --- Styling Values                                                     --- */
+/* -------------------------------------------------------------------------- */
+
+.eva-sized-area {
+    padding: 3px;
+    white-space: pre;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.eva-stmt {
+    cursor: default;
+    color: grey;
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- Probe Panel                                                        --- */
+/* -------------------------------------------------------------------------- */
+
+.eva-probeinfo {
+    min-height: 32px;
+    padding-left: 6px;
+    padding-top: 2px;
+    padding-bottom: 4px;
+    width: 100%;
+    background: #ccc;
+    display: flex;
+}
+
+.eva-probeinfo-label {
+    flex: 0 1 auto;
+    min-width: 22px;
+    text-align: left;
+}
+
+.eva-probeinfo-code {
+    flex: 0 1 auto;
+    background: lightgrey;
+    min-width: 120px;
+    width: auto;
+    border: thin solid black;
+    padding-left: 6px;
+    padding-right: 6px;
+    border-radius: 4px;
+    overflow: hidden;
+}
+
+.eva-probeinfo-stmt {
+    flex: 0 0 auto;
+    margin-left: 2px;
+    margin-right: 0px;
+    margin-top: 2px;
+}
+
+.eva-probeinfo-button {
+    flex: 0 0 auto;
+    margin: 1px;
+    min-width: 16px;
+}
+
+.eva-probeinfo-state {
+    margin-top: 2px;
+    margin-bottom: 2px;
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- Call Satck Info                                                    --- */
+/* -------------------------------------------------------------------------- */
+
+.eva-info {
+    width: 100%;
+    background: #ccc;
+    padding-top: 3px;
+    padding-left: 12px;
+    padding-bottom: 2px;
+}
+
+.eva-callsite {
+    fill: #7cacbb;
+    background: #eee;
+    border-radius: 4px;
+    border: thin solid black;
+    padding-right: 7px;
+}
+
+.eva-callsite {
+    cursor: default;
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- Table Rows                                                         --- */
+/* -------------------------------------------------------------------------- */
+
+.eva-row {
+    display: flex;
+    position: relative;
+    flex: 0 1 auto;
+    height: 100%;
+    border-bottom: thin solid black;
+    border-left: thin solid black;
+}
+
+.eva-probes .eva-row {
+    border-top: thin solid black;
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- Table Cells                                                        --- */
+/* -------------------------------------------------------------------------- */
+
+.eva-stack {
+    width: 12px;
+    padding-top: 1px;
+    color: #777;
+    text-align: center;
+}
+
+.eva-cell {
+    flex: 1 1 auto;
+    border-right: thin solid black;
+}
+
+.eva-cell:nth-child(last) {
+    border-right: none;
+}
+
+.eva-probes .eva-cell {
+    padding: 2px;
+    text-align: center;
+}
+
+.eva-diff-shadow {
+    border: solid red thin;
+    position: relative;
+    z-index: -1;
+}
+
+.eva-diff-added { }
+.eva-diff-removed { text-decoration: strike }
+
+.eva-state-Here .eva-diff { background: pink; }
+.eva-state-After .eva-diff { background: yellow; }
+.eva-state-Then .eva-diff { background: green; }
+.eva-state-Else .eva-diff { background: orange; }
+
+/* -------------------------------------------------------------------------- */
+/* --- Alarms                                                             --- */
+/* -------------------------------------------------------------------------- */
+
+.eva-cell-alarms {
+    fill: orange;
+    position: absolute;
+    top: -1px;
+    right: 3px;
+    z-index: 1;
+}
+
+.eva-alarm-info {
+    font-size: x-small;
+    padding: 0px;
+    margin: 1px;
+}
+
+.eva-alarm-True { fill: green; }
+.eva-alarm-False { fill: red; }
+.eva-alarm-Unknown { fill: darkorange; }
+
+/* -------------------------------------------------------------------------- */
+/* --- Table Rows Background                                              --- */
+/* -------------------------------------------------------------------------- */
+
+.eva-probes .eva-row {
+    background: #cbe4cb;
+}
+
+.eva-values .eva-row {
+    background: #eee;
+}
+
+.eva-callstack.eva-row-odd .eva-row {
+    background: #eee;
+}
+
+.eva-callstack.eva-row-even .eva-row {
+    background: #e2e8e8;
+}
+
+.eva-callstack.eva-row-odd .eva-row .eva-transient {
+    background: #fff0d5;
+}
+
+.eva-callstack.eva-row-even .eva-row .eva-transient {
+    background: #fff0d5;
+}
+
+.eva-callstack.eva-row-selected .eva-row .eva-transient {
+    background: lightblue;
+}
+
+.eva-callstack.eva-row-selected .eva-row {
+    background: lightblue;
+}
+
+.eva-probes .eva-focused {
+    background: #02da02;
+}
+
+.eva-probes .eva-transient {
+    background: #fff0d5;
+}
+
+.eva-probes .eva-transient.eva-focused {
+    background: orange;
+}
+
+.eva-stack {
+    background: #eee;
+}
+
+.eva-callstack.eva-row-selected .eva-stack {
+    background: lightblue;
+}
+
+/* -------------------------------------------------------------------------- */
diff --git a/ivette/src/renderer/ASTview.tsx b/ivette/src/renderer/ASTview.tsx
index 3d6d82e4b316940218b02d7c3121d84b220f59c8..8583af381634c5a90bebcf4e82ac79ff56515e73 100644
--- a/ivette/src/renderer/ASTview.tsx
+++ b/ivette/src/renderer/ASTview.tsx
@@ -108,13 +108,13 @@ function getBulletColor(status: States.Tag) {
     case 'invalid_under_hyp': return '#FF0000';
     case 'valid':
     case 'valid_under_hyp': return '#00B900';
-    case 'considered_valid': return '#0000FF';
+    case 'considered_valid': return '#73bbbb';
     case 'invalid_but_dead':
     case 'valid_but_dead':
     case 'unknown_but_dead': return '#000000';
     case 'never_tried': return '#FFFFFF';
     case 'inconsistent': return '#FF00FF';
-    default: return '#0000FF';
+    default: return '#FF8300';
   }
 }
 
diff --git a/ivette/src/renderer/Application.tsx b/ivette/src/renderer/Application.tsx
index 381801a671a094310e84572fdc6867e065ff7112..494bf6008be4442b00ef445438d3e538ce08c6e4 100644
--- a/ivette/src/renderer/Application.tsx
+++ b/ivette/src/renderer/Application.tsx
@@ -2,6 +2,8 @@
 // --- Main React Component rendered by './index.js'
 // --------------------------------------------------------------------------
 
+// --- React & Dome
+
 import React from 'react';
 import * as Dome from 'dome';
 import * as States from 'frama-c/states';
@@ -9,22 +11,26 @@ import { Vfill } from 'dome/layout/boxes';
 import { LSplit } from 'dome/layout/splitters';
 import * as Toolbar from 'dome/frame/toolbars';
 import * as Sidebar from 'dome/frame/sidebars';
+import { GridHbox, GridItem } from 'dome/layout/grids';
 
-import './style.css';
+// --- Frama-C Plugins
 
 import { LabView, View, Group } from 'frama-c/LabViews';
+import Values from 'frama-c/eva/Values';
 import Dive from 'frama-c/dive/Dive';
-import { GridHbox, GridItem } from 'dome/layout/grids';
-import * as Controller from './Controller';
 
+// --- Ivette Main Renderers
+
+import * as Controller from './Controller';
 import ASTview from './ASTview';
 import ASTinfo from './ASTinfo';
 import Globals, { GlobalHint, useHints } from './Globals';
 import Properties from './Properties';
 import Locations from './Locations';
-import Values from './Values';
 import SourceCode from './SourceCode';
 
+import './style.css';
+
 // --------------------------------------------------------------------------
 // --- Selection Controls
 // --------------------------------------------------------------------------
diff --git a/ivette/src/renderer/Values.tsx b/ivette/src/renderer/Values.tsx
deleted file mode 100644
index 8e34cf741ac8b9e7f296e514af2fe122d8e186e5..0000000000000000000000000000000000000000
--- a/ivette/src/renderer/Values.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-// --------------------------------------------------------------------------
-// --- Eva Values
-// --------------------------------------------------------------------------
-
-import React from 'react';
-import * as States from 'frama-c/states';
-import * as Json from 'dome/data/json';
-import * as Eva from 'frama-c/api/plugins/eva/values';
-import * as Ast from 'frama-c/api/kernel/ast';
-import * as Compare from 'dome/data/compare';
-
-import { Table, Column } from 'dome/table/views';
-import { ArrayModel } from 'dome/table/arrays';
-import { Component } from 'frama-c/LabViews';
-import { Icon } from 'dome/controls/icons';
-import { Label } from 'dome/controls/labels';
-
-// --------------------------------------------------------------------------
-// --- Columns
-// --------------------------------------------------------------------------
-
-const CallstackRenderer = (
-  (cs: Eva.callstack) => <Label label={cs.short} title={cs.full} />
-);
-
-const ColumnCallstack = () => Column({
-  id: 'callstack',
-  label: 'Callstack',
-  title: 'Context of the evaluation',
-  align: 'left',
-  width: 150,
-  render: CallstackRenderer,
-});
-
-const AlarmRenderer = (
-  (alarm: boolean) => <>{alarm && <Icon id="ATTENTION" />}</>
-);
-
-const ColumnAlarm = (props: { visible: boolean }) => Column({
-  id: 'alarm',
-  label: 'Alarm',
-  title: 'Did the evaluation emit an alarm?',
-  align: 'center',
-  width: 26,
-  fixed: true,
-  icon: 'WARNING',
-  visible: props.visible,
-  render: AlarmRenderer,
-});
-
-const byValues: Compare.ByFields<Eva.valuesData> =
-  { callstack: Compare.defined(Compare.byFields({ full: Compare.string })) };
-
-class ValuesModel extends ArrayModel<Json.key<'#values'>, Eva.valuesData> {
-  constructor() {
-    super();
-    this.setOrderingByFields(byValues);
-  }
-}
-
-// --------------------------------------------------------------------------
-// --- Values Panel
-// --------------------------------------------------------------------------
-
-const Values = () => {
-
-  const model = React.useMemo(() => new ValuesModel(), []);
-  const evaValues = States.useSyncArray(Eva.values).getArray();
-  const selectMarker = States.useSelection()[0]?.current?.marker;
-  const markerInfo = States.useSyncArray(Ast.markerInfo).getArray();
-  const [name, setName] = React.useState<string | undefined>(undefined);
-
-  States.useRequest(Eva.getValues, selectMarker);
-
-  React.useEffect(() => {
-    model.removeAllData();
-    if (selectMarker && evaValues) {
-      const selectMarkerInfo = markerInfo.find((e) => e.key === selectMarker);
-      if (selectMarkerInfo && selectMarkerInfo.var !== 'function') {
-        switch (selectMarkerInfo.kind) {
-          case 'expression':
-          case 'lvalue':
-            evaValues.forEach((i) => model.setData(i.key, i));
-            setName(selectMarkerInfo.descr);
-            break;
-          case 'declaration':
-            evaValues.forEach((i) => model.setData(i.key, i));
-            setName(selectMarkerInfo.name);
-            break;
-          default:
-            setName(undefined);
-        }
-      }
-    } else {
-      setName(undefined);
-    }
-    model.reload();
-  }, [evaValues, selectMarker, markerInfo, model]);
-
-  // Component
-  return (
-    <>
-      <Table model={model}>
-        <ColumnCallstack />
-        <Column
-          id="value_before"
-          visible={!!name}
-          label={name && `${name} (before)`}
-          title="Values inferred by Eva just before the selected point"
-          disableSort
-          width={300}
-        />
-        <ColumnAlarm visible={!!name} />
-        <Column
-          id="value_after"
-          visible={!!name}
-          label={name && `${name} (after)`}
-          title="Values inferred by Eva just after the selected point"
-          disableSort
-          width={300}
-        />
-      </Table>
-    </>
-  );
-};
-
-// --------------------------------------------------------------------------
-// --- Export Component
-// --------------------------------------------------------------------------
-
-export default () => (
-  <Component
-    id="frama-c.values"
-    label="Eva Values"
-    title="Values inferred by the Eva analysis"
-  >
-    <Values />
-  </Component>
-);
-
-// --------------------------------------------------------------------------
diff --git a/ivette/yarn.lock b/ivette/yarn.lock
index e708d53b0660333948b3107c0e6c76eb09631b01..d1b9cd89c54e0e40fff49ff63ea303bc37d47555 100644
--- a/ivette/yarn.lock
+++ b/ivette/yarn.lock
@@ -876,6 +876,13 @@
     core-js-pure "^3.0.0"
     regenerator-runtime "^0.13.4"
 
+"@babel/runtime@^7.0.0":
+  version "7.12.5"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
+  integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
+  dependencies:
+    regenerator-runtime "^0.13.4"
+
 "@babel/runtime@^7.10.2":
   version "7.10.4"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.4.tgz#a6724f1a6b8d2f6ea5236dbfe58c7d7ea9c5eb99"
@@ -1015,6 +1022,11 @@
   resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd"
   integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==
 
+"@types/diff@^4.0.2":
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/@types/diff/-/diff-4.0.2.tgz#2e9bb89f9acc3ab0108f0f3dc4dbdcf2fff8a99c"
+  integrity sha512-mIenTfsIe586/yzsyfql69KRnA75S8SVXQbTLpDejRrjH0QSJcpu3AUOi/Vjnt9IOsXKxPhJfGpQUNMueIU1fQ==
+
 "@types/eslint-visitor-keys@^1.0.0":
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
@@ -1111,6 +1123,13 @@
     "@types/prop-types" "*"
     "@types/react" "*"
 
+"@types/react-window@^1.8.2":
+  version "1.8.2"
+  resolved "https://registry.yarnpkg.com/@types/react-window/-/react-window-1.8.2.tgz#a5a6b2762ce73ffaab7911ee1397cf645f2459fe"
+  integrity sha512-gP1xam68Wc4ZTAee++zx6pTdDAH08rAkQrWm4B4F/y6hhmlT9Mgx2q8lTCXnrPHXsr15XjRN9+K2DLKcz44qEQ==
+  dependencies:
+    "@types/react" "*"
+
 "@types/react@*", "@types/react@^16.9.17":
   version "16.9.32"
   resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.32.tgz#f6368625b224604148d1ddf5920e4fefbd98d383"
@@ -3226,6 +3245,11 @@ detect-node@^2.0.4:
   resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
   integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
 
+diff@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
+  integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
+
 diffie-hellman@^5.0.0:
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
@@ -5911,6 +5935,11 @@ mem@^4.0.0:
     mimic-fn "^2.0.0"
     p-is-promise "^2.0.0"
 
+"memoize-one@>=3.1.1 <6":
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
+  integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
+
 memory-fs@^0.4.0, memory-fs@^0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
@@ -7411,6 +7440,14 @@ react-virtualized@^9.21.2:
     prop-types "^15.6.0"
     react-lifecycles-compat "^3.0.4"
 
+react-window@^1.8.6:
+  version "1.8.6"
+  resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.6.tgz#d011950ac643a994118632665aad0c6382e2a112"
+  integrity sha512-8VwEEYyjz6DCnGBsd+MgkD0KJ2/OXFULyDtorIiTz+QzwoP94tBoA7CnbtyXMm+cCeAUER5KJcPtWl9cpKbOBg==
+  dependencies:
+    "@babel/runtime" "^7.0.0"
+    memoize-one ">=3.1.1 <6"
+
 react@^16.8:
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
diff --git a/src/plugins/value/api/values_request.ml b/src/plugins/value/api/values_request.ml
index 85d2df413cded868f8088cdb2d72602a095ca4ac..035340717f3f43761da0c8aeb4d49614d572d6ab 100644
--- a/src/plugins/value/api/values_request.ml
+++ b/src/plugins/value/api/values_request.ml
@@ -23,7 +23,16 @@
 open Server
 open Data
 open Cil_types
+
+module Kmap = Kernel_function.Hashtbl
+module Smap = Cil_datatype.Stmt.Hashtbl
+module CS = Value_types.Callstack
+module CSmap = CS.Hashtbl
+
 module Md = Markdown
+module Jkf = Kernel_ast.Kf
+module Jstmt = Kernel_ast.Stmt
+module Jmarker = Kernel_ast.Marker
 
 let package =
   Package.package
@@ -33,334 +42,460 @@ let package =
     ~readme:"eva.md"
     ()
 
-type value =
-  { value: string;
-    alarm: bool; }
-
-type evaluation =
-  | Unreachable
-  | Evaluation of value
-
-type after =
-  | Unchanged
-  | Reduced of evaluation
-
-type before_after =
-  { before: evaluation;
-    after_instr: after option;
-    after_then: after option;
-    after_else: after option; }
-
-type values =
-  { values: before_after;
-    callstack: (Value_util.callstack * before_after) list option; }
-
-let get_value = function
-  | Unreachable -> "Unreachable"
-  | Evaluation { value } -> value
-
-let get_alarm = function
-  | Unreachable -> false
-  | Evaluation { alarm } -> alarm
-
-let get_after_value =
-  Extlib.opt_map
-    (function Unchanged -> "unchanged" | Reduced eval -> get_value eval)
-
-module CallStackId =
-  Data.Index
-    (Value_types.Callstack.Map)
-    (struct
-      let name = "eva-callstack-id"
-    end)
-
-(* This pretty-printer drops the toplevel kf, which is always the function
-   in which we are pretty-printing the expression/term *)
-let pretty_callstack fmt cs =
-  match cs with
-  | [_, Kglobal] -> ()
-  | (_kf_cur, Kstmt callsite) :: q -> begin
-      let rec aux callsite = function
-        | (kf, callsite') :: q -> begin
-            Format.fprintf fmt "%a (%a)"
-              Kernel_function.pretty kf
-              Cil_datatype.Location.pretty (Cil_datatype.Stmt.loc callsite);
-            match callsite' with
-            | Kglobal -> ()
-            | Kstmt callsite' ->
-              Format.fprintf fmt " ←@ ";
-              aux callsite' q
+type probe =
+  | Pexpr of exp * stmt
+  | Plval of lval * stmt
+  | Pnone
+
+type callstack = Value_types.callstack
+type truth = Abstract_interp.truth
+type step = [ `Here | `After | `Then of exp | `Else of exp ]
+
+type domain = {
+  values: ( step * string ) list ;
+  alarms: ( truth * string ) list ;
+}
+
+let signal = Request.signal ~package ~name:"changed"
+    ~descr:(Md.plain "Emitted when EVA results has changed")
+
+let () = Analysis.register_computed_hook (fun () -> Request.emit signal)
+
+(* -------------------------------------------------------------------------- *)
+(* --- Marker Utilities                                                   --- *)
+(* -------------------------------------------------------------------------- *)
+
+let next_steps s : step list =
+  match s.skind with
+  | If(cond,_,_,_) -> [ `Then cond ; `Else cond ]
+  | Instr (Set _ | Call _ | Local_init _ | Asm _ | Code_annot _)
+  | Switch _ | Loop _ | Block _ | UnspecifiedSequence _
+  | TryCatch _ | TryFinally _ | TryExcept _
+    -> [ `After ]
+  | Instr (Skip _) | Return _ | Break _ | Continue _ | Goto _ | Throw _ -> []
+
+let probe_stmt s =
+  match s.skind with
+  | Instr (Set(lv,_,_)) -> Plval(lv,s)
+  | Instr (Call(Some lr,_,_,_)) -> Plval(lr,s)
+  | Instr (Local_init(v,_,_)) -> Plval((Var v,NoOffset),s)
+  | Return (Some e,_) | If(e,_,_,_) | Switch(e,_,_,_) -> Pexpr(e,s)
+  | _ -> Pnone
+
+let probe marker =
+  let open Printer_tag in
+  match marker with
+  | PLval(_,Kstmt s,l) -> Plval(l,s)
+  | PExp(_,Kstmt s,e) -> Pexpr(e,s)
+  | PStmt(_,s) | PStmtStart(_,s) -> probe_stmt s
+  | PVDecl(_,Kstmt s,v) -> Plval((Var v,NoOffset),s)
+  | _ -> Pnone
+
+(* -------------------------------------------------------------------------- *)
+(* --- Stmt Ranking                                                       --- *)
+(* -------------------------------------------------------------------------- *)
+
+module Ranking :
+sig
+  val stmt : stmt -> int
+  val sort : callstack list -> callstack list
+end =
+struct
+
+  class ranker =
+    object(self)
+      inherit Visitor.frama_c_inplace
+      (* ranks really starts at 1 *)
+      (* rank < 0 means not computed yet *)
+      val mutable rank = (-1)
+      val rmap = Smap.create 0
+      val fmark = Kmap.create 0
+      val fqueue = Queue.create ()
+
+      method private call kf =
+        if not (Kmap.mem fmark kf) then
+          begin
+            Kmap.add fmark kf () ;
+            Queue.push kf fqueue ;
           end
-        | _ -> assert false
-      in
-      Format.fprintf fmt "@[<hv>%a" Value_types.Callstack.pretty_hash cs;
-      aux callsite q;
-      Format.fprintf fmt "@]"
+
+      method private newrank s =
+        let r = succ rank in
+        Smap.add rmap s r ;
+        rank <- r ; r
+
+      method! vlval lv =
+        begin
+          try match fst lv with
+            | Var vi -> self#call (Globals.Functions.get vi)
+            | _ -> ()
+          with Not_found -> ()
+        end ; Cil.DoChildren
+
+      method! vstmt_aux s =
+        ignore (self#newrank s) ;
+        Cil.DoChildren
+
+      method flush =
+        while not (Queue.is_empty fqueue) do
+          let kf = Queue.pop fqueue in
+          ignore (Visitor.(visitFramacKf (self :> frama_c_visitor) kf))
+        done
+
+      method compute =
+        match Globals.entry_point () with
+        | kf , _ -> self#call kf ; self#flush
+        | exception Globals.No_such_entry_point _ -> ()
+
+      method rank s =
+        if rank < 0 then (rank <- 0 ; self#compute) ;
+        try Smap.find rmap s
+        with Not_found ->
+          let kf = Kernel_function.find_englobing_kf s in
+          self#call kf ;
+          self#flush ;
+          try Smap.find rmap s
+          with Not_found -> self#newrank s
+
     end
-  | _ -> assert false
-
-(* This pretty-printer prints only the lists of the functions, not
-   the locations. *)
-let pretty_callstack_short fmt cs =
-  match cs with
-  | [_, Kglobal] -> ()
-  | (_kf_cur, Kstmt _callsite) :: q ->
-    Format.fprintf fmt "%a" Value_types.Callstack.pretty_hash cs;
-    Pretty_utils.pp_flowlist ~left:"@[" ~sep:" ←@ " ~right:"@]"
-      (fun fmt (kf, _) -> Kernel_function.pretty fmt kf) fmt q
-  | _ -> assert false
-
-module CallStack = struct
-  type record
-
-  let record: record Record.signature = Record.signature ()
-
-  let id = Record.field record ~name:"id"
-      ~descr:(Md.plain "Callstack id") (module Jint)
-  let short = Record.field record ~name:"short"
-      ~descr:(Md.plain "Short name for the callstack") (module Jstring)
-  let full = Record.field record ~name:"full"
-      ~descr:(Md.plain "Full name for the callstack") (module Jstring)
-
-  module R =
-    (val
-      (Record.publish
-         ~package
-         ~name:"callstack"
-         ~descr:(Md.plain "CallStack")
-         record) : Record.S with type r = record)
-
-  type t = Value_types.callstack option
-
-  let jtype = R.jtype
-
-  let pp_callstack ~short = function
-    | None -> if short then "all" else ""
-    | Some callstack ->
-      let pp_text =
-        if short
-        then Pretty_utils.to_string ~margin:50 pretty_callstack_short
-        else Pretty_utils.to_string pretty_callstack
-      in
-      (pp_text callstack)
-
-  let id_callstack = function
-    | None -> -1
-    | Some callstack -> CallStackId.get callstack
-
-  let to_json callstack =
-    R.default |>
-    R.set id (id_callstack callstack) |>
-    R.set short (pp_callstack ~short:true callstack) |>
-    R.set full (pp_callstack ~short:false callstack) |>
-    R.to_json
-
-  let key = function
-    | None -> "all"
-    | Some callstack -> string_of_int (CallStackId.get callstack)
+
+  let stmt = let rk = new ranker in rk#rank
+
+  let rec ranks (rks : int list) (cs : callstack) : int list =
+    match cs with
+    | [] -> rks
+    | (_,Kglobal)::wcs -> ranks rks wcs
+    | (_,Kstmt s)::wcs -> ranks (stmt s :: rks) wcs
+
+  let order : int list -> int list -> int = Stdlib.compare
+
+  let sort (wcs : callstack list) : callstack list =
+    List.map fst @@
+    List.sort (fun (_,rp) (_,rq) -> order rp rq) @@
+    List.map (fun cs -> cs , ranks [] cs) wcs
+
+end
+
+(* -------------------------------------------------------------------------- *)
+(* --- Domain Utilities                                                   --- *)
+(* -------------------------------------------------------------------------- *)
+
+module Jcallstack : S with type t = callstack =
+struct
+  module I = Data.Index
+      (Value_types.Callstack.Map)
+      (struct let name = "eva-callstack-id" end)
+  let jtype = Data.declare ~package ~name:"callstack" I.jtype
+  type t = I.t
+  let to_json = I.to_json
+  let of_json = I.of_json
 end
 
+module Jcalls : Request.Output with type t = callstack =
+struct
+
+  type t = callstack
+  let jtype = Package.(Jlist (Jrecord [
+      "callee" , Jkf.jtype ;
+      "caller" , Joption Jkf.jtype ;
+      "stmt" , Joption Jstmt.jtype ;
+      "rank" , Joption Jnumber ;
+    ]))
+
+  let rec jcallstack jcallee ki cs : json list =
+    match ki , cs with
+    | Kglobal , _ | _ , [] -> [
+        `Assoc [ "callee", jcallee ]
+      ]
+    | Kstmt stmt , (called,ki) :: cs ->
+      let jcaller = Jkf.to_json called in
+      let callsite = `Assoc [
+          "callee", jcallee ;
+          "caller", jcaller ;
+          "stmt", Jstmt.to_json stmt ;
+          "rank", Jint.to_json (Ranking.stmt stmt) ;
+        ] in
+      callsite :: jcallstack jcaller ki cs
+
+  let to_json = function
+    | [] -> `List []
+    | (callee,ki)::cs -> `List (jcallstack (Jkf.to_json callee) ki cs)
 
-let consolidated = ref None
-let table = Hashtbl.create 100
-
-let iter f =
-  if Hashtbl.length table > 1
-  then Extlib.may (fun values -> f (None, values)) !consolidated;
-  Hashtbl.iter (fun key data -> f (Some key, data)) table
-
-let array =
-  let model = States.model () in
-  let () =
-    States.column
-      ~name:"callstack"
-      ~descr:(Md.plain "CallStack")
-      ~data:(module CallStack)
-      ~get:fst
-      model
-  in
-  let () =
-    States.column
-      ~name:"value_before"
-      ~descr:(Md.plain "Value inferred just before the selected point")
-      ~data:(module Jstring)
-      ~get:(fun (_, e) -> get_value e.before)
-      model
-  in
-  let () =
-    States.column
-      ~name:"alarm"
-      ~descr:(Md.plain "Did the evaluation led to an alarm?")
-      ~data:(module Jbool)
-      ~get:(fun (_, e) -> get_alarm e.before)
-      model
-  in
-  let () =
-    States.column
-      ~name:"value_after"
-      ~descr:(Md.plain "Value inferred just after the selected point")
-      ~data:(module Joption(Jstring))
-      ~get:(fun (_, e) -> get_after_value e.after_instr)
-      model
-  in
-  States.register_array
-    ~package
-    ~name:"values"
-    ~descr:(Md.plain "Abstract values inferred by the Eva analysis")
-    ~key:(fun (cs, _) -> CallStack.key cs)
-    ~iter
-    model
-
-let update_values values =
-  Hashtbl.clear table;
-  consolidated := Some values.values;
-  let () =
-    match values.callstack with
-    | None -> ()
-    | Some by_callstack ->
-      List.iter
-        (fun (callstack, before_after) ->
-           Hashtbl.add table callstack before_after)
-        by_callstack
-  in
-  States.reload array
-
-module type S = sig
-  val evaluate: kinstr -> exp -> values
-  val lvaluate: kinstr -> lval -> values
 end
 
-module Make (Eva: Analysis.S) : S = struct
-
-  let make_before eval before =
-    let before =
-      match before with
-      | `Bottom -> Unreachable
-      | `Value state -> Evaluation (eval state)
-    in
-    { before; after_instr = None; after_then = None; after_else = None; }
-
-  let make_callstack stmt eval =
-    let before = Eva.get_stmt_state_by_callstack ~after:false stmt in
-    match before with
-    | (`Bottom | `Top) -> []
-    | `Value before ->
-      let aux callstack before acc =
-        let before_after = make_before eval (`Value before) in
-        (callstack, before_after) :: acc
-      in
-      Value_types.Callstack.Hashtbl.fold aux before []
-
-  let make_before_after eval ~before ~after =
-    match before with
-    | `Bottom ->
-      { before = Unreachable;
-        after_instr = None;
-        after_then = None;
-        after_else = None; }
-    | `Value before ->
-      let before = eval before in
-      let after_instr =
-        match after with
-        | `Bottom -> Some (Reduced Unreachable)
-        | `Value after ->
-          let after = eval after in
-          if String.equal before.value after.value
-          then Some Unchanged
-          else Some (Reduced (Evaluation after))
-      in
-      { before = Evaluation before;
-        after_instr; after_then = None; after_else = None; }
-
-  let make_instr_callstack stmt eval =
-    let before = Eva.get_stmt_state_by_callstack ~after:false stmt in
-    let after = Eva.get_stmt_state_by_callstack ~after:true stmt in
-    match before, after with
-    | (`Bottom | `Top), _
-    | _, (`Bottom | `Top) -> []
-    | `Value before, `Value after ->
-      let aux callstack before acc =
-        let before = `Value before in
-        let after =
-          try `Value (Value_types.Callstack.Hashtbl.find after callstack)
+
+
+module Jtruth : Data.S with type t = truth =
+struct
+  type t = truth
+  let jtype =
+    Package.(Junion [ Jtag "True" ; Jtag "False" ; Jtag "Unknown" ])
+  let to_json = function
+    | Abstract_interp.Unknown -> `String "Unknown"
+    | True -> `String "True"
+    | False -> `String "False"
+  let of_json = function
+    | `String "True" -> Abstract_interp.True
+    | `String "False" -> Abstract_interp.False
+    | _ -> Abstract_interp.Unknown
+end
+
+(* -------------------------------------------------------------------------- *)
+(* --- EVA Proxy                                                          --- *)
+(* -------------------------------------------------------------------------- *)
+
+module type EvaProxy =
+sig
+  val callstacks : stmt -> callstack list
+  val domain : probe -> callstack option -> domain
+end
+
+module Proxy(A : Analysis.S) : EvaProxy =
+struct
+
+  open Eval
+  type dstate = A.Dom.state or_top_or_bottom
+
+  let callstacks stmt =
+    match A.get_stmt_state_by_callstack ~after:false stmt with
+    | `Top | `Bottom -> []
+    | `Value states ->
+      CSmap.fold_sorted (fun cs _st wcs -> cs :: wcs) states []
+
+  let dstate ~after stmt callstack =
+    match callstack with
+    | None -> (A.get_stmt_state ~after stmt :> dstate)
+    | Some cs ->
+      begin match A.get_stmt_state_by_callstack ~after stmt with
+        | `Top -> `Top
+        | `Bottom -> `Bottom
+        | `Value cmap ->
+          try `Value (CSmap.find cmap cs)
           with Not_found -> `Bottom
-        in
-        let before_after = make_before_after eval ~before ~after in
-        (callstack, before_after) :: acc
-      in
-      Value_types.Callstack.Hashtbl.fold aux before []
-
-  let make eval kinstr =
-    let before = Eva.get_kinstr_state ~after:false kinstr in
-    let values, callstack =
-      match kinstr with
-      | Cil_types.Kglobal ->
-        make_before eval before, None
-      | Cil_types.Kstmt stmt ->
-        match stmt.skind with
-        | Instr _ ->
-          let after = Eva.get_kinstr_state ~after:true kinstr in
-          let values = make_before_after eval ~before ~after in
-          let callstack = make_instr_callstack stmt eval in
-          values, Some callstack
-        | _ ->
-          make_before eval before, Some (make_callstack stmt eval)
-    in
-    { values; callstack; }
-
-
-  let evaluate kinstr expr =
-    let eval state =
-      let value, alarms = Eva.eval_expr state expr in
-      let alarm = not (Alarmset.is_empty alarms) in
-      let str = Format.asprintf "%a" (Bottom.pretty Eva.Val.pretty) value in
-      { value = str; alarm }
-    in
-    make eval kinstr
-
-  let lvaluate kinstr lval =
-    let eval state =
-      let value, alarms = Eva.copy_lvalue state lval in
-      let alarm = not (Alarmset.is_empty alarms) in
-      let flagged_value = match value with
-        | `Bottom -> Eval.Flagged_Value.bottom
-        | `Value v -> v
-      in
-      let pretty = Eval.Flagged_Value.pretty Eva.Val.pretty in
-      let str = Format.asprintf "%a" pretty flagged_value in
-      { value = str; alarm }
-    in
-    make eval kinstr
+      end
+
+  let dnone = {
+    alarms = [] ;
+    values = [] ;
+  }
+
+  let dtop = {
+    alarms = [] ;
+    values = [`Here , "Not available."] ;
+  }
+
+  let dbottom = {
+    alarms = [] ;
+    values = [`Here , "Unreachable."] ;
+  }
+
+  let dalarms alarms =
+    let pool = ref [] in
+    Alarmset.iter
+      (fun alarm status ->
+         let descr = Format.asprintf "@[<hov 2>%a@]" Alarms.pretty alarm
+         in pool := (status , descr) :: !pool
+      ) alarms ;
+    List.rev !pool
+
+  let deval (eval : A.Dom.state -> string * Alarmset.t) stmt callstack =
+    match dstate ~after:false stmt callstack with
+    | `Bottom -> dbottom
+    | `Top -> dtop
+    | `Value state ->
+      let value, alarms = eval state in
+      let dnext (step : step) vs = function
+        | `Top | `Bottom -> vs
+        | `Value state ->
+          let values =
+            try fst @@ eval state
+            with exn -> Printf.sprintf "Error (%S)" (Printexc.to_string exn)
+          in (step , values) :: vs in
+      let others = List.fold_right
+          begin fun st vs ->
+            match st with
+            | `Here -> vs (* absurd *)
+            | `After -> dnext st vs @@ dstate ~after:true stmt callstack
+            | `Then cond -> dnext st vs @@ A.assume_cond stmt state cond true
+            | `Else cond -> dnext st vs @@ A.assume_cond stmt state cond false
+          end (next_steps stmt) []
+      in {
+        values = (`Here,value) :: others ;
+        alarms = dalarms alarms ;
+      }
+
+  let e_expr expr state =
+    let value, alarms = A.eval_expr state expr in
+    begin
+      Pretty_utils.to_string (Bottom.pretty A.Val.pretty) value,
+      alarms
+    end
+
+  let e_lval lval state =
+    let value, alarms = A.copy_lvalue state lval in
+    let flagged = match value with
+      | `Bottom -> Eval.Flagged_Value.bottom
+      | `Value v -> v in
+    begin
+      Pretty_utils.to_string (Eval.Flagged_Value.pretty A.Val.pretty) flagged,
+      alarms
+    end
+
+  let domain p wcs = match p with
+    | Plval(l,s) -> deval (e_lval l) s wcs
+    | Pexpr(e,s) -> deval (e_expr e) s wcs
+    | Pnone -> dnone
+
 end
 
+let proxy =
+  let make (a : (module Analysis.S)) = (module Proxy(val a) : EvaProxy) in
+  let current = ref (make @@ Analysis.current_analyzer ()) in
+  let () = Analysis.register_hook
+      begin fun a ->
+        current := make a ;
+        Request.emit signal ;
+      end
+  in current
+
+(* -------------------------------------------------------------------------- *)
+(* --- Request getCallstacks                                              --- *)
+(* -------------------------------------------------------------------------- *)
+
+let () = Request.register ~package
+    ~kind:`GET ~name:"getCallstacks"
+    ~descr:(Md.plain "Callstacks for markers")
+    ~input:(module Jstmt)
+    ~output:(module Jlist(Jcallstack))
+    begin fun stmt ->
+      let module A = (val !proxy) in
+      Ranking.sort (A.callstacks stmt)
+    end
 
-let ref_request =
-  let module Analyzer = (val Analysis.current_analyzer ()) in
-  ref (module Make (Analyzer) : S)
+(* -------------------------------------------------------------------------- *)
+(* --- Request getCallstackInfo                                           --- *)
+(* -------------------------------------------------------------------------- *)
 
-let hook (module Analyzer: Analysis.S) =
-  ref_request := (module Make (Analyzer) : S)
+let () =
+  Request.register ~package
+    ~kind:`GET ~name:"getCallstackInfo"
+    ~descr:(Md.plain "Callstack Description")
+    ~input:(module Jcallstack)
+    ~output:(module Jcalls)
+    begin fun cs -> cs end
 
-let () = Analysis.register_hook hook
+(* -------------------------------------------------------------------------- *)
+(* --- Request getStmtInfo                                                --- *)
+(* -------------------------------------------------------------------------- *)
 
+let () =
+  let getStmtInfo = Request.signature ~input:(module Jstmt) () in
+  let set_fct = Request.result getStmtInfo ~name:"fct"
+      ~descr:(Md.plain "Englobing function")
+      (module Jkf) in
+  let set_rank = Request.result getStmtInfo ~name:"rank"
+      ~descr:(Md.plain "Global stmt order")
+      (module Jint) in
+  Request.register_sig ~package getStmtInfo
+    ~kind:`GET ~name:"getStmtInfo"
+    ~descr:(Md.plain "Stmt Information")
+    begin fun rq s ->
+      set_fct rq (Kernel_function.find_englobing_kf s) ;
+      set_rank rq (Ranking.stmt s) ;
+    end
 
-let update tag =
-  let module Request = (val !ref_request) in
-  match tag with
-  | Printer_tag.PExp (_kf, kinstr, expr) ->
-    update_values (Request.evaluate kinstr expr)
-  | Printer_tag.PLval (_kf, kinstr, lval) ->
-    update_values (Request.lvaluate kinstr lval)
-  | PVDecl (_kf, kinstr, varinfo) ->
-    update_values (Request.lvaluate kinstr (Var varinfo, NoOffset))
-  | _ -> ()
+(* -------------------------------------------------------------------------- *)
+(* --- Request getProbeInfo                                               --- *)
+(* -------------------------------------------------------------------------- *)
 
 let () =
-  Server.Request.register
-    ~package
-    ~kind:`GET
-    ~name:"getValues"
-    ~descr:(Md.plain "Get the abstract values computed for an expression or lvalue")
-    ~input:(module Kernel_ast.Marker)
-    ~output:(module Junit)
-    update
+  let getProbeInfo = Request.signature ~input:(module Jmarker) () in
+  let set_code = Request.result_opt getProbeInfo
+      ~name:"code" ~descr:(Md.plain "Probe source code")
+      (module Jstring) in
+  let set_stmt = Request.result_opt getProbeInfo
+      ~name:"stmt" ~descr:(Md.plain "Probe statement")
+      (module Jstmt) in
+  let set_rank = Request.result getProbeInfo
+      ~name:"rank" ~descr:(Md.plain "Probe statement rank")
+      ~default:0 (module Jint) in
+  let set_effects = Request.result getProbeInfo
+      ~name:"effects" ~descr:(Md.plain "Effectfull statement")
+      ~default:false (module Jbool) in
+  let set_condition = Request.result getProbeInfo
+      ~name:"condition" ~descr:(Md.plain "Conditional statement")
+      ~default:false (module Jbool) in
+  let set_probe rq pp p s =
+    begin
+      set_code rq (Some (Pretty_utils.to_string pp p)) ;
+      set_stmt rq (Some s) ;
+      set_rank rq (Ranking.stmt s) ;
+      List.iter
+        (function
+          | `Here -> ()
+          | `Then _ | `Else _ -> set_condition rq true
+          | `After -> set_effects rq true
+        )
+        (next_steps s)
+    end
+  in Request.register_sig ~package ~kind:`GET getProbeInfo
+    ~name:"getProbeInfo" ~descr:(Md.plain "Probe informations")
+    begin fun rq marker ->
+      match probe marker with
+      | Plval(l,s) ->
+        set_probe rq Printer.pp_lval l s ;
+      | Pexpr(e,s) ->
+        set_probe rq Printer.pp_exp e s ;
+      | Pnone -> ()
+    end
+
+(* -------------------------------------------------------------------------- *)
+(* --- Request getValues                                                  --- *)
+(* -------------------------------------------------------------------------- *)
+
+let () =
+  let getValues = Request.signature () in
+  let get_tgt = Request.param getValues ~name:"target"
+      ~descr:(Md.plain "Works with all markers containing an expression")
+      (module Jmarker) in
+  let get_cs = Request.param_opt getValues ~name:"callstack"
+      ~descr:(Md.plain "Callstack to collect (defaults to none)")
+      (module Jcallstack) in
+  let set_alarms = Request.result getValues ~name:"alarms"
+      ~descr:(Md.plain "Alarms raised during evaluation")
+      (module Jlist(Jpair(Jtruth)(Jstring))) in
+  let set_domain = Request.result_opt getValues ~name:"values"
+      ~descr:(Md.plain "Domain values")
+      (module Jstring) in
+  let set_after = Request.result_opt getValues ~name:"v_after"
+      ~descr:(Md.plain "Domain values after execution")
+      (module Jstring) in
+  let set_then = Request.result_opt getValues ~name:"v_then"
+      ~descr:(Md.plain "Domain values for true condition")
+      (module Jstring) in
+  let set_else = Request.result_opt getValues ~name:"v_else"
+      ~descr:(Md.plain "Domain values for false condition")
+      (module Jstring) in
+  Request.register_sig ~package getValues
+    ~kind:`GET ~name:"getValues"
+    ~descr:(Md.plain "Abstract values for the given marker")
+    begin fun rq () ->
+      let marker = get_tgt rq in
+      let callstack = get_cs rq in
+      let domain =
+        let module A : EvaProxy = (val !proxy) in
+        A.domain (probe marker) callstack in
+      set_alarms rq domain.alarms ;
+      List.iter
+        (fun (step,values) ->
+           let domain = Some values in
+           match step with
+           | `Here -> set_domain rq domain
+           | `After -> set_after rq domain
+           | `Then _ -> set_then rq domain
+           | `Else _ -> set_else rq domain
+        ) domain.values ;
+    end
+
+
+(* -------------------------------------------------------------------------- *)
diff --git a/src/plugins/value/engine/analysis.ml b/src/plugins/value/engine/analysis.ml
index 06c72bf50993d410b025682e5ee4f2ab914a45a9..d1d807098e3f571860d4221bbc4f5c2912f6e10a 100644
--- a/src/plugins/value/engine/analysis.ml
+++ b/src/plugins/value/engine/analysis.ml
@@ -40,10 +40,11 @@ module type Results = sig
   val eval_lval_to_loc: state -> lval -> location evaluated
   val eval_function_exp:
     state -> ?args:exp list -> exp ->  kernel_function list evaluated
+  val assume_cond : stmt -> state -> exp -> bool -> state or_bottom
 end
 
 module type S = sig
-  include Abstractions.S
+  include Abstractions.Eva
   include Results with type state := Dom.state
                    and type value := Val.t
                    and type location := Loc.location
@@ -95,6 +96,11 @@ module Make (Abstract: Abstractions.S) = struct
   let eval_function_exp state ?args e =
     Eval.eval_function_exp e ?args state >>=: (List.map fst)
 
+  let assume_cond stmt state cond positive =
+    fst (Eval.reduce state cond positive) >>- fun valuation ->
+    let dval = Eval.to_domain_valuation valuation in
+    Dom.assume stmt cond positive dval state
+
 end
 
 
@@ -121,9 +127,16 @@ let current_analyzer () = (module (val (snd !ref_analyzer)): S)
    Useful for the GUI parts that depend on it. *)
 module Analyzer_Hook = Hook.Build (struct type t = (module S) end)
 
+(* Set of hooks called whenever the current Analyzer is computed.
+   Useful for the GUI parts that depend on it. *)
+module Computed_Hook = Hook.Build (struct type t = unit end)
+
 (* Register a new hook. *)
 let register_hook = Analyzer_Hook.extend
 
+(* Register a new computed hook. *)
+let register_computed_hook = Computed_Hook.extend
+
 (* Sets the current Analyzer module for a given configuration.
    Calls the hooks above. *)
 let set_current_analyzer config (analyzer: (module Analyzer)) =
@@ -163,7 +176,8 @@ let force_compute () =
   let kf, lib_entry = Globals.entry_point () in
   reset_analyzer ();
   let module Analyzer = (val snd !ref_analyzer) in
-  Analyzer.compute_from_entry_point ~lib_entry kf
+  Analyzer.compute_from_entry_point ~lib_entry kf ;
+  Computed_Hook.apply ()
 
 (* Resets the Analyzer when the current project is changed. *)
 let () =
diff --git a/src/plugins/value/engine/analysis.mli b/src/plugins/value/engine/analysis.mli
index e96f3ed5ea11ade74b0b3a80210df1757d26e5db..50b943ccad73a30eb0d8e41ab94a60e334e471ef 100644
--- a/src/plugins/value/engine/analysis.mli
+++ b/src/plugins/value/engine/analysis.mli
@@ -40,6 +40,7 @@ module type Results = sig
   val eval_lval_to_loc: state -> lval -> location evaluated
   val eval_function_exp:
     state -> ?args:exp list -> exp -> kernel_function list evaluated
+  val assume_cond : stmt -> state -> exp -> bool -> state or_bottom
 end
 
 
@@ -55,7 +56,7 @@ end
 
 
 module type S = sig
-  include Abstractions.S
+  include Abstractions.Eva
   include Results with type state := Dom.state
                    and type value := Val.t
                    and type location := Loc.location
@@ -69,6 +70,10 @@ val register_hook: ((module S) -> unit) -> unit
     is changed. This happens when a new analysis is run with different
     abstractions than before, or when the current project is changed. *)
 
+val register_computed_hook: (unit -> unit) -> unit
+(** Registers a hook that will be called each time the [current] analyzer
+    has been computed. *)
+
 val force_compute : unit -> unit
 (** Perform a full analysis, starting from the [main] function. *)
 
diff --git a/src/plugins/value_types/value_types.mli b/src/plugins/value_types/value_types.mli
index 99589861241eeb1e280acc5f5abee60270ee4f0e..cdd8f86c51b15e072ea718a42cf62740ba5e5143 100644
--- a/src/plugins/value_types/value_types.mli
+++ b/src/plugins/value_types/value_types.mli
@@ -27,8 +27,23 @@ open Cil_types
 
 (* TODO: These types are already defined in Value_util. *)
 type call_site = kernel_function * kinstr
+(** Value call-site.
+    A callsite [(f,p)] represents a call at function [f] invoked
+    {i from} program point [p].
+*)
+
 type callstack = call_site list
-(** Value callstacks, as used e.g. in Db.Value hooks *)
+(** Value callstacks, as used e.g. in Db.Value hooks.
+
+    The head call site [(f,p)] is the most recent one,
+    where current function [f] has been called from program point [p].
+
+    Therefore, the tail call site is expected to be [(main,Kglobal)]
+    where [main] is the global entry point.
+
+    Moreover, given two consecutive call-sites […(_,p);(g,_)…] in a callstack,
+    program point [p] is then expected to live in function [g].
+ *)
 
 module Callsite: Datatype.S_with_collections with type t = call_site
 module Callstack: sig