diff --git a/ivette/.eslintignore b/ivette/.eslintignore
index a5bbd22266ef469c4109f7160c1a950e81386b5a..ba02ae7397f98663969ed6a85953c7f336d21644 100644
--- a/ivette/.eslintignore
+++ b/ivette/.eslintignore
@@ -6,5 +6,5 @@ dist
 coverage
 # don't lint tsc output, if any
 lib
-# don't lint dome, for the moment
-src/dome
\ No newline at end of file
+# don't lint the generated API
+api
diff --git a/ivette/.eslintrc.js b/ivette/.eslintrc.js
index 3743b5c868ed1be148cfdacef9520626e1f84149..3e4065c048a1906f5bb0b4f990ee3f72b52ff56c 100644
--- a/ivette/.eslintrc.js
+++ b/ivette/.eslintrc.js
@@ -15,8 +15,15 @@ module.exports = {
   parserOptions: {
     project: './tsconfig.json',
   },
+  settings: {
+    // Electron is in devDependencies because of its special build system
+    "import/core-modules": [ "electron" ]
+  },
   rules: {
+    // Do not enforce a displayName
     "react/display-name": "off",
+    // Do not enforce component methods order
+    "react/sort-comp": "off",
     // Be more strict on usage of useMemo and useRef
     "react-hooks/exhaustive-deps": "error",
     // Allow type any, even if it should be avoided
@@ -51,6 +58,8 @@ module.exports = {
     "padded-blocks": "off",
     // Allow braces on their own line
     "@typescript-eslint/brace-style": "off",
+    // Already has built-in compiler checks in TSC for that
+    "@typescript-eslint/no-unused-vars": "off",
     // Allow range conditions such as 0 <= x && x < 10
     "yoda": [2, "never", { "onlyEquality": true }],
     // Allow single command on new line after 'if' statement
@@ -77,6 +86,23 @@ module.exports = {
     "react/destructuring-assignment": "off",
     // Allow console errors and warnings
     "no-console": ["error", { allow: ["warn", "error"] }],
+    // Disable accessibility rules
+    "jsx-a11y/label-has-associated-control": "off",
+    "jsx-a11y/click-events-have-key-events": "off",
+    "jsx-a11y/no-static-element-interactions": "off",
+    "jsx-a11y/no-noninteractive-element-interactions": "off",
+    "jsx-a11y/no-autofocus": "off",
+    // Completely broken rule
+    "react/prop-types": "off",
+    // Enable ++ and --
+    "no-plusplus": "off",
+    // Enable nested ternary operations
+    "no-nested-ternary": "off",
+    // Checked by TSC compiler
+    "default-case": "off",
+    "consistent-return": "off",
+    // Allow modify properties of object passed in parameter
+    "no-param-reassign": "error", //[ "error", { "props": false } ],
     // Disallow the use of var in favor of let and const
     "no-var": "error",
     // Do not favor default import
diff --git a/ivette/.gitignore b/ivette/.gitignore
index 6cdb63fc2a1604ecfbbec90bf1de3799108da7d0..e776663c6d3cf6a1573ce6fd0d6ad83e4cfa63e0 100644
--- a/ivette/.gitignore
+++ b/ivette/.gitignore
@@ -5,6 +5,7 @@
 .ivette
 .dome-*.stamp
 .dome-*.back
+.eslint-cache
 node_modules
 yarn-error.log
 /bin
diff --git a/ivette/Makefile b/ivette/Makefile
index a9ec5f1eaebf64972c184c45a04a6ca087c11d26..6effb1ee1b2c0b918e8d54fce26e96a678fb6746 100644
--- a/ivette/Makefile
+++ b/ivette/Makefile
@@ -8,28 +8,24 @@ DOME_API=./src/frama-c
 COPYRIGHT=CEA LIST / LSL
 # --------------------------------------------------------------------------
 
-.PHONY: all app dev doc serve dist typecheck lint tsc
+.PHONY: all app dev doc serve dist lint fixlint
 
-all: typecheck lint app
+all: lint app
 
 app: dome-app
 dev: dome-dev
 dist: dome-dist
 
-typecheck: dome-pkg dome-templ
-	@echo "[Ivette] running ts typechecker"
-	yarn run typecheck
-
 lint: dome-pkg dome-templ
-	@echo "[Ivette] running ts linter"
+	@echo "[Ivette] running typechecker & linter"
+	yarn run typecheck
 	yarn run lint
 
 fixlint: dome-pkg dome-templ
-	@echo "[Ivette] running ts linter (with fix)"
+	@echo "[Ivette] running typechecker & linter (fix mode)"
+	yarn run typecheck
 	yarn run lint --fix
 
-tsc: typecheck fixlint
-
 # --------------------------------------------------------------------------
 # --- Frama-C API
 # --------------------------------------------------------------------------
diff --git a/ivette/api/plugins/eva/general/index.ts b/ivette/api/plugins/eva/general/index.ts
index 9e02bf5bcd03023ecf42eb76d21bbb007a679d38..519329eeb53dfa7fda48ac9c286506bd2da3440c 100644
--- a/ivette/api/plugins/eva/general/index.ts
+++ b/ivette/api/plugins/eva/general/index.ts
@@ -31,13 +31,12 @@ const getCallers_internal: Server.GetRequest<
   kind: Server.RqKind.GET,
   name:   'plugins.eva.general.getCallers',
   input:  Json.jKey<'#fct'>('#fct'),
-  output: Json.jList(Json.jTry(
-                       Json.jPair(
-                         Json.jFail(Json.jKey<'#fct'>('#fct'),
-                           '#fct expected'),
-                         Json.jFail(Json.jKey<'#stmt'>('#stmt'),
-                           '#stmt expected'),
-                       ))),
+  output: Json.jList(
+            Json.jTry(
+              Json.jPair(
+                Json.jFail(Json.jKey<'#fct'>('#fct'),'#fct expected'),
+                Json.jFail(Json.jKey<'#stmt'>('#stmt'),'#stmt expected'),
+              ))),
 };
 /** Get the list of call site of a function */
 export const getCallers: Server.GetRequest<
diff --git a/ivette/api/server_tsc.ml b/ivette/api/server_tsc.ml
index 67920d57a0efe34b280bade299d33f559dd29af4..4e11f8b611762b57c27393e014da65f20edd760a 100644
--- a/ivette/api/server_tsc.ml
+++ b/ivette/api/server_tsc.ml
@@ -77,7 +77,7 @@ let makeJtype ?self ~names =
     | Jtag a -> Format.fprintf fmt "\"%s\"" a
     | Jkey kd -> Format.fprintf fmt "Json.key<'#%s'>" kd
     | Jindex kd -> Format.fprintf fmt "Json.index<'#%s'>" kd
-    | Jdict(kd,js) -> Format.fprintf fmt "Json.Dict<'#%s',%a>" kd pp js
+    | Jdict js -> Format.fprintf fmt "@[<hov 2>Json.dict<@,%a>@]" pp js
     | Jdata id | Jenum id -> pp_ident fmt id
     | Joption js -> Format.fprintf fmt "%a |@ undefined" pp js
     | Jtuple js ->
@@ -174,10 +174,10 @@ let rec makeDecoder ~safe ?self ~names fmt js =
   | Jenum id -> jsafe ~safe (Pkg.name_of_ident id) (jenum names) fmt id
   | Jself -> jcall names fmt (Pkg.Derived.decode ~safe (getSelf self))
   | Joption js -> makeLoose fmt js
-  | Jdict(kd,js) ->
-    Format.fprintf fmt "@[<hov 2>Json.jDictionary('#%s',@,%a)@]" kd makeLoose js
+  | Jdict js ->
+    Format.fprintf fmt "@[<hov 2>Json.jDict(@,%a)@]" makeLoose js
   | Jlist js ->
-    Format.fprintf fmt "@[<hov 2>Json.jList(%a)@]" makeLoose js
+    Format.fprintf fmt "@[<hov 2>Json.jList(@,%a)@]" makeLoose js
   | Jarray js ->
     if safe
     then Format.fprintf fmt "@[<hov 2>Json.jArray(%a)@]" makeSafe js
@@ -250,11 +250,11 @@ let makeOrder ~self ~names fmt js =
       List.iter
         (fun (fd,js) -> Format.fprintf fmt "@ @[<hov 2>%s: %a,@]" fd pp js) jfs ;
       Format.fprintf fmt "@]@ })@]" ;
-    | Jdict(kd,js) ->
+    | Jdict js ->
       let jtype fmt js = makeJtype ~names fmt js in
       Format.fprintf fmt
-        "@[<hov 2>Compare.dictionary<@,Json.dict<'#%s'@,%a>>(@,%a)@]"
-        kd jtype js pp js
+        "@[<hov 2>Compare.dictionary<@,Json.dict<%a>>(@,%a)@]"
+        jtype js pp js
     | Jany | Junion _ | Jtag _ ->
       Format.fprintf fmt "Compare.structural"
   in pp fmt js
diff --git a/ivette/src/dome/src/misc/utils.ts b/ivette/src/dome/src/misc/utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9dbd931ae2cc4673f797078f4654ca50d9ad2201
--- /dev/null
+++ b/ivette/src/dome/src/misc/utils.ts
@@ -0,0 +1,90 @@
+// --------------------------------------------------------------------------
+// --- Utilities
+// --------------------------------------------------------------------------
+
+/**
+   @packageDocumentation
+   @module dome/misc/utils
+ */
+
+import type { CSSProperties } from 'react';
+
+type falsy = undefined | boolean | null | '';
+
+export type ClassSpec = string | falsy | { [cname: string]: true | falsy };
+
+/**
+   Utility function to merge various HTML class properties
+   into a `className` property.
+   Class specifications can be made of:
+    - a string, interpreted as a CSS class specification
+    - an object, with keys corresponding to CSS class associated
+      to true of falsy value.
+    - any falsy value, which is discarded
+
+    Example of usage:
+
+    * ```ts
+    *    const className = classes(
+    *       'my-base-class',
+    *        condition && 'my-class-when-condition',
+    *        {
+    *           'my-class-1': cond-1,
+    *           'my-class-2': cond-2,
+    *           'my-class-3': cond-3,
+    *        }
+    *    );
+    * ```
+
+ */
+export function classes(
+  ...args: ClassSpec[]
+): string {
+  const buffer: string[] = [];
+  args.forEach((cla) => {
+    if (cla) {
+      if (typeof (cla) === 'string' && cla !== '') buffer.push(cla);
+      else if (typeof (cla) === 'object') {
+        const cs = Object.keys(cla);
+        cs.forEach((c) => { if (cla[c]) buffer.push(c); });
+      }
+    }
+  });
+  return buffer.join(' ');
+}
+
+export type StyleSpec = falsy | CSSProperties;
+
+/**
+   Utility function to merge various CSS style properties
+   into a single CSS style object.
+
+   Each style specification can be made of a CSS object or (discarded)
+   falsy values.
+   Example of usage:
+
+   * ```ts
+   *    const sty = styles(
+   *        { ... },
+   *        cond-1 && { ... },
+   *        cond-2 && { ... },
+   *    );
+   * ```
+
+*/
+
+export function styles(
+  ...args: StyleSpec[]
+): CSSProperties | undefined {
+  let empty = true;
+  let buffer = {};
+  args.forEach((sty) => {
+    if (sty && typeof (sty) === 'object') {
+      empty = false;
+      buffer = { ...buffer, ...sty };
+    }
+  });
+  return (empty ? undefined : buffer);
+}
+
+// --------------------------------------------------------------------------
diff --git a/ivette/src/dome/src/renderer/controls/buttons.tsx b/ivette/src/dome/src/renderer/controls/buttons.tsx
index 0789962f9c941cebc455ec5c53ea11d0b2cce94d..f3f2c0a38ce7e4039e988a00d6aa2f87d0c41d12 100644
--- a/ivette/src/dome/src/renderer/controls/buttons.tsx
+++ b/ivette/src/dome/src/renderer/controls/buttons.tsx
@@ -8,18 +8,21 @@
 */
 
 import React from 'react';
+import { classes } from 'dome/misc/utils';
 import { Icon } from './icons';
 import { LabelProps } from './labels';
 import './style.css';
 
-const DISABLED = ({ disabled = false, enabled = true }) => !!disabled || !enabled;
-
 interface EVENT {
   stopPropagation: () => void;
 }
 
+const DISABLED = ({ disabled = false, enabled = true }) => (
+  !!disabled || !enabled
+);
+
 const TRIGGER = (onClick?: () => void) => (evt?: EVENT) => {
-  evt && evt.stopPropagation();
+  evt?.stopPropagation();
   if (onClick) onClick();
 };
 
@@ -27,13 +30,15 @@ const TRIGGER = (onClick?: () => void) => (evt?: EVENT) => {
 // --- LCD
 // --------------------------------------------------------------------------
 
-const LCDCLASS = 'dome-xButton dome-xBoxButton dome-text-code dome-xButton-lcd ';
-
 /** Button-like label. */
 export function LCD(props: LabelProps) {
+  const className = classes(
+    'dome-xButton dome-xBoxButton dome-text-code dome-xButton-lcd ',
+    props.className,
+  );
   return (
     <label
-      className={LCDCLASS + (props.className || '')}
+      className={className}
       title={props.title}
       style={props.style}
     >
@@ -42,7 +47,7 @@ export function LCD(props: LabelProps) {
       {props.children}
     </label>
   );
-};
+}
 
 // --------------------------------------------------------------------------
 // --- Led
@@ -72,11 +77,15 @@ export interface LEDprops {
 }
 
 export const LED = (props: LEDprops) => {
-  const classes = 'dome-xButton-led dome-xButton-led-'
-    + (props.status || 'inactive')
-    + (props.blink ? ' dome-xButton-blink' : '')
-    + (props.className ? ' ' + props.className : '');
-  return (<div className={classes} title={props.title} style={props.style} />);
+  const className = classes(
+    'dome-xButton-led',
+    `dome-xButton-led-${props.status || 'inactive'}`,
+    props.blink && 'dome-xButton-blink',
+    props.className,
+  );
+  return (
+    <div className={className} title={props.title} style={props.style} />
+  );
 };
 
 // --------------------------------------------------------------------------
@@ -89,19 +98,26 @@ const HIDDEN: React.CSSProperties = { visibility: 'hidden' };
 interface LABELprops {
   disabled: boolean;
   label: string;
-};
+}
 
 const LABEL = ({ disabled, label }: LABELprops) => (
-  <div className="dome-xButton-label" >
-    <div className="dome-xButton-label dome-control-enabled"
-      style={disabled ? HIDDEN : VISIBLE} >{label}</div>
-    <div className="dome-xButton-label dome-control-disabled"
-      style={disabled ? VISIBLE : HIDDEN}>{label}</div>
+  <div className="dome-xButton-label">
+    <div
+      className="dome-xButton-label dome-control-enabled"
+      style={disabled ? HIDDEN : VISIBLE}
+    >{label}
+    </div>
+    <div
+      className="dome-xButton-label dome-control-disabled"
+      style={disabled ? VISIBLE : HIDDEN}
+    >{label}
+    </div>
   </div>
 );
 
 export type ButtonKind =
-  undefined | 'default' | 'active' | 'primary' | 'warning' | 'positive' | 'negative';
+  undefined | 'default' |
+  'active' | 'primary' | 'warning' | 'positive' | 'negative';
 
 export interface ButtonProps {
   /** Text of the label. Prepend to other children elements. */
@@ -151,18 +167,23 @@ export interface ButtonProps {
 /** Standard button. */
 export function Button(props: ButtonProps) {
   const disabled = props.onClick ? DISABLED(props) : true;
-  const { focusable = false, kind = 'default',
+  const {
+    focusable = false, kind = 'default',
     visible = true, display = true, blink = false,
-    selected, icon, label, className = '' } = props;
-  const theClass = 'dome-xButton dome-xBoxButton dome-xButton-'
-    + (selected ? 'selected' : kind)
-    + (!blink ? '' : ' dome-xButton-blink')
-    + (visible ? '' : ' dome-control-hidden')
-    + (display ? '' : ' dome-control-erased')
-    + (className ? ' ' + className : '');
+    selected, icon, label, className = '',
+  } = props;
+  const theClass = classes(
+    'dome-xButton dome-xBoxButton',
+    `dome-xButton-${selected ? 'selected' : kind}`,
+    blink && 'dome-xButton-blink',
+    !visible && 'dome-control-hidden',
+    !display && 'dome-control-erased',
+    className,
+  );
   const nofocus = focusable ? undefined : true;
   return (
-    <button type='button'
+    <button
+      type="button"
       className={theClass}
       disabled={disabled}
       onClick={TRIGGER(props.onClick)}
@@ -175,7 +196,7 @@ export function Button(props: ButtonProps) {
       {label && <LABEL disabled={disabled} label={label} />}
     </button>
   );
-};
+}
 
 // --------------------------------------------------------------------------
 // --- Icon Button
@@ -184,18 +205,23 @@ export function Button(props: ButtonProps) {
 /** Circled Icon Button. The label property is ignored. */
 export const CircButton = (props: ButtonProps) => {
   const disabled = props.onClick ? DISABLED(props) : true;
-  const { focusable = false, kind = 'default',
+  const {
+    focusable = false, kind = 'default',
     visible = true, display = true,
-    selected, icon, blink, className = '' } = props;
-  const theClass = 'dome-xButton dome-xCircButton dome-xButton-'
-    + (selected ? 'selected' : kind)
-    + (!blink ? '' : ' dome-xButton-blink')
-    + (visible ? '' : ' dome-control-hidden')
-    + (display ? '' : ' dome-control-erased')
-    + (className ? ' ' + className : '');
+    selected, icon, blink, className = '',
+  } = props;
+  const theClass = classes(
+    'dome-xButton dome-xCircButton',
+    `dome-xButton-${selected ? 'selected' : kind}`,
+    blink && 'dome-xButton-blink',
+    !visible && 'dome-control-hidden',
+    !display && 'dome-control-erased',
+    className,
+  );
   const nofocus = focusable ? undefined : true;
   return (
-    <button type='button'
+    <button
+      type="button"
       className={theClass}
       disabled={disabled}
       onClick={TRIGGER(props.onClick)}
@@ -259,15 +285,17 @@ export function IconButton(props: IconButtonProps) {
   const {
     icon, title, className,
     visible = true, display = true, selected,
-    kind = 'default'
+    kind = 'default',
   } = props;
   if (!icon) return null;
-  const theClass = 'dome-xIconButton'
-    + ' dome-xIconButton-' + (selected ? 'selected' : kind)
-    + (disabled ? ' dome-control-disabled' : ' dome-control-enabled')
-    + (visible ? '' : ' dome-control-hidden')
-    + (display ? '' : ' dome-control-erased')
-    + (className ? ' ' + className : '');
+  const theClass = classes(
+    'dome-xIconButton',
+    `dome-xIconButton-${selected ? 'selected' : kind}`,
+    (disabled ? 'dome-control-disabled' : 'dome-control-enabled'),
+    !visible && 'dome-control-hidden',
+    !display && 'dome-control-erased',
+    className,
+  );
   return (
     <Icon
       id={icon}
@@ -279,7 +307,7 @@ export function IconButton(props: IconButtonProps) {
       onClick={TRIGGER(disabled ? undefined : props.onClick)}
     />
   );
-};
+}
 
 // --------------------------------------------------------------------------
 // --- CheckBox
@@ -318,11 +346,14 @@ export const Checkbox = (props: CheckProps) => {
     <label
       title={props.title}
       style={props.style}
-      className={baseClass + labelClass} >
-      <input type="checkbox"
+      className={baseClass + labelClass}
+    >
+      <input
+        type="checkbox"
         disabled={disabled}
         checked={value}
-        onChange={callback} />
+        onChange={callback}
+      />
       {props.label}
     </label>
   );
@@ -334,9 +365,11 @@ export const Switch = (props: CheckProps) => {
   const disabled = onChange ? DISABLED(props) : true;
   const iconId = props.value ? 'SWITCH.ON' : 'SWITCH.OFF';
   const onClick = onChange && (() => onChange(!value));
-  const className = 'dome-xSwitch '
-    + (disabled ? 'dome-control-disabled' : 'dome-control-enabled')
-    + (props.className ? ' ' + props.className : '');
+  const className = classes(
+    'dome-xSwitch',
+    (disabled ? 'dome-control-disabled' : 'dome-control-enabled'),
+    props.className,
+  );
   return (
     <label
       title={props.title}
@@ -387,13 +420,18 @@ export function Radio<A>(props: RadioProps<A>) {
     <label
       title={props.title}
       style={props.style}
-      className={baseClass + labelClass} >
-      <input type="radio"
-        disabled={disabled} checked={checked} onChange={onChange} />
+      className={baseClass + labelClass}
+    >
+      <input
+        type="radio"
+        disabled={disabled}
+        checked={checked}
+        onChange={onChange}
+      />
       {props.label}
     </label>
   );
-};
+}
 
 // --------------------------------------------------------------------------
 // --- Radio Group
@@ -410,15 +448,15 @@ export interface RadioGroupProps<A> {
   onChange?: (newValue: A) => void;
   /** Default selected value. */
   className?: string;
-  /** Additional style for the `<dov/>` container of Raiods */
+  /** Additional style for the `< dov /> ` container of Raiods */
   style?: React.CSSProperties;
   /** [[Radio]] Buttons. */
   children: any;
-};
+}
 
 /**
-   Selector of Radio Buttons.
-   Childrens of the `RadioGroup` shall be [[Radio]] buttons.
+   Selector of Radio Buttons.  Childrens of the `RadioGroup` shall be [[Radio]]
+   buttons.
 
    The selected value of the group is broadcasted to the radio buttons. Their
    callbacks are activated _before_ the radio group one, if any.
@@ -428,26 +466,34 @@ export interface RadioGroupProps<A> {
    group is enabled, the `disabled` property of each radio button is taken into
    account.
 
-   The radio buttons inside a group are laidout in a vertical box with the additional
-   styling properties.
- */
+   The radio buttons inside a group are laidout in a vertical box with the
+   additional styling properties.
+*/
 export function RadioGroup<A>(props: RadioGroupProps<A>) {
-  const { className = '', style, value: selection, onChange: onGroupSelect } = props;
+  const {
+    className = '',
+    style,
+    value: selection,
+    onChange: onGroupSelect,
+  } = props;
   const disabledGroup = onGroupSelect ? DISABLED(props) : true;
   const makeRadio = (elt: any) => {
     const radioProps = elt.props as RadioProps<A>;
     const disabled = disabledGroup || DISABLED(radioProps);
     const { onSelection: onRadioSelect } = radioProps;
     const onSelection = (v: A) => {
-      onRadioSelect && onRadioSelect(v);
-      onGroupSelect && onGroupSelect(v);
+      if (onRadioSelect) onRadioSelect(v);
+      if (onGroupSelect) onGroupSelect(v);
     };
     return React.cloneElement(elt, {
-      disabled, enabled: !disabled, selection, onSelection
+      disabled,
+      enabled: !disabled,
+      selection,
+      onSelection,
     });
   };
   return (
-    <div className={'dome-xRadio-group ' + className} style={style}>
+    <div className={`dome - xRadio - group ${className} `} style={style}>
       {React.Children.map(props.children, makeRadio)}
     </div>
   );
@@ -474,7 +520,7 @@ export interface SelectProps {
   onChange?: (newValue?: string) => void;
   /** Default selected value. */
   className?: string;
-  /** Additional style for the `<dov/>` container of Raiods */
+  /** Additional style for the `< dov /> ` container of Raiods */
   style?: React.CSSProperties;
   /** Shall be [[Item]] elements. */
   children: any;
@@ -483,34 +529,37 @@ export interface SelectProps {
 /**
    Menu Button.
 
-   The different options shall be specified with HTML `<option/>` and `<optgroup />` elements.
+   The different options shall be specified with HTML
+   `< option/>` and `<optgroup/>` elements.
    Options and group shall be specified as follows:
 
-       <optgroup label='…'>…</optgroup>
-       <option value='…' disabled=… >…</option>
+   *   <optgroup label='…'>…</optgroup>
+   *   <option value='…' disabled=… >…</option>
 
-   **Warning:** most non-positionning CSS properties might not work on the`<select>` element due
-   to the native rendering used by Chrome.
-   You might use`-webkit-appearance: none` to cancel this behavior, you will have to restyle the
+   **Warning:** most non-positionning CSS properties might not
+   work on the`<select>` element due to the native rendering used
+   by Chrome.
+   You might use `-webkit-appearance: none` to cancel this behavior,
+   you will have to restyle the
    component entirely, which is quite ugly by default.
  */
 export function Select(props: SelectProps) {
   const { onChange, className = '', placeholder } = props;
   const disabled = onChange ? DISABLED(props) : true;
   const callback = (evt: React.ChangeEvent<HTMLSelectElement>) => {
-    onChange && onChange(evt.target.value);
+    if (onChange) onChange(evt.target.value);
   };
   return (
     <select
       id={props.id}
       disabled={disabled}
-      className={'dome-xSelect ' + className}
+      className={`dome - xSelect ${className} `}
       style={props.style}
       title={props.title}
       value={props.value}
       onChange={callback}
     >
-      {placeholder && <option value=''>— {placeholder} —</option>}
+      {placeholder && <option value="">— {placeholder} —</option>}
       {props.children}
     </select>
   );
@@ -533,7 +582,7 @@ export interface FieldProps {
   disabled?: boolean;
   /** Default fo `false`. */
   autoFocus?: boolean;
-  /** Currently selected value (updated on `ENTER` key)*/
+  /** Currently selected value (updated on `ENTER` key) */
   value?: string;
   /** Callback on `ENTER` key. */
   onChange?: (newValue: string) => void;
@@ -541,7 +590,7 @@ export interface FieldProps {
   onEdited?: (tmpValue: string) => void;
   /** Default selected value. */
   className?: string;
-  /** Additional style for the `<dov/>` container of Raiods */
+  /** Additional style for the `< dov /> ` container of Raiods */
   style?: React.CSSProperties;
 }
 
@@ -554,31 +603,36 @@ export const Field = (props: FieldProps) => {
   const disabled = onChange ? DISABLED(props) : true;
   const theValue = current ?? value;
   const ONCHANGE = (evt: React.ChangeEvent<HTMLInputElement>) => {
-    let text = evt.target.value || '';
+    const text = evt.target.value || '';
     setCurrent(text);
-    onEdited && onEdited(text);
+    if (onEdited) onEdited(text);
   };
   const ONKEYPRESS = (evt: React.KeyboardEvent) => {
     switch (evt.key) {
       case 'Enter':
         setCurrent(undefined);
-        onChange && current && onChange(current);
+        if (onChange && current) onChange(current);
         break;
       case 'Escape':
         setCurrent(undefined);
         break;
-    };
+      default:
+        break;
+    }
   };
   return (
-    <input id={props.id} type='text'
+    <input
+      id={props.id}
+      type="text"
       autoFocus={!disabled && props.autoFocus}
       value={theValue}
-      className={'dome-xField ' + className}
+      className={`dome - xField ${className} `}
       style={props.style}
       disabled={disabled}
       placeholder={props.placeholder}
       onKeyPress={ONKEYPRESS}
-      onChange={ONCHANGE} />
+      onChange={ONCHANGE}
+    />
   );
 };
 
diff --git a/ivette/src/dome/src/renderer/controls/icons.tsx b/ivette/src/dome/src/renderer/controls/icons.tsx
index a15c2642911594d421d221b8355de6def6d2aa82..d6547bf5a8d89165ae31e9d4d1a9154aa4ffb33a 100644
--- a/ivette/src/dome/src/renderer/controls/icons.tsx
+++ b/ivette/src/dome/src/renderer/controls/icons.tsx
@@ -12,11 +12,12 @@
 
 import _ from 'lodash';
 import React from 'react';
+import { classes } from 'dome/misc/utils';
 import Gallery from './gallery.json';
 import './style.css';
 
-/*@ internal */
-const Icons: { [id: string]: { viewBox?: string, path: string } } = Gallery;
+/* @ internal */
+const Icons: { [id: string]: { viewBox?: string; path: string } } = Gallery;
 
 // --------------------------------------------------------------------------
 // --- Raw SVG element
@@ -48,9 +49,9 @@ export function SVG(props: SVGprops): null | JSX.Element {
   const {
     title,
     size = 12,
-    offset = _.floor(- size * 0.125),
+    offset = _.floor(-size * 0.125),
   } = props;
-  const { path, viewBox = "0 0 24 24" } = icon;
+  const { path, viewBox = '0 0 24 24' } = icon;
   return (
     <svg
       height={size}
@@ -77,7 +78,7 @@ export interface IconProps extends SVGprops {
   /** Fill style property. */
   fill?: string;
   /** Click callback. */
-  onClick?: (event?: React.MouseEvent) => void;
+  onClick?: (event?: React.MouseEvent<HTMLDivElement>) => void;
 }
 
 /**
@@ -85,8 +86,11 @@ export interface IconProps extends SVGprops {
    Consult the [Icon Gallery](../guides/icons.md.html) for default icons.
  */
 export function Icon(props: IconProps) {
-  const { id, title, onClick, fill, size, className = '', offset, style } = props;
-  const divClass = 'dome-xIcon ' + className;
+  const {
+    id, title, onClick, fill,
+    size, className = '', offset, style,
+  } = props;
+  const divClass = classes('dome-xIcon', className);
   const divStyle = fill ? { fill, ...style } : style;
   return (
     <div
@@ -130,13 +134,16 @@ export function Badge(props: BadgeProps) {
   } else {
     const style =
       (typeof (value) === 'number' && value < 10) ||
-        (typeof (value) === 'string' && value.length == 1) ?
+        (typeof (value) === 'string' && value.length === 1) ?
         { paddingLeft: 2, paddingRight: 2 } : {};
-    content = <label style={style} className='dome-text-label'>{value}</label>;
+    content = <label style={style} className="dome-text-label">{value}</label>;
   }
   return (
-    <div className="dome-xBadge"
-      title={title} onClick={onClick}>
+    <div
+      className="dome-xBadge"
+      title={title}
+      onClick={onClick}
+    >
       {content}
     </div>
   );
@@ -169,10 +176,11 @@ export function register(icon: CustomIcon) {
    See [[register]] to add custom icons to the gallery.
  */
 export function forEach(fn: (ico: CustomIcon) => void) {
-  for (let id in Icons) {
+  const ids = Object.keys(Icons);
+  ids.forEach((id) => {
     const jsicon = Icons[id];
     fn({ id, ...jsicon });
-  }
+  });
 }
 
 // --------------------------------------------------------------------------
diff --git a/ivette/src/dome/src/renderer/controls/labels.tsx b/ivette/src/dome/src/renderer/controls/labels.tsx
index 43273c5ee1505b8cf6aec5548c6157b452315a17..28c1b23777a292e970418364efb5d4e4c4afd22d 100644
--- a/ivette/src/dome/src/renderer/controls/labels.tsx
+++ b/ivette/src/dome/src/renderer/controls/labels.tsx
@@ -8,6 +8,7 @@
 */
 
 import React from 'react';
+import { classes } from 'dome/misc/utils';
 import { Icon } from './icons';
 import './style.css';
 
@@ -34,14 +35,17 @@ export interface LabelProps {
 
 const makeLabel = (className: string, props: LabelProps) => {
   const { display = true } = props;
-  const allClasses =
-    className +
-    (display ? ' ' : ' dome-control-erased ') +
-    (props.className || '');
+  const allClasses = classes(
+    className,
+    !display && 'dome-control-erased',
+    props.className,
+  );
   return (
-    <label className={allClasses}
+    <label
+      className={allClasses}
       title={props.title}
-      style={props.style} >
+      style={props.style}
+    >
       {props.icon && <Icon title={props.title} id={props.icon} />}
       {props.label}
       {props.children}
@@ -53,11 +57,11 @@ const makeLabel = (className: string, props: LabelProps) => {
 // --- CSS Classes
 // --------------------------------------------------------------------------
 
-const LABEL = "dome-xLabel dome-text-label";
-const TITLE = "dome-xLabel dome-text-title";
-const DESCR = "dome-xLabel dome-text-descr";
-const TDATA = "dome-xLabel dome-text-data";
-const TCODE = "dome-xLabel dome-text-code";
+const LABEL = 'dome-xLabel dome-text-label';
+const TITLE = 'dome-xLabel dome-text-title';
+const DESCR = 'dome-xLabel dome-text-descr';
+const TDATA = 'dome-xLabel dome-text-data';
+const TCODE = 'dome-xLabel dome-text-code';
 
 // --------------------------------------------------------------------------
 // --- Components
diff --git a/ivette/src/dome/src/renderer/data/compare.ts b/ivette/src/dome/src/renderer/data/compare.ts
index b9bcc989b71af9d42a5f7ab74406649a466ad81d..337be8717b306847b32aa58963bf784c3e2d0fcf 100644
--- a/ivette/src/dome/src/renderer/data/compare.ts
+++ b/ivette/src/dome/src/renderer/data/compare.ts
@@ -13,7 +13,7 @@ import FastCompare from 'react-fast-compare';
 /**
    Interface for comparison functions.
    These function shall fullfill the following contract:
-   - `compare(x,y) == 0` shall be an equivalence relation
+   - `compare(x,y) === 0` shall be an equivalence relation
      (reflexive, symmetric, transitive)
    - `compare(x,y) <= 0` shall be a complete order
      (reflexive, antisymetric, transitive)
@@ -38,7 +38,10 @@ export type bignum = bigint | number;
 
 /** Detect Non-NaN numbers and big-ints. */
 export function isBigNum(x: any): x is bignum {
-  return typeof (x) === 'bigint' || (typeof (x) === 'number' && !Number.isNaN(x));
+  return (
+    (typeof (x) === 'bigint') ||
+    (typeof (x) === 'number' && !Number.isNaN(x))
+  );
 }
 
 /** @internal */
@@ -88,17 +91,18 @@ export function number(x: number, y: number) {
 */
 export function alpha(x: string, y: string) {
   const cmp = primitive(x.toLowerCase(), y.toLowerCase());
-  return cmp != 0 ? cmp : primitive(x, y);
+  return cmp !== 0 ? cmp : primitive(x, y);
 }
 
 /** Combine comparison orders in sequence. */
 export function sequence<A>(...orders: (Order<A> | undefined)[]): Order<A> {
   return (x: A, y: A) => {
     if (x === y) return 0;
-    for (const order of orders) {
+    for (let k = 0; k < orders.length; k++) {
+      const order = orders[k];
       if (order) {
         const cmp = order(x, y);
-        if (cmp != 0) return cmp;
+        if (cmp !== 0) return cmp;
       }
     }
     return 0;
@@ -108,9 +112,9 @@ export function sequence<A>(...orders: (Order<A> | undefined)[]): Order<A> {
 /** Compare optional values. Undefined values come first. */
 export function option<A>(order: Order<A>): Order<undefined | A> {
   return (x?: A, y?: A) => {
-    if (x == undefined && y == undefined) return 0;
-    if (x == undefined) return -1;
-    if (y == undefined) return 1;
+    if (x === undefined && y === undefined) return 0;
+    if (x === undefined) return -1;
+    if (y === undefined) return 1;
     return order(x, y);
   };
 }
@@ -118,9 +122,9 @@ export function option<A>(order: Order<A>): Order<undefined | A> {
 /** Compare optional values. Undefined values come last. */
 export function defined<A>(order: Order<A>): Order<undefined | A> {
   return (x?: A, y?: A) => {
-    if (x == undefined && y == undefined) return 0;
-    if (x == undefined) return 1;
-    if (y == undefined) return -1;
+    if (x === undefined && y === undefined) return 0;
+    if (x === undefined) return 1;
+    if (y === undefined) return -1;
     return order(x, y);
   };
 }
@@ -134,7 +138,7 @@ export function array<A>(order: Order<A>): Order<A[]> {
     const m = p < q ? p : q;
     for (let k = 0; k < m; k++) {
       const cmp = order(x[k], y[k]);
-      if (cmp != 0) return cmp;
+      if (cmp !== 0) return cmp;
     }
     return p - q;
   };
@@ -143,11 +147,13 @@ export function array<A>(order: Order<A>): Order<A[]> {
 /** Order by dictionary order.
     Can be used directly with an enum type declaration.
  */
-export function byEnum<A extends string>(d: { [key: string]: A }): Order<A> {
+export function byEnum<A extends string>(
+  d: { [key: string]: A },
+): Order<A> {
   const ranks: { [index: string]: number } = {};
   const values = Object.keys(d);
   const wildcard = values.length;
-  values.forEach((C, k) => ranks[C] = k);
+  values.forEach((C, k) => { ranks[C] = k; });
   return (x: A, y: A) => {
     if (x === y) return 0;
     const rx = ranks[x] ?? wildcard;
@@ -157,20 +163,21 @@ export function byEnum<A extends string>(d: { [key: string]: A }): Order<A> {
 }
 
 /** Order string enumeration constants.
-    `byRank(v1,...,vN)` will order constant following the order of arguments.
-    Non-listed constants appear at the end, or at the rank specified by `'*'`. */
+    `byRank(v1,...,vN)` will order constant following the
+    order of arguments.
+    Non-listed constants appear at the end, or at the rank
+    specified by `'*'`. */
 export function byRank(...args: string[]): Order<string> {
   const ranks: { [index: string]: number } = {};
-  args.forEach((C, k) => ranks[C] = k);
+  args.forEach((C, k) => { ranks[C] = k; });
   const wildcard = ranks['*'] ?? ranks.length;
   return (x: string, y: string) => {
     if (x === y) return 0;
     const rx = ranks[x] ?? wildcard;
     const ry = ranks[y] ?? wildcard;
-    if (rx == wildcard && ry == wildcard)
+    if (rx === wildcard && ry === wildcard)
       return primitive(x, y);
-    else
-      return rx - ry;
+    return rx - ry;
   };
 }
 
@@ -196,7 +203,7 @@ export function getKeys<T>(a: T): (keyof T)[] {
  */
 export type ByFields<A> = {
   [P in keyof A]?: Order<A[P]>;
-}
+};
 
 /**
    Maps each field of `A` to some comparison of the associated type.
@@ -205,36 +212,39 @@ export type ByFields<A> = {
 */
 export type ByAllFields<A> = {
   [P in keyof A]: Order<A[P]>;
-}
+};
 
-/** Object comparison by (some) fields.
+/**
+   Object comparison by (some) fields.
 
-    Compare objects field by field, using the comparison orders provided by the
-    `order` argument. Order of field comparison is taken from the `order`
-    argument, not from the compared values.
+   Compare objects field by field, using the comparison orders provided by the
+   `order` argument. Order of field comparison is taken from the `order`
+   argument, not from the compared values.
 
-    You may not compare _all_ fields of the compared values.  For optional
-    fields, you shall provide a comparison function compatible with type
-    `undefined`.
+   You may not compare _all_ fields of the compared values.  For optional
+   fields, you shall provide a comparison function compatible with type
+   `undefined`.
 
-    It might be difficult for Typescript to typecheck `byFields(…)` expressions
-    when dealing with optional types. In such cases, you shall use `byFields<A>(…)`
-    and explicitly mention the type of compared values.
+   It might be difficult for Typescript to typecheck `byFields(…)` expressions
+   when dealing with optional types. In such cases, you shall use
+   `byFields<A>(…)` and explicitly mention the type of compared values.
 
-    Example:
+   Example:
 
-        type foo = { id: number, name?: string, descr?: string }
-        const compare = fields<foo>({ id: number, name: option(alpha) });
+   *   type foo = { id: number, name?: string, descr?: string }
+   *   const compare = fields<foo>({ id: number, name: option(alpha) });
 
 */
 export function byFields<A>(order: ByFields<A>): Order<A> {
   return (x: A, y: A) => {
     if (x === y) return 0;
-    for (const fd of getKeys(order)) {
+    const fds = getKeys(order);
+    for (let k = 0; k < fds.length; k++) {
+      const fd = fds[k];
       const byFd = order[fd];
       if (byFd !== undefined) {
         const cmp = byFd(x[fd], y[fd]);
-        if (cmp != 0) return cmp;
+        if (cmp !== 0) return cmp;
       }
     }
     return 0;
@@ -248,10 +258,12 @@ export function byFields<A>(order: ByFields<A>): Order<A> {
 export function byAllFields<A>(order: ByAllFields<A>): Order<A> {
   return (x: A, y: A) => {
     if (x === y) return 0;
-    for (const fd of getKeys<ByFields<A>>(order)) {
+    const fds = getKeys<ByFields<A>>(order);
+    for (let k = 0; k < fds.length; k++) {
+      const fd = fds[k];
       const byFd = order[fd];
       const cmp = byFd(x[fd], y[fd]);
-      if (cmp != 0) return cmp;
+      if (cmp !== 0) return cmp;
     }
     return 0;
   };
@@ -273,13 +285,14 @@ export function dictionary<A>(order: Order<A>): Order<dict<A>> {
     const p = fs.length;
     const q = gs.length;
     for (let i = 0, j = 0; i < p && j < q;) {
-      let a = undefined, b = undefined;
+      let a;
+      let b;
       const f = fs[i];
       const g = gs[j];
       if (f <= g) { a = dx[f]; i++; }
       if (g <= f) { b = dy[g]; j++; }
       const cmp = phi(a, b);
-      if (cmp != 0) return cmp;
+      if (cmp !== 0) return cmp;
     }
     return p - q;
   };
@@ -292,7 +305,7 @@ export function pair<A, B>(ordA: Order<A>, ordB: Order<B>): Order<[A, B]> {
     const [x1, y1] = u;
     const [x2, y2] = v;
     const cmp = ordA(x1, x2);
-    return cmp != 0 ? cmp : ordB(y1, y2);
+    return cmp !== 0 ? cmp : ordB(y1, y2);
   };
 }
 
@@ -307,9 +320,9 @@ export function triple<A, B, C>(
     const [x1, y1, z1] = u;
     const [x2, y2, z2] = v;
     const cmp1 = ordA(x1, x2);
-    if (cmp1 != 0) return cmp1;
+    if (cmp1 !== 0) return cmp1;
     const cmp2 = ordB(y1, y2);
-    if (cmp2 != 0) return cmp2;
+    if (cmp2 !== 0) return cmp2;
     return ordC(z1, z2);
   };
 }
@@ -326,11 +339,11 @@ export function tuple4<A, B, C, D>(
     const [x1, y1, z1, t1] = u;
     const [x2, y2, z2, t2] = v;
     const cmp1 = ordA(x1, x2);
-    if (cmp1 != 0) return cmp1;
+    if (cmp1 !== 0) return cmp1;
     const cmp2 = ordB(y1, y2);
-    if (cmp2 != 0) return cmp2;
+    if (cmp2 !== 0) return cmp2;
     const cmp3 = ordC(z1, z2);
-    if (cmp3 != 0) return cmp3;
+    if (cmp3 !== 0) return cmp3;
     return ordD(t1, t2);
   };
 }
@@ -348,13 +361,13 @@ export function tuple5<A, B, C, D, E>(
     const [x1, y1, z1, t1, w1] = u;
     const [x2, y2, z2, t2, w2] = v;
     const cmp1 = ordA(x1, x2);
-    if (cmp1 != 0) return cmp1;
+    if (cmp1 !== 0) return cmp1;
     const cmp2 = ordB(y1, y2);
-    if (cmp2 != 0) return cmp2;
+    if (cmp2 !== 0) return cmp2;
     const cmp3 = ordC(z1, z2);
-    if (cmp3 != 0) return cmp3;
+    if (cmp3 !== 0) return cmp3;
     const cmp4 = ordD(t1, t2);
-    if (cmp4 != 0) return cmp4;
+    if (cmp4 !== 0) return cmp4;
     return ordE(w1, w2);
   };
 }
@@ -364,11 +377,16 @@ export function tuple5<A, B, C, D, E>(
 // --------------------------------------------------------------------------
 
 /** @internal */
-enum RANK { UNDEFINED, BOOLEAN, SYMBOL, NAN, BIGNUM, STRING, ARRAY, OBJECT, FUNCTION };
+enum RANK {
+  UNDEFINED,
+  BOOLEAN, SYMBOL, NAN, BIGNUM,
+  STRING,
+  ARRAY, OBJECT, FUNCTION
+}
 
 /** @internal */
 function rank(x: any): RANK {
-  let t = typeof x;
+  const t = typeof x;
   switch (t) {
     case 'undefined': return RANK.UNDEFINED;
     case 'boolean': return RANK.BOOLEAN;
@@ -378,14 +396,16 @@ function rank(x: any): RANK {
     case 'bigint':
       return RANK.BIGNUM;
     case 'string': return RANK.STRING;
-    case 'object': return Array.isArray(x) ? RANK.ARRAY : RANK.OBJECT;
     case 'function': return RANK.FUNCTION;
+    case 'object':
+      return Array.isArray(x) ? RANK.ARRAY : RANK.OBJECT;
   }
 }
 
 /**
    Universal structural comparison.
-   Values are ordered by _rank_, each being associated with some type of values:
+   Values are ordered by _rank_, each being
+   associated with some type of values:
    1. undefined values;
    2. booleans;
    3. symbols;
@@ -398,10 +418,9 @@ function rank(x: any): RANK {
    For values of same primitive type, primitive ordering is performed.
 
    For array values, lexicographic ordering is performed.
-
-   For object values, lexicographic ordering is performed over their properties:
-   properties are ordered by name, and recursive structural ordering is performed
-   on property values.
+   For object values, lexicographic ordering is performed over their
+   properties: they are ordered by name, and recursive structural
+   ordering is performed on property values.
 
    All functions are compared equal.
  */
@@ -418,17 +437,18 @@ export function structural(x: any, y: any): number {
     const p = fs.length;
     const q = gs.length;
     for (let i = 0, j = 0; i < p && j < q;) {
-      let a = undefined, b = undefined;
+      let a;
+      let b;
       const f = fs[i];
       const g = gs[j];
       if (f <= g) { a = x[f]; i++; }
       if (g <= f) { b = y[g]; j++; }
       const cmp = structural(a, b);
-      if (cmp != 0) return cmp;
+      if (cmp !== 0) return cmp;
     }
     return p - q;
   }
   return rank(x) - rank(y);
-};
+}
 
 // --------------------------------------------------------------------------
diff --git a/ivette/src/dome/src/renderer/data/json.ts b/ivette/src/dome/src/renderer/data/json.ts
index e34c8b6aa9f1e5e1255309262ac168956b820c0a..2dfd4a7b52b6c5ad984f6ad6d41ae84fd294a98d 100644
--- a/ivette/src/dome/src/renderer/data/json.ts
+++ b/ivette/src/dome/src/renderer/data/json.ts
@@ -11,7 +11,7 @@
 import { DEVEL } from 'dome/system';
 
 export type json =
-  undefined | null | number | string | json[] | { [key: string]: json }
+  undefined | null | number | string | json[] | { [key: string]: json };
 
 /**
    Parse without _revivals_.
@@ -71,14 +71,14 @@ export interface Encoder<D> {
 }
 
 /** Can be used for most encoders. */
-export function identity<A>(v: A): A { return v; };
+export function identity<A>(v: A): A { return v; }
 
 // --------------------------------------------------------------------------
 // --- Primitives
 // --------------------------------------------------------------------------
 
 /** Always returns `undefined` on any input. */
-export const jNull: Safe<undefined> = (_: json) => undefined;
+export const jNull: Safe<undefined> = () => undefined;
 
 /** Identity. */
 export const jAny: Safe<json> = (js: json) => js;
@@ -124,7 +124,7 @@ export const jString: Loose<string> = (js: json) => (
     might be more efficient.
 */
 export function jTag<A>(tg: A): Loose<A> {
-  return (js: json) => Object.is(js, tg) ? tg : undefined;
+  return (js: json) => (Object.is(js, tg) ? tg : undefined);
 }
 
 /**
@@ -132,7 +132,7 @@ export function jTag<A>(tg: A): Loose<A> {
    Can be used directly for enum types, eg. `jEnum(myEnumType)`.
  */
 export function jEnum<A>(d: { [tag: string]: A }): Loose<A> {
-  return (v: json) => typeof v === 'string' ? d[v] : undefined;
+  return (v: json) => (typeof v === 'string' ? d[v] : undefined);
 }
 
 /**
@@ -141,8 +141,8 @@ export function jEnum<A>(d: { [tag: string]: A }): Loose<A> {
    type `A`. However, it will not protected you from missings constants in `A`.
 */
 export function jTags<A>(...values: ((string | number) & A)[]): Loose<A> {
-  var m = new Map<string | number, A>();
-  values.forEach(v => m.set(v, v));
+  const m = new Map<string | number, A>();
+  values.forEach((v) => m.set(v, v));
   return (v: json) => (typeof v === 'string' ? m.get(v) : undefined);
 }
 
@@ -155,16 +155,20 @@ export function jDefault<A>(
   fn: Loose<A>,
   defaultValue: A,
 ): Safe<A> {
-  return (js: json) =>
-    js === undefined ? defaultValue : (fn(js) ?? defaultValue);
+  return (js: json) => (
+    js === undefined ? defaultValue : (fn(js) ?? defaultValue)
+  );
 }
 
 /**
-   Force returning `undefined` or a default value for `undefined` _or_ `null` JSON input.
+   Force returning `undefined` or a default value for
+   `undefined` _or_ `null` JSON input.
    Typically useful to leverage an existing `Safe<A>` decoder.
  */
 export function jOption<A>(fn: Safe<A>, defaultValue?: A): Loose<A> {
-  return (js: json) => (js === undefined || js === null ? defaultValue : fn(js));
+  return (js: json) => (
+    js === undefined || js === null ? defaultValue : fn(js)
+  );
 }
 
 /**
@@ -175,7 +179,7 @@ export function jFail<A>(fn: Loose<A>, error: string | Error): Safe<A> {
   return (js: json) => {
     const d = fn(js);
     if (d !== undefined) return d;
-    throw error;
+    throw (typeof (error) === 'string' ? new Error(error) : error);
   };
 }
 
@@ -216,10 +220,11 @@ export function jMap<A>(fn: Loose<A>): Safe<Map<string, A>> {
   return (js: json) => {
     const m = new Map<string, A>();
     if (js !== null && typeof js === 'object' && !Array.isArray(js)) {
-      for (let k of Object.keys(js)) {
+      const keys = Object.keys(js);
+      keys.forEach((k) => {
         const v = fn(js[k]);
         if (v !== undefined) m.set(k, v);
-      }
+      });
     }
     return m;
   };
@@ -229,7 +234,7 @@ export function jMap<A>(fn: Loose<A>): Safe<Map<string, A>> {
    Converts dictionaries to maps.
  */
 export function eMap<A>(fn: Encoder<A>): Encoder<Map<string, undefined | A>> {
-  return m => {
+  return (m) => {
     const js: json = {};
     m.forEach((v, k) => {
       if (v !== undefined) {
@@ -248,7 +253,7 @@ export function eMap<A>(fn: Encoder<A>): Encoder<Map<string, undefined | A>> {
    to discard undefined elements, or use a true _safe_ decoder.
  */
 export function jArray<A>(fn: Safe<A>): Safe<A[]> {
-  return (js: json) => Array.isArray(js) ? js.map(fn) : [];
+  return (js: json) => (Array.isArray(js) ? js.map(fn) : []);
 }
 
 /**
@@ -259,7 +264,7 @@ export function jArray<A>(fn: Safe<A>): Safe<A[]> {
 export function jList<A>(fn: Loose<A>): Safe<A[]> {
   return (js: json) => {
     const buffer: A[] = [];
-    if (Array.isArray(js)) js.forEach(vj => {
+    if (Array.isArray(js)) js.forEach((vj) => {
       const d = fn(vj);
       if (d !== undefined) buffer.push(d);
     });
@@ -271,9 +276,9 @@ export function jList<A>(fn: Loose<A>): Safe<A[]> {
    Exports all non-undefined elements.
  */
 export function eList<A>(fn: Encoder<A>): Encoder<(A | undefined)[]> {
-  return m => {
+  return (m) => {
     const js: json[] = [];
-    m.forEach(v => {
+    m.forEach((v) => {
       if (v !== undefined) {
         const u = fn(v);
         if (u !== undefined) js.push(u);
@@ -288,10 +293,10 @@ export function jPair<A, B>(
   fa: Safe<A>,
   fb: Safe<B>,
 ): Loose<[A, B]> {
-  return (js: json) => Array.isArray(js) ? [
+  return (js: json) => (Array.isArray(js) ? [
     fa(js[0]),
     fb(js[1]),
-  ] : undefined;
+  ] : undefined);
 }
 
 /** Similar to [[jPair]]. */
@@ -300,11 +305,11 @@ export function jTriple<A, B, C>(
   fb: Safe<B>,
   fc: Safe<C>,
 ): Loose<[A, B, C]> {
-  return (js: json) => Array.isArray(js) ? [
+  return (js: json) => (Array.isArray(js) ? [
     fa(js[0]),
     fb(js[1]),
     fc(js[2]),
-  ] : undefined;
+  ] : undefined);
 }
 
 /** Similar to [[jPair]]. */
@@ -314,12 +319,12 @@ export function jTuple4<A, B, C, D>(
   fc: Safe<C>,
   fd: Safe<D>,
 ): Loose<[A, B, C, D]> {
-  return (js: json) => Array.isArray(js) ? [
+  return (js: json) => (Array.isArray(js) ? [
     fa(js[0]),
     fb(js[1]),
     fc(js[2]),
     fd(js[3]),
-  ] : undefined;
+  ] : undefined);
 }
 
 /** Similar to [[jPair]]. */
@@ -330,13 +335,13 @@ export function jTuple5<A, B, C, D, E>(
   fd: Safe<D>,
   fe: Safe<E>,
 ): Loose<[A, B, C, D, E]> {
-  return (js: json) => Array.isArray(js) ? [
+  return (js: json) => (Array.isArray(js) ? [
     fa(js[0]),
     fb(js[1]),
     fc(js[2]),
     fd(js[3]),
     fe(js[4]),
-  ] : undefined;
+  ] : undefined);
 }
 
 /**
@@ -345,7 +350,7 @@ export function jTuple5<A, B, C, D, E>(
 */
 export type Props<A> = {
   [P in keyof A]: Safe<A[P]>;
-}
+};
 
 /**
    Decode an object given the decoders of its fields.
@@ -355,7 +360,8 @@ export function jObject<A>(fp: Props<A>): Loose<A> {
   return (js: json) => {
     if (js !== null && typeof js === 'object' && !Array.isArray(js)) {
       const buffer = {} as A;
-      for (var k of Object.keys(fp)) {
+      const keys = Object.keys(fp);
+      keys.forEach((k) => {
         const fn = fp[k as keyof A];
         if (fn !== undefined) {
           const fj = js[k];
@@ -364,7 +370,7 @@ export function jObject<A>(fp: Props<A>): Loose<A> {
             if (fv !== undefined) buffer[k as keyof A] = fv;
           }
         }
-      }
+      });
       return buffer;
     }
     return undefined;
@@ -376,8 +382,8 @@ export function jObject<A>(fp: Props<A>): Loose<A> {
  */
 export function jUnion<A>(...cases: Loose<A>[]): Loose<A> {
   return (js: json) => {
-    for (var fn of cases) {
-      const fv = fn(js);
+    for (let i = 0; i < cases.length; i++) {
+      const fv = cases[i](js);
       if (fv !== undefined) return fv;
     }
     return undefined;
@@ -389,7 +395,7 @@ export function jUnion<A>(...cases: Loose<A>[]): Loose<A> {
 */
 export type EProps<A> = {
   [P in keyof A]?: Encoder<A[P]>;
-}
+};
 
 /**
    Encode an object given the provided encoders by fields.
@@ -399,7 +405,8 @@ export type EProps<A> = {
 export function eObject<A>(fp: EProps<A>): Encoder<A> {
   return (m: A) => {
     const js: json = {};
-    for (var k of Object.keys(fp)) {
+    const keys = Object.keys(fp);
+    keys.forEach((k) => {
       const fn = fp[k as keyof A];
       if (fn !== undefined) {
         const fv = m[k as keyof A];
@@ -408,9 +415,9 @@ export function eObject<A>(fp: EProps<A>): Encoder<A> {
           if (r !== undefined) js[k] = r;
         }
       }
-    }
+    });
     return js;
-  }
+  };
 }
 
 // Intentionnaly internal and only declared
@@ -423,56 +430,43 @@ export function forge<K, A>(_tag: K, data: A): phantom<K, A> {
   return data as any;
 }
 
-/** String key with kind. Can be used as a `string` but shall be created with [forge]. */
+/** String key with kind.
+    Can be used as a `string` but shall be created with [forge]. */
 export type key<K> = phantom<K, string>;
 
-/** Number index with kind. Can be used as a `number` but shall be created with [forge]. */
+/** Number index with kind.
+    Can be used as a `number` but shall be created with [forge]. */
 export type index<K> = phantom<K, number>;
 
 /** Decoder for `key<K>` strings. */
 export function jKey<K>(kd: K): Loose<key<K>> {
-  return (js: json) => typeof js === 'string' ? forge(kd, js) : undefined;
+  return (js: json) => (typeof js === 'string' ? forge(kd, js) : undefined);
 }
 
 /** Decoder for `index<K>` numbers. */
 export function jIndex<K>(kd: K): Loose<index<K>> {
-  return (js: json) => typeof js === 'number' ? forge(kd, js) : undefined;
-}
-
-/** Dictionaries with « typed » keys. */
-export type dict<K, A> = phantom<K, { [key: string]: A }>
-
-/** Lookup into dictionary.
-    Better than a direct access to `d[k]` for undefined values. */
-export function lookup<K, A>(d: dict<K, A>, k: key<K>): A | undefined {
-  return d[k];
-}
-
-/** Empty dictionary. */
-export function empty<K, A>(kd: K): dict<K, A> {
-  return forge(kd, {} as any);
+  return (js: json) => (typeof js === 'number' ? forge(kd, js) : undefined);
 }
 
-/** Dictionary extension. */
-export function index<K, A>(d: dict<K, A>, key: key<K>, value: A) {
-  d[key] = value;
-}
+/** Dictionaries. */
+export type dict<A> = { [key: string]: A };
 
 /**
    Decode a JSON dictionary, discarding all inconsistent entries.
    If the JSON contains no valid entry, still returns `{}`.
 */
-export function jDictionary<K, A>(kd: K, fn: Loose<A>): Safe<dict<K, A>> {
+export function jDict<A>(fn: Loose<A>): Safe<dict<A>> {
   return (js: json) => {
-    const buffer: dict<K, A> = empty(kd);
+    const buffer: dict<A> = {};
     if (js !== null && typeof js === 'object' && !Array.isArray(js)) {
-      for (var key of Object.keys(js)) {
+      const keys = Object.keys(js);
+      keys.forEach((key) => {
         const fd = js[key];
         if (fd !== undefined) {
           const fv = fn(fd);
-          if (fv !== undefined) index(buffer, forge(kd, key), fv);
+          if (fv !== undefined) buffer[key] = fv;
         }
-      }
+      });
     }
     return buffer;
   };
@@ -482,16 +476,17 @@ export function jDictionary<K, A>(kd: K, fn: Loose<A>): Safe<dict<K, A>> {
    Encode a dictionary into JSON, discarding all inconsistent entries.
    If the dictionary contains no valid entry, still returns `{}`.
 */
-export function eDictionary<K, A>(fn: Encoder<A>): Encoder<dict<K, A>> {
-  return (d: dict<K, A>) => {
+export function eDict<A>(fn: Encoder<A>): Encoder<dict<A>> {
+  return (d: dict<A>) => {
     const js: json = {};
-    for (var k of Object.keys(d)) {
+    const keys = Object.keys(d);
+    keys.forEach((k) => {
       const fv = d[k];
       if (fv !== undefined) {
         const fr = fn(fv);
         if (fr !== undefined) js[k] = fr;
       }
-    }
+    });
     return js;
   };
 }
diff --git a/ivette/src/dome/src/renderer/data/states.ts b/ivette/src/dome/src/renderer/data/states.ts
index c1fbb827435c3b1f721268d059e9a4640d7aeb0b..ce7704696991024b8dc1ecb49baaa7825a6c3466 100644
--- a/ivette/src/dome/src/renderer/data/states.ts
+++ b/ivette/src/dome/src/renderer/data/states.ts
@@ -11,9 +11,6 @@
 import React from 'react';
 import Emitter from 'events';
 import isEqual from 'react-fast-compare';
-import { DEVEL } from 'dome/misc/system';
-import * as Dome from 'dome';
-import * as JSON from './json';
 
 const UPDATE = 'dome.states.update';
 
@@ -25,7 +22,7 @@ export class State<A> {
 
   constructor(initValue: A) {
     this.value = initValue;
-    this.emitter = new Emitter;
+    this.emitter = new Emitter();
     this.getValue = this.getValue.bind(this);
     this.setValue = this.setValue.bind(this);
   }
@@ -59,222 +56,8 @@ export function useState<A>(s: State<A>): [A, (update: A) => void] {
   React.useEffect(() => {
     s.on(setCurrent);
     return () => s.off(setCurrent);
-  });
+  }, [s]);
   return [current, s.setValue];
-};
-
-// --------------------------------------------------------------------------
-// --- Settings
-// --------------------------------------------------------------------------
-
-/**
-   Generic interface to Window and Global Settings.
-   To be used with [[useSettings]] with instances of its derived classes,
-   typically [[WindowSettings]] and [[GlobalSettings]]. You should never have
-   to implement a Settings class on your own.
-
-   All setting values are identified with
-   an untyped `dataKey: string`, that can be dynamically modified
-   for each component. Hence, two components might share both datakeys
-   and settings.
-
-   When several components share the same setting `dataKey` the behavior will be
-   different depending on the situation:
-   - for Window Settings, each component in each window retains its own
-   setting value, although the last modified value from _any_ of them will be
-   saved and used for any further initial value;
-   - for Global Settings, all components synchronize to the last modified value
-   from any component of any window.
-
-   Type safety is ensured by safe JSON encoders and decoders, however, they
-   might fail at runtime, causing settings value to be initialized to their
-   fallback and not to be saved nor synchronized.
-   This is not harmful but annoying.
-
-   To mitigate this effect, each instance of a Settings class has its
-   own, private, unique symbol that we call its « role ». A given `dataKey`
-   shall always be used with the same « role » otherwise it is discarded,
-   and an error message is logged when in DEVEL mode.
- */
-export abstract class Settings<A> {
-
-  private static keyRoles = new Map<string, symbol>();
-
-  private readonly role: symbol;
-  protected readonly decoder: JSON.Safe<A>;
-  protected readonly encoder: JSON.Encoder<A>;
-
-  /**
-     Encoders shall be protected against exception.
-     Use [[dome/data/json.jTry]] and [[dome/data/json.jCatch]] in case of uncertainty.
-     Decoders are automatically protected internally to the Settings class.
-     @param role Debugging name of instance roles (each instance has its unique
-     role, though)
-     @param decoder JSON decoder for the setting values
-     @param encoder JSON encoder for the setting values
-     @param fallback If provided, used to automatically protect your encoders
-     against exceptions.
-   */
-  constructor(
-    role: string,
-    decoder: JSON.Safe<A>,
-    encoder: JSON.Encoder<A>,
-    fallback?: A,
-  ) {
-    this.role = Symbol(role);
-    this.encoder = encoder;
-    this.decoder =
-      fallback !== undefined ? JSON.jCatch(decoder, fallback) : decoder;
-  }
-
-  /**
-     Returns identity if the data key is only
-     used with the same setting instance.
-     Otherwise, returns `undefined`.
-   */
-  validateKey(dataKey?: string): string | undefined {
-    if (dataKey === undefined) return undefined;
-    const rq = this.role;
-    const rk = Settings.keyRoles.get(dataKey);
-    if (rk === undefined) {
-      Settings.keyRoles.set(dataKey, rq);
-    } else {
-      if (rk !== rq) {
-        if (DEVEL) console.error(
-          `[Dome.settings] Key ${dataKey} used with incompatible roles`, rk, rq,
-        );
-        return undefined;
-      }
-    }
-    return dataKey;
-  }
-
-  /** @internal */
-  abstract loadData(key: string): JSON.json;
-
-  /** @internal */
-  abstract saveData(key: string, data: JSON.json): void;
-
-  /** @internal */
-  abstract event: string;
-
-  /** Returns the current setting value for the provided data key. You shall
-      only use validated keys otherwise you might fallback to default values. */
-  loadValue(dataKey?: string) {
-    return this.decoder(dataKey ? this.loadData(dataKey) : undefined)
-  }
-
-  /** Push the new setting value for the provided data key.
-      You shall only use validated keys otherwise further loads
-      might fail and fallback to defaults. */
-  saveValue(dataKey: string, value: A) {
-    try { this.saveData(dataKey, this.encoder(value)); }
-    catch (err) {
-      if (DEVEL) console.error(
-        '[Dome.settings] Error while encoding value',
-        dataKey, value, err,
-      );
-    }
-  }
-
-}
-
-/**
-   Generic React Hook to be used with any kind of [[Settings]].
-   You may share `dataKey` between components, or change it dynamically.
-   However, a given data key shall always be used for the same Setting instance.
-   See [[Settings]] documentation for details.
-   @param S The instance settings to be used.
-   @param dataKey Identifies which value in the settings to be used.
- */
-export function useSettings<A>(
-  S: Settings<A>,
-  dataKey?: string,
-): [A, (update: A) => void] {
-
-  const theKey = React.useMemo(() => S.validateKey(dataKey), [S, dataKey]);
-  const [value, setValue] = React.useState<A>(() => S.loadValue(theKey));
-
-  React.useEffect(() => {
-    if (theKey) {
-      const callback = () => setValue(S.loadValue(theKey));
-      Dome.on(S.event, callback);
-      return () => Dome.off(S.event, callback);
-    }
-    return undefined;
-  });
-
-  const updateValue = React.useCallback((update: A) => {
-    if (!isEqual(value, update)) {
-      setValue(update);
-      if (theKey) S.saveValue(theKey, update);
-    }
-  }, [S, theKey]);
-
-  return [value, updateValue];
-
-}
-
-/** Window Settings for non-JSON data.
-    In most situations, you can use [[WindowSettings]] instead.
-    You can use a [[dome/data/json.Loose]] decoder for optional values. */
-export class WindowSettingsData<A> extends Settings<A> {
-
-  constructor(
-    role: string,
-    decoder: JSON.Safe<A>,
-    encoder: JSON.Encoder<A>,
-    fallback?: A,
-  ) {
-    super(role, decoder, encoder, fallback);
-  }
-
-  event = 'dome.defaults';
-  loadData(key: string) { return Dome.getWindowSetting(key) as JSON.json; }
-  saveData(key: string, data: JSON.json) { Dome.setWindowSetting(key, data); }
-
-}
-
-/** Global Settings for non-JSON data.
-    In most situations, you can use [[WindowSettings]] instead.
-    You can use a [[dome/data/json.Loose]] decoder for optional values. */
-export class GlobalSettingsData<A> extends Settings<A> {
-
-  constructor(
-    role: string,
-    decoder: JSON.Safe<A>,
-    encoder: JSON.Encoder<A>,
-    fallback?: A,
-  ) {
-    super(role, decoder, encoder, fallback);
-  }
-
-  event = 'dome.settings';
-  loadData(key: string) { return Dome.getGlobalSetting(key) as JSON.json; }
-  saveData(key: string, data: JSON.json) { Dome.setGlobalSetting(key, data); }
-
-}
-
-/** Window Settings.
-    For non-JSON data, use [[WindowSettingsData]] instead.
-    You can use a [[dome/data/json.Loose]] decoder for optional values. */
-export class WindowSettings<A extends JSON.json> extends WindowSettingsData<A> {
-
-  constructor(role: string, decoder: JSON.Safe<A>, fallback?: A) {
-    super(role, decoder, JSON.identity, fallback);
-  }
-
-}
-
-/** Global Settings.
-    For non-JSON data, use [[WindowSettingsData]] instead.
-    You can use a [[dome/data/json.Loose]] decoder for optional values. */
-export class GlobalSettings<A extends JSON.json> extends GlobalSettingsData<A> {
-
-  constructor(role: string, decoder: JSON.Safe<A>, fallback?: A) {
-    super(role, decoder, JSON.identity, fallback);
-  }
-
 }
 
 // --------------------------------------------------------------------------
diff --git a/ivette/src/dome/src/renderer/dialogs.js b/ivette/src/dome/src/renderer/dialogs.js
deleted file mode 100644
index 1532f867e03d3d77374951c3e39839fb4c39f349..0000000000000000000000000000000000000000
--- a/ivette/src/dome/src/renderer/dialogs.js
+++ /dev/null
@@ -1,307 +0,0 @@
-/**
-   @packageDocumentation
-   @module dome/dialogs
-   @description
-   Various kind of (modal) dialogs attached to the main application window.
- */
-
-import filepath from 'path' ;
-import { remote } from 'electron' ;
-import * as System from 'dome/system' ;
-
-// --------------------------------------------------------------------------
-// --- Message Box
-// --------------------------------------------------------------------------
-
-const defaultItems = [
-  { value:undefined },
-  { value:true, label:'Ok' }
-];
-
-const valueLabel = (v) => {
-  switch(v) {
-  case undefined: return 'Cancel' ;
-  case true: return 'Ok' ;
-  case false: return 'No' ;
-  default: return ''+v ;
-  }
-};
-
-const itemLabel = ({value,label}) => label || valueLabel(value) ;
-const isDefault = ({value,label}) => value===true || label==='Ok' || label==='Yes' ;
-const isCancel = ({value,label}) => !value || label==='Cancel' || label==='No' ;
-
-/**
-   @summary Show a configurable message box.
-   @parameter {object} options - configuration (see above)
-   @return {Promise} the selected option (see above)
-   @description
-The available fields and options for configuring the dialog are:
-
-| Options | Type | Description |
-|:--------|:----:|:------------|
-| `kind` | `'none','info','error','warning'` | Icon of the message box |
-| `title` | `string` (_opt._) | Heading of message box |
-| `message` | `string` | Message text |
-| `buttons` | `button[]` (_opt._) | Dialog buttons |
-| `defaultValue` | (any) (_opt._) | Value of the default button |
-| `cancelValue` | (any) (_opt._) | Value of the cancel key |
-
-The dialog buttons are specified by objects with the following fields:
-
-| Button Field | Type | Description |
-|:-------------|:----:|:------------|
-| `label` | `string` | Button label |
-| `value` | (any) | Button identifier (items only) |
-
-The returned promise object is never rejected, and is asynchronously
-resolved into:
-- the cancel value if the cancel key is pressed,
-- the default value if the enter key is pressed,
-- or the value of the clicked button otherwised.
-
-The default buttons are `"Ok"` and `"Cancel"` associated to values `true` and
-`undefined`, which are also associated to the enter and cancel keys.
-Unless specified, the default value is associated with the first button
-with 'true' value or 'Ok' or 'Yes' label,
-and the cancel value is the first button with a falsy value or 'Cancel'
-or 'No' label.
-*/
-export function showMessageBox( options )
-{
-  const {
-    kind,
-    title,
-    message,
-    defaultValue,
-    cancelValue,
-      buttons = defaultItems
-  } = options ;
-
-  const labels = buttons.map(itemLabel);
-  let defaultId =
-      defaultValue === undefined
-      ? buttons.findIndex(isDefault)
-      : buttons.findIndex((a) => a.value === defaultValue);
-  let cancelId =
-      cancelValue === undefined
-      ? buttons.findIndex(isCancel)
-      : buttons.findIndex((a) => a.value === cancelValue);
-
-  if (cancelId === defaultId) cancelId = -1;
-
-  return remote.dialog.showMessageBox(
-    remote.getCurrentWindow(),
-    {
-      type:kind,
-      message: message && title,
-      detail:  message || title,
-      defaultId, cancelId, buttons: labels
-    }
-  ).then((result) => {
-    let itemIndex = result ? result.response : -1 ;
-    return itemIndex ? buttons[itemIndex].value : cancelValue ;
-  });
-}
-
-// --------------------------------------------------------------------------
-// --- openFile dialog
-// --------------------------------------------------------------------------
-
-const defaultPath = (path) => filepath.extname(path) ? filepath.dirname(path) : path ;
-
-/**
-   @summary Show a dialog for opening file.
-   @parameter {object} options - configuration (see above)
-   @return {Promise} the selected file (see above)
-   @description
-The available fields and options for configuring the dialog are:
-
-| Options | Type | Description |
-|:--------|:----:|:------------|
-| `message` | `string` (_opt._) | Prompt message |
-| `label` | `string` (_opt._) | Open button label |
-| `path` | `filepath` (_opt._) | Initially selected path |
-| `hidden` | `boolean` (_opt._) | Show hidden files (not by default) |
-| `filters` | `filter[]` (_opt._) | File filters (all files by default) |
-
-The file filters are object with the following fields:
-
-| Filter Field | Type | Description |
-|:-------------|:----:|:------------|
-| `name` | `string` | Filter name |
-| `extensions` | `string[]` | Allowed file extensions, _without_ dots («.») |
-
-A file filter with `extensions:["*"]` would accept any file extension.
-
-The returned promise object will be asynchronously:
-- either _resolved_ with `undefined` if no file has been selected,
-- or _resolved_ with the selected path
-
-The promise is never rejected.
-
-*/
-export function showOpenFile( options )
-{
-  const { message, label, path, hidden, filters } = options ;
-  const properties = [ 'openFile' ];
-  if (hidden) properties.push('showHiddenFiles');
-
-  return remote.dialog.showOpenDialog(
-    remote.getCurrentWindow(),
-    {
-      message, buttonLabel: label,
-      defaultPath: path && defaultPath(path),
-      properties, filters
-    }
-  ).then(result => {
-    if (!result.canceled && result.filePaths && result.filePaths.length > 0)
-      return result.filePaths[0] ;
-    else
-      return undefined ;
-  });
-}
-
-/**
-   @summary Show a dialog for opening file.
-   @parameter {object} options - configuration (see above)
-   @return {Promise} the selected file(s) (see above)
-   @description
-The available fields and options for configuring the dialog are:
-
-| Options | Type | Description |
-|:--------|:----:|:------------|
-| `message` | `string` (_opt._) | Prompt message |
-| `label` | `string` (_opt._) | Open button label |
-| `path` | `filepath` (_opt._) | Initially selected path |
-| `hidden` | `boolean` (_opt._) | Show hidden files (not by default) |
-| `filters` | `filter[]` (_opt._) | File filters (all files by default) |
-
-The file filters are object with the following fields:
-
-| Filter Field | Type | Description |
-|:-------------|:----:|:------------|
-| `name` | `string` | Filter name |
-| `extensions` | `string[]` | Allowed file extensions, _without_ dots («.») |
-
-A file filter with `extensions:["*"]` would accept any file extension.
-
-The returned promise object will be asynchronously:
-- either _resolved_ with `undefined` if no file has been selected,
-- or _resolved_ with the selected paths
-
-The promise is never rejected.
-
-*/
-export function showOpenFiles( options )
-{
-  const { message, label, path, hidden, filters } = options ;
-  const properties = [ 'openFile', 'multiSelections' ];
-  if (hidden) properties.push('showHiddenFiles');
-
-  return remote.dialog.showOpenDialog(
-    remote.getCurrentWindow(),
-    {
-      message, buttonLabel: label,
-      defaultPath: path && defaultPath(path),
-      properties, filters
-    }
-  ).then(result => {
-    if (!result.canceled && result.filePaths && result.filePaths.length > 0)
-      return result.filePaths ;
-    else
-      return undefined ;
-  });
-}
-
-
-// --------------------------------------------------------------------------
-// --- saveFile dialog
-// --------------------------------------------------------------------------
-
-/**
-   @summary Show a dialog for saving file.
-   @parameter {object} options - configuration (see above)
-   @return {Promise} the selected path (see above)
-   @description
-The available fields and options for configuring the dialog are:
-
-| Options | Type | Description |
-|:--------|:----:|:------------|
-| `message` | `string` (_opt._) | Prompt message |
-| `label` | `string` (_opt._) | Save button label |
-| `path` | `filepath` (_opt._) | Initially selected path |
-| `filters` | `filter[]` (_opt._) | Cf. `openFileDialog` |
-
-The returned promise object will be asynchronously:
-- either _resolved_ with `undefined` if no file has been selected,
-- or _resolved_ with the selected (single) path.
-
-The promise is never rejected.
-
-*/
-export function showSaveFile( options )
-{
-  const { message, label, path, filters } = options ;
-
-  return remote.dialog.showSaveDialog(
-    remote.getCurrentWindow(),
-    {
-      message, buttonLabel: label,
-      defaultPath: path,
-      filters
-    }
-  );
-}
-
-// --------------------------------------------------------------------------
-// --- openDir dialog
-// --------------------------------------------------------------------------
-
-/**
-   @summary Show a dialog for selecting directories.
-   @parameter {object} options - configuration (see above)
-   @return {Promise} the selected directories (see above)
-   @description
-The available fields and options for configuring the dialog are:
-
-| Options | Type | Description |
-|:--------|:----:|:------------|
-| `message` | `string` (_opt._) | Prompt message |
-| `label` | `string` (_opt._) | Open button label |
-| `path` | `filepath` (_opt._) | Initially selected path |
-| `hidden` | `boolean` (_opt._) | Show hidden files (not by default) |
-
-The returned promise object will be asynchronously:
-- either _resolved_ with `undefined` if no file has been selected,
-- or _resolved_ with the selected path
-
-*/
-export function showOpenDir( options )
-{
-  const { message, label, path, hidden } = options ;
-  const properties = [ 'openDirectory' ];
-  if (hidden) properties.push('showHiddenFiles');
-
-  switch(System.platform) {
-    case 'macos':   properties.push( 'createDirectory' ); break;
-  case 'windows': properties.push( 'promptToCreate' ); break;
-  }
-
-  return remote.dialog.showOpenDialog(
-    remote.getCurrentWindow(),
-    {
-      message,
-      buttonLabel: label,
-      defaultPath: path,
-      properties
-    }
-  ).then(result => {
-    if (!result.canceled && result.filePaths && result.filePaths.length > 0)
-      return result.filePaths[0] ;
-    else
-      return undefined ;
-  });
-}
-
-// --------------------------------------------------------------------------
diff --git a/ivette/src/dome/src/renderer/dialogs.tsx b/ivette/src/dome/src/renderer/dialogs.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e5bc3fa9f1d88b946358fdaff1577acb0c937a67
--- /dev/null
+++ b/ivette/src/dome/src/renderer/dialogs.tsx
@@ -0,0 +1,288 @@
+/**
+   Various kind of (modal) dialogs attached to the main application window.
+   @packageDocumentation
+   @module dome/dialogs
+ */
+
+import filepath from 'path';
+import { remote } from 'electron';
+import * as System from 'dome/system';
+
+// --------------------------------------------------------------------------
+// --- Message Box
+// --------------------------------------------------------------------------
+
+export interface DialogButton<A> {
+  label?: string;
+  value?: A;
+}
+
+const defaultItems: DialogButton<boolean>[] = [
+  { value: undefined },
+  { value: true, label: 'Ok' },
+];
+
+const valueLabel = (v: any) => {
+  switch (v) {
+    case undefined: return 'Cancel';
+    case true: return 'Ok';
+    case false: return 'No';
+    default: return `${v}`;
+  }
+};
+
+const itemLabel = ({ value, label }: DialogButton<any>) => (
+  (label || valueLabel(value))
+);
+
+const isDefault = ({ value, label }: DialogButton<any>) => (
+  (value === true || label === 'Ok' || label === 'Yes')
+);
+
+const isCancel = ({ value, label }: DialogButton<any>) => (
+  (!value || label === 'Cancel' || label === 'No')
+);
+
+export type MessageKind = 'none' | 'info' | 'error' | 'warning';
+
+export interface MessageProps<A> {
+  /** Dialog window icon (default is `'none'`. */
+  kind?: MessageKind;
+  /** Message text (short sentence). */
+  message: string;
+  /** Message details (short sentence). */
+  details?: string;
+  /** Message buttons. */
+  buttons?: DialogButton<A>[];
+  /** Default button's value. */
+  defaultValue?: A;
+  /** Cancel value. */
+  cancelValue?: A;
+}
+
+/**
+   Show a configurable message box.
+
+   The returned promise object is never rejected, and is asynchronously
+   resolved into:
+   - the cancel value if the cancel key is pressed,
+   - the default value if the enter key is pressed,
+   - or the value of the clicked button otherwised.
+
+   The default buttons are `"Ok"` and `"Cancel"` associated to values `true` and
+   `undefined`, which are also associated to the enter and cancel keys.
+   Unless specified, the default value is associated with the first button
+   with 'true' value or 'Ok' or 'Yes' label,
+   and the cancel value is the first button with a falsy value or 'Cancel'
+   or 'No' label.
+ */
+export async function showMessageBox<A>(
+  props: MessageProps<A>,
+): Promise<A | boolean | undefined> {
+  const {
+    kind,
+    message,
+    details,
+    defaultValue,
+    cancelValue,
+    buttons = (defaultItems as DialogButton<A | boolean>[]),
+  } = props;
+
+  const labels = buttons.map(itemLabel);
+  const defaultId =
+    defaultValue === undefined
+      ? buttons.findIndex(isDefault)
+      : buttons.findIndex((a) => a.value === defaultValue);
+  let cancelId =
+    cancelValue === undefined
+      ? buttons.findIndex(isCancel)
+      : buttons.findIndex((a) => a.value === cancelValue);
+
+  if (cancelId === defaultId) cancelId = -1;
+
+  return remote.dialog.showMessageBox(
+    remote.getCurrentWindow(),
+    {
+      type: kind,
+      message,
+      detail: details,
+      defaultId,
+      cancelId,
+      buttons: labels,
+    },
+  ).then((result) => {
+    const itemIndex = result ? result.response : -1;
+    return itemIndex ? buttons[itemIndex].value : cancelValue;
+  });
+}
+
+// --------------------------------------------------------------------------
+// --- File Dialogs
+// --------------------------------------------------------------------------
+
+const defaultPath =
+  (path: string) => (filepath.extname(path) ? filepath.dirname(path) : path);
+
+export interface FileFilter {
+  /** Filter name. */
+  name: string;
+  /**
+     Allowed extensions, _without_ dot.
+     Use `['*']` to accept all files.
+   */
+  extensions: string[];
+}
+
+export interface FileDialogProps {
+  /** Prompt message. */
+  message?: string;
+  /** Open button label (default is « Open »). */
+  label?: string;
+  /** Initially selected path. */
+  path?: string;
+}
+
+export interface SaveFileProps extends FileDialogProps {
+  /** File filters (default to all). */
+  filters?: FileFilter[];
+}
+
+export interface OpenFileProps extends SaveFileProps {
+  /** Show hidden files (default is `false`). */
+  hidden?: boolean;
+}
+
+export interface OpenDirProps extends FileDialogProps {
+  /** Show hidden directories (default is `false`). */
+  hidden?: boolean;
+}
+
+// --------------------------------------------------------------------------
+// --- openFile dialog
+// --------------------------------------------------------------------------
+
+/**
+   Show a dialog for opening file.
+   A file filter with `extensions:["*"]` would accept any file extension.
+
+   The returned promise object will be asynchronously:
+   - either _resolved_ with `undefined` if no file has been selected,
+   - or _resolved_ with the selected path
+
+   The promise is never rejected.
+ */
+export async function showOpenFile(
+  props: OpenFileProps,
+): Promise<string | undefined> {
+  const { message, label, path, hidden = false, filters } = props;
+  return remote.dialog.showOpenDialog(
+    remote.getCurrentWindow(),
+    {
+      message,
+      buttonLabel: label,
+      defaultPath: path && defaultPath(path),
+      properties: (hidden ? ['openFile', 'showHiddenFiles'] : ['openFile']),
+      filters,
+    },
+  ).then((result) => {
+    if (!result.canceled && result.filePaths && result.filePaths.length > 0)
+      return result.filePaths[0];
+    return undefined;
+  });
+}
+
+/**
+   Show a dialog for opening files multiple files.
+*/
+export async function showOpenFiles(
+  props: OpenFileProps,
+): Promise<string[] | undefined> {
+  const { message, label, path, hidden, filters } = props;
+
+  return remote.dialog.showOpenDialog(
+    remote.getCurrentWindow(),
+    {
+      message,
+      buttonLabel: label,
+      defaultPath: path && defaultPath(path),
+      properties: (
+        hidden
+          ? ['openFile', 'multiSelections', 'showHiddenFiles']
+          : ['openFile', 'multiSelections']
+      ),
+      filters,
+    },
+  ).then((result) => {
+    if (!result.canceled && result.filePaths && result.filePaths.length > 0)
+      return result.filePaths;
+    return undefined;
+  });
+}
+
+// --------------------------------------------------------------------------
+// --- saveFile dialog
+// --------------------------------------------------------------------------
+
+/**
+   Show a dialog for saving file.
+
+   The returned promise object will be asynchronously:
+   - either _resolved_ with `undefined` when canceled,
+   - or _resolved_ with the selected (single) path.
+
+   The promise is never rejected.
+*/
+export async function showSaveFile(
+  props: SaveFileProps,
+): Promise<string | undefined> {
+  const { message, label, path, filters } = props;
+  return remote.dialog.showSaveDialog(
+    remote.getCurrentWindow(),
+    {
+      message,
+      buttonLabel: label,
+      defaultPath: path,
+      filters,
+    },
+  ).then(({ canceled, filePath }) => (canceled ? undefined : filePath));
+}
+
+// --------------------------------------------------------------------------
+// --- openDir dialog
+// --------------------------------------------------------------------------
+
+type openDirProperty =
+  'openDirectory' | 'showHiddenFiles' | 'createDirectory' | 'promptToCreate';
+
+/**
+   Show a dialog for selecting directories.
+ */
+export async function showOpenDir(
+  props: OpenDirProps,
+): Promise<string | undefined> {
+  const { message, label, path, hidden } = props;
+  const properties: openDirProperty[] = ['openDirectory'];
+  if (hidden) properties.push('showHiddenFiles');
+
+  switch (System.platform) {
+    case 'macos': properties.push('createDirectory'); break;
+    case 'windows': properties.push('promptToCreate'); break;
+    default: break;
+  }
+
+  return remote.dialog.showOpenDialog(
+    remote.getCurrentWindow(),
+    {
+      message,
+      buttonLabel: label,
+      defaultPath: path,
+      properties,
+    },
+  ).then((result) => {
+    if (!result.canceled && result.filePaths && result.filePaths.length > 0)
+      return result.filePaths[0];
+    return undefined;
+  });
+}
+
+// --------------------------------------------------------------------------
diff --git a/ivette/src/dome/src/renderer/errors.js b/ivette/src/dome/src/renderer/errors.js
deleted file mode 100644
index ae951aa7ee0fe87c9be98dd687e9d0e769b63fa8..0000000000000000000000000000000000000000
--- a/ivette/src/dome/src/renderer/errors.js
+++ /dev/null
@@ -1,78 +0,0 @@
-// --------------------------------------------------------------------------
-// --- Managing Errors
-// --------------------------------------------------------------------------
-
-/**
-   @packageDocumentation
-   @module dome/errors
-*/
-
-import React from 'react' ;
-import { Label } from 'dome/controls/labels' ;
-import { Button } from 'dome/controls/buttons' ;
-
-// --------------------------------------------------------------------------
-// --- Error Boundaries
-// --------------------------------------------------------------------------
-
-/**
-   @summary React Error Boundaries.
-   @property {string} [label] - Default error box label
-   @property {function} [onError] - Alternative renderer
-   @description
-   Install an error boundary. In case of error, the default
-   rendering is a warning button that output on console the
-   catched error.
-
-   An alternative rendering can be supplied
-   with `onError:(error,info) => React.Element`.
-
- */
-export class Catch extends React.Component
-{
-
-  constructor(props) {
-    super(props);
-    this.state = { };
-    this.logerr = this.logerr.bind(this);
-    this.reload = this.reload.bind(this);
-  }
-
-  dumpError(error,info) {
-  }
-
-  componentDidCatch(error, info) {
-    this.setState({ error, info });
-  }
-
-  logerr() {
-    const { error, info } = this.state ;
-    console.error('[dome] Catched error:',error,info);
-  }
-
-  reload() {
-    this.setState({ error: undefined, info: undefined });
-  }
-
-  render() {
-    const { error, info } = this.state ;
-    if (error) {
-      const { onError, label='Error' } = this.props ;
-      if (typeof(onError)==='function')
-        return onError(error,info,this.reload);
-      else
-        return (
-          <div>
-            <Button icon='WARNING' kind='warning'
-                    title={error}
-                    onClick={this.logerr} />
-            <Button icon='RELOAD' onClick={this.reload} />
-            <Label>{label}</Label>
-          </div>
-        );
-    }
-    return this.props.children || null ;
-  }
-}
-
-// --------------------------------------------------------------------------
diff --git a/ivette/src/dome/src/renderer/errors.tsx b/ivette/src/dome/src/renderer/errors.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b8cfc300071c79342ab28a8912b3296e5b3c1f11
--- /dev/null
+++ b/ivette/src/dome/src/renderer/errors.tsx
@@ -0,0 +1,86 @@
+// --------------------------------------------------------------------------
+// --- Managing Errors
+// --------------------------------------------------------------------------
+
+/**
+   @packageDocumentation
+   @module dome/errors
+*/
+
+import React from 'react';
+import { Label } from 'dome/controls/labels';
+import { Button } from 'dome/controls/buttons';
+
+// --------------------------------------------------------------------------
+// --- Error Boundaries
+// --------------------------------------------------------------------------
+
+/**
+   Alternative renderer in case of error.
+   @param reload - callback for re-rendering the faulty component
+ */
+export interface ErrorRenderer {
+  (error: any, info: any, reload: () => void): JSX.Element;
+}
+
+export interface CatchProps {
+  /** Name of the error boundary. */
+  label?: string;
+  /** Alternative renderer callback in case of errors. */
+  onError?: JSX.Element | ErrorRenderer;
+}
+
+interface CatchState {
+  error?: any;
+  info?: any;
+}
+
+/**
+   React Error Boundaries.
+ */
+export class Catch extends React.Component<CatchProps, CatchState, {}> {
+
+  constructor(props: CatchProps) {
+    super(props);
+    this.state = {};
+    this.logerr = this.logerr.bind(this);
+    this.reload = this.reload.bind(this);
+  }
+
+  componentDidCatch(error: any, info: any) {
+    this.setState({ error, info });
+  }
+
+  logerr() {
+    const { error, info } = this.state;
+    console.error('[dome] Catched error:', error, info);
+  }
+
+  reload() {
+    this.setState({ error: undefined, info: undefined });
+  }
+
+  render() {
+    const { error, info } = this.state;
+    if (error) {
+      const { onError, label = 'Error' } = this.props;
+      if (typeof (onError) === 'function')
+        return onError(error, info, this.reload);
+      return (
+        <div>
+          <Button
+            icon="WARNING"
+            kind="warning"
+            title={error}
+            onClick={this.logerr}
+          />
+          <Button icon="RELOAD" onClick={this.reload} />
+          <Label>{label}</Label>
+        </div>
+      );
+    }
+    return this.props.children || null;
+  }
+}
+
+// --------------------------------------------------------------------------
diff --git a/ivette/src/dome/src/renderer/layout/boxes.js b/ivette/src/dome/src/renderer/layout/boxes.js
deleted file mode 100644
index e7848a9ddcc671c3b9c0dfeb80a733708875357d..0000000000000000000000000000000000000000
--- a/ivette/src/dome/src/renderer/layout/boxes.js
+++ /dev/null
@@ -1,184 +0,0 @@
-// --------------------------------------------------------------------------
-// --- Box Layout
-// --------------------------------------------------------------------------
-
-/**
-   @packageDocumentation
-   @module dome/layout/boxes
-   @description
-
-   This modules offers several `<div>` containers with various
-   predefined layout.
-
-   Boxes are the very elementary way to layout components horizontally
-   or vertically. The different kinds of proposed boxes differ in how they
-   extends in both directions: normal boxes extends
-   along their layout direction, _pack_ boxes don't extends and _fill_ boxes
-   extends along both directions.
-
-   Grids layout their component from left-to-right inside predefined _columns_,
-   then vertically by wrapping cells in rows.
-
-   The various containers layout and extensibility is listed below:
-    - [[Hbox]] horizontal, fixed height
-    - [[Vbox]] vertical, fixed width
-    - [[Hpack]] horizontal, fixed dimensions
-    - [[Vpack]] vertical, fixed dimensions
-    - [[Hfill]] horizontal, extends in both directions
-    - [[Vfill]] vertical, extends in both directions
-    - [[Grid]] uses CSS grid columns, extends in both directions
-    - [[Scroll]] scrolls its content
-
-   Inside a box, you may add `<Space/>` and `<Filler/>` to separate items.
-   Inside a grid, you may also use `<Space/>` or an empty `<div/>` for empty cells.
-
-   <strong>Warning:</strong> large elements will be clipped if they overflow.
-   If you want to add scrolling capabilities to some item that does not manage overflow
-   natively, place it inside a `<Scroll/>` sub-container.
-*/
-
-import React from 'react';
-import * as Dome from 'dome';
-import { Title } from 'dome/controls/labels' ;
-import './style.css' ;
-
-// --------------------------------------------------------------------------
-// --- Generic Box
-// --------------------------------------------------------------------------
-
-const makeBox = ( boxClasses, props, morestyle ) => {
-  const { children, className, style, ...others } = props ;
-  const allClasses = className ? boxClasses + ' ' + className : boxClasses ;
-  const allStyles = morestyle ? (style ? Object.assign( {}, style, morestyle ) : morestyle) : style ;
-  return (
-    <div className={allClasses} style={allStyles} {...others} >
-      {children}
-    </div>
-  );
-};
-
-// --------------------------------------------------------------------------
-// --- Predefined Defaults
-// --------------------------------------------------------------------------
-
-/**
-   @summary Horizontal box (extends horizontally, no overflow).
-   @property {object} [...props] - Extra properties passed to the `<div>` container
-   @description
-   <strong>Warning:</strong> large elements will be clipped if they overflow.
-*/
-export const Hbox = (props) => makeBox( 'dome-xBoxes-hbox dome-xBoxes-box' , props );
-
-/**
-   @summary Vertical box (extends vertically, no overflow).
-   @property {object} [...props] - Extra properties passed to the `<div>` container
-   @description
-   <strong>Warning:</strong> large elements will be clipped if they overflow.
-*/
-export const Vbox = (props) => makeBox( 'dome-xBoxes-vbox dome-xBoxes-box' , props );
-
-/**
-   @summary Compact Horizontal box (fixed dimensions, no overflow).
-   @property {object} [...props] - Extra properties passed to the `<div>` container
-   @description
-   <strong>Warning:</strong> large elements would be clipped if they overflow.
-*/
-export const Hpack = (props) => makeBox( 'dome-xBoxes-hbox dome-xBoxes-pack' , props );
-
-/**
-   @summary Compact Vertical box (fixed dimensions, no overflow).
-   @property {object} [...props] - Extra properties passed to the `<div>` container
-   @description
-   <strong>Warning:</strong> large elements will be clipped if they overflow.
-*/
-export const Vpack = (props) => makeBox( 'dome-xBoxes-vbox dome-xBoxes-pack' , props );
-
-/**
-   @summary Horizontally filled box (fixed height, maximal width, no overflow).
-   @property {object} [...props] - Extra properties passed to the `<div>` container
-   @description
-   <strong>Warning:</strong> large elements will be clipped if they overflow.
-*/
-export const Hfill = (props) => makeBox( 'dome-xBoxes-hbox dome-xBoxes-fill' , props );
-
-/**
-   @summary Vertically filled box (fixed width, maximal height, no overflow).
-   @property {object} [...props] - Extra properties passed to the `<div>` container
-   @description
-   <strong>Warning:</strong> large elements will be clipped if they overflow.
-*/
-export const Vfill = (props) => makeBox( 'dome-xBoxes-vbox dome-xBoxes-fill' , props );
-
-// --------------------------------------------------------------------------
-// --- Scrolling & Spacing
-// --------------------------------------------------------------------------
-
-/**
-   @summary Scrolling container.
-   @property {object} [...props] - Extra properties passed to the `<div>` container
-*/
-export const Scroll = (props) => makeBox( 'dome-xBoxes-scroll dome-container' , props );
-
-/**
-   @summary Rigid space between items in a box.
-   @property {object} [...props] - Extra properties passed to the `<div>` separator
-*/
-export const Space = (props) => makeBox( 'dome-xBoxes-space' , props );
-
-/**
-   @summary Extensible space between items in a box.
-   @property {object} [...props] - Extra properties passed to the `<div>` separator
-*/
-export const Filler = (props) => makeBox( 'dome-xBoxes-filler' , props );
-
-// --------------------------------------------------------------------------
-// --- Grids
-// --------------------------------------------------------------------------
-
-/**
-   @summary Grid box container.
-   @property {string} [columns] - Grid column specifications
-   @property {object} [...props] - Extra properties passed to the `<div>` container
-   @description
-   Layout its children in a multi-column grid. Each column is specified by its
-   width, following the CSS Grid Layout `grid-template-columns` property.
-
-   The rows are populated with children from left-to-right, using one column at a time.
-   Items layout can be modified by adding CSS Grid Layout item properties.
-
-   Example: `<Grid columns="25% auto auto"> ... </Grid>`
-*/
-export const Grid = ({columns='auto',...props}) =>
-  makeBox( 'dome-xBoxes-grid', props , { gridTemplateColumns: columns });
-
-// --------------------------------------------------------------------------
-// --- Folders
-// --------------------------------------------------------------------------
-
-/**
-   @summary Foldable Vpack box.
-   @property {string} label - box label
-   @property {string} [title] - box label tooltip
-   @property {string} [settings] - window setting to store the fold/unfold state
-   @property {boolean} [defaultUnfold] - initial state (default is `false`)
-   @property {React.Children} [children] - content of the vertical box
-   @description
-   A vertical `Vpack` box with a clickable head label to fold/unfold its content.
-*/
-export const Folder =
-  ({ settings, defaultUnfold=false, indent=18, label, title, children }) =>
-{
-  const [ unfold , setUnfold ] = Dome.useState( settings, defaultUnfold );
-  const icon = unfold ? 'TRIANGLE.DOWN' : 'TRIANGLE.RIGHT' ;
-  const onClick = () => setUnfold( !unfold );
-  return (
-    <Vpack>
-      <Hpack onClick={onClick}><Title icon={icon} label={label} title={title} /></Hpack>
-      <Vpack style={{ marginLeft:indent }}>
-        { unfold && children }
-      </Vpack>
-    </Vpack>
-  );
-};
-
-// --------------------------------------------------------------------------
diff --git a/ivette/src/dome/src/renderer/layout/boxes.tsx b/ivette/src/dome/src/renderer/layout/boxes.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..2d98e79fd1753cd726bf857c092db42f18cb9f52
--- /dev/null
+++ b/ivette/src/dome/src/renderer/layout/boxes.tsx
@@ -0,0 +1,198 @@
+// --------------------------------------------------------------------------
+// --- Box Layout
+// --------------------------------------------------------------------------
+
+/**
+   This modules offers several `<div>` containers with various
+   predefined layout.
+
+   Boxes are the very elementary way to layout components horizontally
+   or vertically. The different kinds of proposed boxes differ in how they
+   extends in both directions: normal boxes extends
+   along their layout direction, _pack_ boxes don't extends and _fill_ boxes
+   extends along both directions.
+
+   Grids layout their component from left-to-right inside predefined _columns_,
+   then vertically by wrapping cells in rows.
+
+   The various containers layout and extensibility is listed below:
+   - [[Hbox]] horizontal, fixed height
+   - [[Vbox]] vertical, fixed width
+   - [[Hpack]] horizontal, fixed dimensions
+   - [[Vpack]] vertical, fixed dimensions
+   - [[Hfill]] horizontal, extends in both directions
+   - [[Vfill]] vertical, extends in both directions
+   - [[Grid]] uses CSS grid columns, extends in both directions
+   - [[Scroll]] scrolls its content
+
+   Inside a box, you may add `<Space/>` and `<Filler/>` to separate items.
+   Inside a grid, you may also use `<Space/>` or an empty `<div/>` for empty
+   cells.
+
+   <strong>Warning:</strong> large elements will be clipped if they overflow.
+   If you want to add scrolling capabilities to some item that does not manage
+   overflow natively, place it inside a `<Scroll/>` sub-container.
+
+   @packageDocumentation
+   @module dome/layout/boxes
+ */
+
+import React from 'react';
+import * as Dome from 'dome';
+import { Title } from 'dome/controls/labels';
+import { classes, styles } from 'dome/misc/utils';
+import './style.css';
+
+// --------------------------------------------------------------------------
+// --- Generic Box
+// --------------------------------------------------------------------------
+
+/** Div properties that you can also use in boxes. */
+export type DivProps = React.HTMLAttributes<HTMLDivElement>;
+
+const makeBox = (
+  boxClasses: string,
+  props: DivProps,
+  morestyle?: React.CSSProperties,
+) => {
+  const { children, className, style, ...others } = props;
+  const allClasses = classes(className, boxClasses);
+  const allStyles = styles(style, morestyle);
+  return (
+    <div className={allClasses} style={allStyles} {...others}>
+      {children}
+    </div>
+  );
+};
+
+// --------------------------------------------------------------------------
+// --- Predefined Defaults
+// --------------------------------------------------------------------------
+
+/**
+   Horizontal box (extends horizontally, no overflow).
+*/
+export const Hbox =
+  (props: DivProps) => makeBox('dome-xBoxes-hbox dome-xBoxes-box', props);
+
+/**
+   Vertical box (extends vertically, no overflow).
+*/
+export const Vbox =
+  (props: DivProps) => makeBox('dome-xBoxes-vbox dome-xBoxes-box', props);
+
+/**
+   Compact Horizontal box (fixed dimensions, no overflow).
+*/
+export const Hpack =
+  (props: DivProps) => makeBox('dome-xBoxes-hbox dome-xBoxes-pack', props);
+
+/**
+   Compact Vertical box (fixed dimensions, no overflow).
+*/
+export const Vpack =
+  (props: DivProps) => makeBox('dome-xBoxes-vbox dome-xBoxes-pack', props);
+
+/**
+   Horizontally filled box (fixed height, maximal width, no overflow).
+*/
+export const Hfill =
+  (props: DivProps) => makeBox('dome-xBoxes-hbox dome-xBoxes-fill', props);
+
+/**
+   Vertically filled box (fixed width, maximal height, no overflow).
+*/
+export const Vfill =
+  (props: DivProps) => makeBox('dome-xBoxes-vbox dome-xBoxes-fill', props);
+
+// --------------------------------------------------------------------------
+// --- Scrolling & Spacing
+// --------------------------------------------------------------------------
+
+/**
+   Scrolling container.
+*/
+export const Scroll =
+  (props: DivProps) => makeBox('dome-xBoxes-scroll dome-container', props);
+
+/**
+   Rigid space between items in a box.
+*/
+export const Space =
+  (props: DivProps) => makeBox('dome-xBoxes-space', props);
+
+/**
+   Extensible space between items in a box.
+*/
+export const Filler =
+  (props: DivProps) => makeBox('dome-xBoxes-filler', props);
+
+// --------------------------------------------------------------------------
+// --- Grids
+// --------------------------------------------------------------------------
+
+export interface GridProps extends DivProps { columns?: string }
+
+/**
+   Grid box container.
+
+   Layout its children in a multi-column grid. Each column is specified by its
+   width, following the CSS Grid Layout `grid-template-columns` property.
+
+   The rows are populated with children from left-to-right, using one column at
+   a time.  Items layout can be modified by using CSS Grid Layout item
+   properties.
+
+   Example: `<Grid columns="25% auto auto"> ... </Grid>`
+*/
+export const Grid = (props: GridProps) => {
+  const { columns, ...others } = props;
+  return makeBox('dome-xBoxes-grid', others, { gridTemplateColumns: columns });
+};
+
+// --------------------------------------------------------------------------
+// --- Folders
+// --------------------------------------------------------------------------
+
+export interface FolderProps {
+  /** Title bar label. */
+  label: string;
+  /** Title bar tooltip. */
+  title?: string;
+  /** Window settings key. */
+  settings?: string;
+  /** Default state (`false`). */
+  defaultUnfold?: boolean;
+  /** Contents left margin (in pixels, defaults to 18). */
+  indent?: number;
+  /** Children JSX elements. */
+  children: any;
+}
+
+/**
+   Foldable (vertical, packed) box.
+   The head label is clickable to fold/unfold its contents.
+*/
+export const Folder = (props: FolderProps) => {
+  const {
+    settings,
+    defaultUnfold = false,
+    indent = 18,
+    label, title, children,
+  } = props;
+  const [unfold, onClick] = Dome.useSwitch(settings, defaultUnfold);
+  const icon = unfold ? 'TRIANGLE.DOWN' : 'TRIANGLE.RIGHT';
+  const display = unfold ? 'none' : 'block';
+  return (
+    <Vpack>
+      <Hpack onClick={onClick}>
+        <Title icon={icon} label={label} title={title} />
+      </Hpack>
+      <Vpack style={{ display, marginLeft: indent }}>
+        {children}
+      </Vpack>
+    </Vpack>
+  );
+};
+
+// --------------------------------------------------------------------------
diff --git a/ivette/src/dome/src/renderer/table/arrays.ts b/ivette/src/dome/src/renderer/table/arrays.ts
index e506d8237fc7e5ff1fdd635a9b0b630e761c5161..5abe5992daa086172bd98a05b63c30ec72c72908 100644
--- a/ivette/src/dome/src/renderer/table/arrays.ts
+++ b/ivette/src/dome/src/renderer/table/arrays.ts
@@ -10,7 +10,9 @@
 import * as Compare from 'dome/data/compare';
 import type { ByFields, Order } from 'dome/data/compare';
 import {
-  SortingInfo, Sorting, Filter, Filtering, Model, Collection, forEach
+  SortingInfo, Sorting,
+  Filter, Filtering,
+  Model, Collection, forEach,
 } from './models';
 
 // --------------------------------------------------------------------------
@@ -23,7 +25,7 @@ interface PACK<Key, Row> {
   index: number | undefined;
   key: Key;
   row: Row;
-};
+}
 
 type SORT<K, R> = Order<PACK<K, R>>;
 
@@ -76,8 +78,7 @@ export class ArrayModel<Key, Row>
   private array?: Row[];
 
   // Filtered-out Row Count
-  private filtered: number = 0;
-
+  private filtered = 0;
 
   // Filtering function
   private filter?: Filter<Key, Row>;
@@ -102,16 +103,18 @@ export class ArrayModel<Key, Row>
   protected sorter(): SORT<Key, Row> {
     let current = this.order;
     if (current) return current;
-    current = this.order = orderByRing(this.natural, this.columns, this.ring);
+    current = this.order = // eslint-disable-line no-multi-assign
+      orderByRing(this.natural, this.columns, this.ring);
     return current;
   }
 
-  // Lazily compute table
+  // Lazily compute table ; modifies packed entries in place
+  /* eslint-disable no-param-reassign */
   protected rebuild(): PACK<Key, Row>[] {
     const current = this.table;
     let filtered = 0;
     if (current !== undefined) return current;
-    let table: PACK<Key, Row>[] = [];
+    const table: PACK<Key, Row>[] = [];
     try {
       this.index.forEach((packed) => {
         packed.index = undefined;
@@ -125,11 +128,12 @@ export class ArrayModel<Key, Row>
     } catch (err) {
       console.warn('[Dome] error when rebuilding table:', err);
     }
-    table.forEach((pack, index) => pack.index = index);
+    table.forEach((packed, index) => { packed.index = index; });
     this.table = table;
     this.filtered = filtered;
     return table;
   }
+  /* eslint-enable no-param-reassign */
 
   // --------------------------------------------------------------------------
   // --- Proxy
@@ -191,8 +195,9 @@ export class ArrayModel<Key, Row>
   setOrderingByFields(byfields: ByFields<Row>) {
     this.natural = Compare.byFields(byfields);
     const columns = this.columns ?? {};
-    for (let k of Object.keys(byfields)) {
-      const dataKey = k as (string & keyof Row);
+    const keys = Object.keys(byfields);
+    for (let i = 0; i < keys.length; i++) {
+      const dataKey = keys[i] as (string & keyof Row);
       const fn = byfields[dataKey];
       if (fn) columns[dataKey] = (x: Row, y: Row) => {
         const dx = x[dataKey];
@@ -211,9 +216,9 @@ export class ArrayModel<Key, Row>
      Remove the sorting function for the provided column.
    */
   deleteColumnOrder(dataKey: string) {
-    const columns = this.columns;
+    const { columns } = this;
     if (columns) delete columns[dataKey];
-    this.ring = this.ring.filter(ord => ord.sortBy !== dataKey);
+    this.ring = this.ring.filter((ord) => ord.sortBy !== dataKey);
     this.reload();
   }
 
@@ -222,7 +227,7 @@ export class ArrayModel<Key, Row>
       Use `undefined` or `null` to reset the natural ordering. */
   setSorting(ord?: undefined | null | SortingInfo) {
     if (ord) {
-      const ring = this.ring;
+      const { ring } = this;
       const cur = ring[0];
       const fd = ord.sortBy;
       if (
@@ -235,11 +240,9 @@ export class ArrayModel<Key, Row>
         this.ring = newRing;
         this.reload();
       }
-    } else {
-      if (this.ring.length > 0) {
-        this.ring = [];
-        this.reload();
-      }
+    } else if (this.ring.length > 0) {
+      this.ring = [];
+      this.reload();
     }
   }
 
@@ -294,11 +297,11 @@ export class ArrayModel<Key, Row>
     const k = pack.index ?? -1;
     const n = current ? current.length : 0;
     const phi = this.filter;
-    const old_ok = 0 <= k && k < n;
-    const now_ok = phi ? phi(pack.row, pack.key) : true;
-    if (old_ok !== now_ok) return true;
+    const oldOk = 0 <= k && k < n;
+    const nowOk = phi ? phi(pack.row, pack.key) : true;
+    if (oldOk !== nowOk) return true;
     // Case where element was not displayed and will still not be
-    if (!old_ok) return false;
+    if (!oldOk) return false;
     // Detecting if ordering is preserved
     const order = this.sorter();
     const prev = k - 1;
@@ -360,11 +363,11 @@ export class ArrayModel<Key, Row>
       if (row === null) {
         // Nop
         return;
-      } else {
-        const newPack = { key, row, index: undefined };
-        this.index.set(key, newPack);
-        doReload = this.needReloadForInsert(newPack);
       }
+      const newPack = { key, row, index: undefined };
+      this.index.set(key, newPack);
+      doReload = this.needReloadForInsert(newPack);
+
     }
     if (doReload) this.reload();
   }
@@ -417,7 +420,8 @@ export class ArrayModel<Key, Row>
   getArray(): Row[] {
     let arr = this.array;
     if (arr === undefined) {
-      arr = this.array = this.rebuild().map((e) => e.row);
+      arr = this.array = // eslint-disable-line no-multi-assign
+        this.rebuild().map((e) => e.row);
     }
     return arr;
   }
diff --git a/ivette/src/dome/src/renderer/table/models.ts b/ivette/src/dome/src/renderer/table/models.ts
index 0717180890e3c193eb320ce0c90437a30a7d035d..76c516f2950beaa5843e3c07a68e09647a863700 100644
--- a/ivette/src/dome/src/renderer/table/models.ts
+++ b/ivette/src/dome/src/renderer/table/models.ts
@@ -100,7 +100,8 @@ export function forEach<A>(data: Collection<A>, fn: (elt: A) => void) {
    individual updates.
 
    The model might not hold the entire collection of data at the same time, but
-   serves as a proxy for fetching data on demand. A model makes a distinction between:
+   serves as a proxy for fetching data on demand. A model makes a distinction
+   between:
    - `Key`: a key identifies a given entry in the table at any time;
    - `Row`: the row data associated to some `Key` at a given time;
 
@@ -115,10 +116,13 @@ export function forEach<A>(data: Collection<A>, fn: (elt: A) => void) {
 
    When your data change over time, you shall invoke the following methods
    of the model to keep views in sync:
-   - [[update]] or [[updateIndex]] when single or contiguous row data changes over time;
-   - [[reload]] when the number of rows, their ordering, or (many) row data has been changed.
+   - [[update]] or [[updateIndex]] when single or contiguous row data
+   changes over time;
+   - [[reload]] when the number of rows, their ordering, or (many) row data
+   has been changed.
 
-   It is always safe to use `reload` instead of `update` although it might be less performant.
+   It is always safe to use `reload` instead of `update` although it might be
+   less performant.
 
    @template Key - identification of some entry
    @template Row - dynamic row data associated to some key
@@ -139,16 +143,16 @@ export abstract class Model<Key, Row> {
   abstract getRowCount(): number;
 
   /**
-     Shall return the current row data at a given index in the table, with respect to
-     current filtering and ordering (if any).
-     Might return `undefined` if the index is invalid or not (yet) available.
+     Shall return the current row data at a given index in the table, with
+     respect to current filtering and ordering (if any).  Might return
+     `undefined` if the index is invalid or not (yet) available.
   */
   abstract getRowAt(index: number): undefined | Row;
 
   /**
-     Shall return the key at the given index. The specified index and data
-     are those of the last corresponding call to [[getRowAt]].
-     Might return `undefined` if the index is invalid.
+     Shall return the key at the given index. The specified index and data are
+     those of the last corresponding call to [[getRowAt]].  Might return
+     `undefined` if the index is invalid.
   */
   abstract getKeyAt(index: number): undefined | Key;
 
@@ -160,11 +164,12 @@ export abstract class Model<Key, Row> {
   abstract getKeyFor(index: number, data: Row): undefined | Key;
 
   /**
-     Shall return the index of a given entry in the table, identified by its key, with
-     respect to current filtering and ordering (if any).
-     Shall return `undefined` if the specified key no longer belong to the table or
-     when it is currently filtered out.
-     Out-of-range indices would be treated as `undefined`.
+     Shall return the index of a given entry in the table, identified by its
+     key, with respect to current filtering and ordering (if any).
+     Shall return
+     `undefined` if the specified key no longer belong to the table or when it
+     is currently filtered out.  Out-of-range indices would be treated as
+     `undefined`.
   */
   abstract getIndexOf(key: Key): undefined | number;
 
@@ -205,8 +210,10 @@ export abstract class Model<Key, Row> {
      The initial watching range is empty with no trigger.
      You normally never call this method directly.
      It is automatically called by table views.
-     @param onReload - optional callback for reloads (and updates, unless specified)
-     @param onUpdate - optional callback for updates (when different from reloads)
+     @param onReload - optional callback for reloads
+     (and updates, unless specified)
+     @param onUpdate - optional callback for updates
+     (when different from reloads)
   */
   link(onReload?: Trigger, onUpdate?: Trigger): Client {
     const id = this.clientsId++;
@@ -222,7 +229,7 @@ export abstract class Model<Key, Row> {
       watch(lower: number, upper: number) {
         w.lower = lower;
         w.upper = upper;
-      }
+      },
     };
     m.set(id, w);
     return w;
diff --git a/ivette/src/dome/src/renderer/table/views.tsx b/ivette/src/dome/src/renderer/table/views.tsx
index 3089b35483683f8efa470dc7e241091877333937..671cc8f8438cd53b47aa12fd09bec96521b246ec 100644
--- a/ivette/src/dome/src/renderer/table/views.tsx
+++ b/ivette/src/dome/src/renderer/table/views.tsx
@@ -30,7 +30,7 @@ import { Trigger, Client, Sorting, SortingInfo, Model } from './models';
 
 import './style.css';
 
-const SVG = SVGraw as (props: { id: string, size?: number }) => JSX.Element;
+const SVG = SVGraw as (props: { id: string; size?: number }) => JSX.Element;
 
 // --------------------------------------------------------------------------
 // --- Rendering Interfaces
@@ -56,7 +56,7 @@ export type RenderByFields<Row> = {
    You may use hierarchical index to order columns.
    See [[ColumnGroup]].
  */
-export type index = number | number[]
+export type index = number | number[];
 
 /**
    Column Properties.
@@ -169,7 +169,7 @@ interface ColumnData {
   title?: string;
   headerMenu: () => void;
   headerRef: divRef;
-};
+}
 
 interface PopupItem {
   label: string;
@@ -177,11 +177,11 @@ interface PopupItem {
   enabled?: boolean;
   display?: boolean;
   onClick?: Trigger;
-};
+}
 
 type PopupMenu = ('separator' | PopupItem)[];
 
-type Cmap<A> = Map<string, A>
+type Cmap<A> = Map<string, A>;
 type Cprops = ColProps<any>;
 type ColProps<R> = ColumnProps<R, any>;
 
@@ -206,13 +206,13 @@ const defaultGetter = (row: any, dataKey: string) => {
 
 const defaultRenderer = (d: any) => (
   <div className="dome-xTable-renderer dome-text-label">
-    {new String(d)}
+    {String(d)}
   </div>
 );
 
 function makeRowGetter<Key, Row>(model?: Model<Key, Row>) {
   return ({ index }: Index) => model && model.getRowAt(index);
-};
+}
 
 function makeDataGetter(
   getter: ((row: any, dataKey: string) => any) = defaultGetter,
@@ -237,8 +237,8 @@ function makeDataRenderer(
   render: ((data: any) => ReactNode) = defaultRenderer,
   onContextMenu?: (row: any, index: number, dataKey: string) => void,
 ): TableCellRenderer {
-  return (props => {
-    const cellData = props.cellData;
+  return ((props) => {
+    const { cellData } = props;
     try {
       const contents = cellData ? render(cellData) : null;
       if (onContextMenu) {
@@ -270,7 +270,7 @@ type ColSettings<A> = { [id: string]: undefined | null | A };
 type TableSettings = {
   resize?: ColSettings<number>;
   visible?: ColSettings<boolean>;
-}
+};
 
 // --------------------------------------------------------------------------
 // --- Table State
@@ -384,7 +384,7 @@ class TableState<Key, Row> {
     const wl = cwl ? cwl + offset : 0;
     const wr = cwr ? cwr - offset : 0;
     if (wl > 40 && wr > 40) {
-      const resize = this.resize;
+      const { resize } = this;
       resize.set(lcol, wl);
       resize.set(rcol, wr);
       this.offset = offset;
@@ -405,8 +405,8 @@ class TableState<Key, Row> {
     if (userSettings) {
       const cws: ColSettings<number> = {};
       const cvs: ColSettings<boolean> = {};
-      const resize = this.resize;
-      const visible = this.visible;
+      const { resize } = this;
+      const { visible } = this;
       this.columns.forEach(({ id }) => {
         const cw = resize.get(id);
         const cv = visible.get(id);
@@ -422,8 +422,8 @@ class TableState<Key, Row> {
   importSettings(settings?: string) {
     if (settings !== this.settings) {
       this.settings = settings;
-      const resize = this.resize;
-      const visible = this.visible;
+      const { resize } = this;
+      const { visible } = this;
       resize.clear();
       visible.clear();
       const theSettings: undefined | TableSettings =
@@ -482,11 +482,12 @@ class TableState<Key, Row> {
   onSelection?: (data: Row, key: Key, index: number) => void;
 
   onRowClick(info: RowMouseEventHandlerParams) {
-    const index = info.index;
+    const { index } = info;
     const data = info.rowData as (Row | undefined);
-    const model = this.model;
-    const key = (data !== undefined) ? model?.getKeyFor(index, data) : undefined;
-    const onSelection = this.onSelection;
+    const { model } = this;
+    const key =
+      (data !== undefined) ? model?.getKeyFor(index, data) : undefined;
+    const { onSelection } = this;
     if (key !== undefined && data !== undefined && onSelection)
       onSelection(data, key, index);
   }
@@ -498,11 +499,11 @@ class TableState<Key, Row> {
 
   rowClassName({ index }: Index): string {
     if (this.selectedIndex === index) return 'dome-xTable-selected';
-    return (index & 1 ? 'dome-xTable-even' : 'dome-xTable-odd');
+    return (index & 1 ? 'dome-xTable-even' : 'dome-xTable-odd'); // eslint-disable-line no-bitwise
   }
 
   keyStepper(index: number) {
-    const onSelection = this.onSelection;
+    const { onSelection } = this;
     const key = this.model?.getKeyAt(index);
     const data = this.model?.getRowAt(index);
     if (key !== undefined && data !== undefined && onSelection) {
@@ -521,7 +522,7 @@ class TableState<Key, Row> {
   }
 
   onSorting(ord?: SortingInfo) {
-    const sorting = this.sorting;
+    const { sorting } = this;
     if (sorting) {
       sorting.setSorting(ord);
       this.sortBy = ord?.sortBy;
@@ -561,16 +562,16 @@ class TableState<Key, Row> {
   // ---- Header Context Menu
 
   onHeaderMenu() {
-    let has_order = false;
-    let has_resize = false;
-    let has_visible = false;
-    const visible = this.visible;
-    const columns = this.columns;
-    columns.forEach(col => {
-      if (!col.disableSort) has_order = true;
-      if (!col.fixed) has_resize = true;
+    let hasOrder = false;
+    let hasResize = false;
+    let hasVisible = false;
+    const { visible } = this;
+    const { columns } = this;
+    columns.forEach((col) => {
+      if (!col.disableSort) hasOrder = true;
+      if (!col.fixed) hasResize = true;
       if (col.visible !== 'never' && col.visible !== 'always')
-        has_visible = true;
+        hasVisible = true;
     });
     const resetSizing = () => {
       this.resize.clear();
@@ -584,27 +585,27 @@ class TableState<Key, Row> {
     const items: PopupMenu = [
       {
         label: 'Reset ordering',
-        display: has_order && this.sorting,
+        display: hasOrder && this.sorting,
         onClick: this.onSorting,
       },
       {
         label: 'Reset column widths',
-        display: has_resize,
+        display: hasResize,
         onClick: resetSizing,
       },
       {
         label: 'Restore column defaults',
-        display: has_visible,
+        display: hasVisible,
         onClick: resetColumns,
       },
       'separator',
     ];
-    columns.forEach(col => {
+    columns.forEach((col) => {
       switch (col.visible) {
         case 'never':
         case 'always':
           break;
-        default:
+        default: {
           const { id, label, title } = col;
           const checked = isVisible(visible, col);
           const onClick = () => {
@@ -612,6 +613,7 @@ class TableState<Key, Row> {
             this.updateSettings();
           };
           items.push({ label: label || title || id, checked, onClick });
+        }
       }
     });
     Dome.popupMenu(items);
@@ -654,7 +656,7 @@ class TableState<Key, Row> {
     path: number[],
     index: number,
   ): Trigger {
-    const id = props.id;
+    const { id } = props;
     const theIndex = props.index ?? index;
     const thePath = path.concat(theIndex);
     this.setRegistry(id, { ...props, index: thePath });
@@ -705,7 +707,7 @@ export function Column<Row, Cell>(props: ColumnProps<Row, Cell>) {
 function spawnIndex(
   state: TableState<any, any>,
   path: number[],
-  children: any
+  children: any,
 ) {
   const indexChild = (elt: React.ReactElement, k: number) => (
     <ColumnContext.Provider value={{ state, path, index: k }}>
@@ -758,7 +760,7 @@ function spawnIndex(
    this implicit root column group, just pack your columns inside a classical
    React fragment: `<Table … ><>{children}</></Table>`.
  */
-export function ColumnGroup(props: { index?: index, children: any }) {
+export function ColumnGroup(props: { index?: index; children: any }) {
   const context = React.useContext(ColumnContext);
   if (!context) return null;
   const { state, path, index: defaultIndex } = context;
@@ -787,7 +789,7 @@ function makeColumn<Key, Row>(
   };
   const width = state.resize.get(id) || props.width || 60;
   const flexGrow = fill ? 1 : 0;
-  const sorting = state.sorting;
+  const { sorting } = state;
   const disableSort =
     props.disableSort || !sorting || !sorting.canSortBy(dataKey);
   const getter = state.computeGetter(id, dataKey, props);
@@ -807,7 +809,7 @@ function makeColumn<Key, Row>(
       style={align}
     />
   );
-};
+}
 
 const byIndex = (a: Cprops, b: Cprops) => {
   const ak = a.index ?? 0;
@@ -815,7 +817,7 @@ const byIndex = (a: Cprops, b: Cprops) => {
   if (ak < bk) return -1;
   if (bk < ak) return 1;
   return 0;
-}
+};
 
 function makeCprops<Key, Row>(state: TableState<Key, Row>) {
   const cols: Cprops[] = [];
@@ -846,20 +848,24 @@ function makeColumns<Key, Row>(state: TableState<Key, Row>, cols: Cprops[]) {
 
 const headerIcon = (icon?: string) => (
   icon &&
-  (<div className='dome-xTable-header-icon'>
-    <SVG id={icon} />
-  </div>)
+  (
+    <div className="dome-xTable-header-icon">
+      <SVG id={icon} />
+    </div>
+  )
 );
 
 const headerLabel = (label?: string) => (
   label &&
-  (<label className='dome-xTable-header-label dome-text-label'>
-    {label}
-  </label>)
+  (
+    <label className="dome-xTable-header-label dome-text-label">
+      {label}
+    </label>
+  )
 );
 
 const makeSorter = (id: string) => (
-  <div className='dome-xTable-header-sorter'>
+  <div className="dome-xTable-header-sorter">
     <SVG id={id} size={8} />
   </div>
 );
@@ -889,7 +895,7 @@ function headerRenderer(props: TableHeaderProps) {
       : undefined;
   return (
     <div
-      className='dome-xTable-header'
+      className="dome-xTable-header"
       title={title}
       ref={headerRef}
       onContextMenu={headerMenu}
@@ -930,15 +936,17 @@ const Resizer = (props: ResizerProps) => (
   </DraggableCore>
 );
 
-type ResizeInfo = { id: string, fixed: boolean, left?: string, right?: string };
+type ResizeInfo = { id: string; fixed: boolean; left?: string; right?: string };
 
 function makeResizers(
   state: TableState<any, any>,
   columns: Cprops[],
 ): null | JSX.Element[] {
   if (columns.length < 2) return null;
-  const resizing: ResizeInfo[] = columns.map(({ id, fixed = false }) => ({ id, fixed }));
-  var k: number, cid; // last non-fixed from left/right
+  const resizing: ResizeInfo[] =
+    columns.map(({ id, fixed = false }) => ({ id, fixed }));
+  let k: number; let
+    cid; // last non-fixed from left/right
   for (cid = undefined, k = 0; k < columns.length; k++) {
     const r = resizing[k];
     r.left = cid;
@@ -949,8 +957,9 @@ function makeResizers(
     r.right = cid;
     if (!r.fixed) cid = r.id;
   }
-  const cwidth = columns.map(col => state.computeWidth(col.id));
-  var position = 0, resizers = [];
+  const cwidth = columns.map((col) => state.computeWidth(col.id));
+  let position = 0; const
+    resizers = [];
   for (k = 0; k < columns.length - 1; k++) {
     const width = cwidth[k];
     if (!width) return null;
@@ -991,6 +1000,7 @@ function makeResizers(
 const CSS_HEADER_HEIGHT = 22;
 const CSS_ROW_HEIGHT = 20;
 
+// Modifies state in place
 function makeTable<Key, Row>(
   props: TableProps<Key, Row>,
   state: TableState<Key, Row>,
@@ -998,7 +1008,7 @@ function makeTable<Key, Row>(
 ) {
 
   const { width, height } = size;
-  const model = props.model;
+  const { model } = props;
   const itemCount = model.getRowCount();
   const tableHeight = CSS_HEADER_HEIGHT + CSS_ROW_HEIGHT * itemCount;
   const smallHeight = itemCount > 0 && tableHeight < height;
@@ -1008,11 +1018,13 @@ function makeTable<Key, Row>(
   const columns = makeColumns(state, cprops);
   const resizers = makeResizers(state, cprops);
 
+  /* eslint-disable no-param-reassign */
   state.rowCount = rowCount;
   if (state.width !== width) {
     state.width = width;
     setImmediate(state.forceUpdate);
   }
+  /* eslint-enable no-param-reassign */
 
   return (
     <div onKeyDown={state.onKeyDown}>
@@ -1041,9 +1053,9 @@ function makeTable<Key, Row>(
         {columns}
       </VTable>
       {resizers}
-    </div >
+    </div>
   );
-};
+}
 
 // --------------------------------------------------------------------------
 // --- Table View
@@ -1052,7 +1064,7 @@ function makeTable<Key, Row>(
 /** Table View.
 
    This component is base on
-   [React-Virtualized](https://bvaughn.github.io/react-virtualized/#/components/Table)
+   [React-Virtualized](https://bvaughn.github.io/react-virtualized)
    which offers a super-optimized lazy rendering process that scales on huge
    datasets.
 
@@ -1078,7 +1090,7 @@ function makeTable<Key, Row>(
 
    @template Key - unique identifiers of table entries.
    @template Row - data associated to each key in the table entries.
-*/
+ */
 
 export function Table<Key, Row>(props: TableProps<Key, Row>) {
 
@@ -1096,14 +1108,14 @@ export function Table<Key, Row>(props: TableProps<Key, Row>) {
   });
   Dome.useEvent('dome.defaults', state.clearSettings);
   return (
-    <div className='dome-xTable'>
-      <React.Fragment key='columns'>
+    <div className="dome-xTable">
+      <React.Fragment key="columns">
         {spawnIndex(state, [], props.children)}
       </React.Fragment>
-      <AutoSizer key='table'>
+      <AutoSizer key="table">
         {(size: Size) => makeTable(props, state, size)}
       </AutoSizer>
-    </div >
+    </div>
   );
 }
 
diff --git a/ivette/src/dome/src/renderer/text/buffers.ts b/ivette/src/dome/src/renderer/text/buffers.ts
index c0db788100d9cf3019b8c6a492061b33338c7e26..f0a587f849fa9616c6add252a9533f632576fb8a 100644
--- a/ivette/src/dome/src/renderer/text/buffers.ts
+++ b/ivette/src/dome/src/renderer/text/buffers.ts
@@ -8,9 +8,9 @@
 */
 
 import Emitter from 'events';
-import CodeMirror from 'codemirror/lib/codemirror.js';
+import CodeMirror from 'codemirror/lib/codemirror';
 
-export type Range = { from: CodeMirror.Position, to: CodeMirror.Position };
+export type Range = { from: CodeMirror.Position; to: CodeMirror.Position };
 
 export interface Decorator {
   /** @return a className to apply on markers with the identifier. */
@@ -24,8 +24,10 @@ export interface TextMarkerProxy {
 }
 
 /**
-   Text Marker options. Inherits
-   CodeMirror [TextMerkerOptions](https://codemirror.net/doc/manual.html#api_marker).
+   Text Marker options.
+
+   Inherits CodeMirror
+   [TextMerkerOptions](https://codemirror.net/doc/manual.html#api_marker).
  */
 export interface MarkerProps extends CodeMirror.TextMarkerOptions {
   id?: string;
@@ -48,25 +50,25 @@ export interface CSSMarker {
 // --- Batched Update
 // --------------------------------------------------------------------------
 
-const BATCH_OPS = 500
-const BATCH_DELAY = 5
-const BATCH_RMAX = 1000 // max tag range for sorting
-const BATCH_MARGINS = 20 // visible lines above the viewport
+const BATCH_OPS = 500;
+const BATCH_DELAY = 5;
+const BATCH_RMAX = 1000; // max tag range for sorting
+const BATCH_MARGINS = 20; // visible lines above the viewport
 
 interface MarkerOptions {
-  id?: string,
-  hover?: boolean,
-  className?: string,
-  options: CodeMirror.TextMarkerOptions,
+  id?: string;
+  hover?: boolean;
+  className?: string;
+  options: CodeMirror.TextMarkerOptions;
 }
 
 interface StackedMarker extends MarkerOptions {
-  startIndex: number,
+  startIndex: number;
 }
 
 interface BufferedMarker extends MarkerOptions {
-  startIndex: number,
-  stopIndex: number,
+  startIndex: number;
+  stopIndex: number;
 }
 
 type BufferedTag = BufferedMarker | undefined;
@@ -91,7 +93,9 @@ function byVisibleTag(lmin: number, lmax: number) {
 
 export interface RichTextBufferProps {
 
-  /** CodeMirror [mode](https://codemirror.net/mode/index.html) specification. */
+  /**
+   * CodeMirror [mode](https://codemirror.net/mode/index.html) specification.
+   */
   mode?: any;
 
   /** Maximum number of lines in the buffer. */
@@ -124,9 +128,9 @@ export interface RichTextBufferProps {
    the _edited_ state to `false`, but sill emit an `'edited'` event if the
    buffer was not empty.
 
-   Buffers can also be updated programmatically by various methods. In addition to
-   specified CodeMirror modes, you can also attach text markers programmatically with
-   a push/pop API.
+   Buffers can also be updated programmatically by various methods. In addition
+   to specified CodeMirror modes, you can also attach text markers
+   programmatically with a push/pop API.
 
    Text markers can be associated with an identifier, that can be used for
    dynamic highlighting, called Decorations. Decorations are class names that
@@ -146,13 +150,13 @@ export interface RichTextBufferProps {
 export class RichTextBuffer extends Emitter {
 
   private document: CodeMirror.Doc;
-  private maxlines: number = 10000;
+  private maxlines = 10000;
   private editors: CodeMirror.Editor[] = [];
-  private cacheIndex: number = 0; // document index, negative if not computed
+  private cacheIndex = 0; // document index, negative if not computed
   private bufferedText: string; // buffered text to append
   private bufferedTags: BufferedTag[];
   private stacked: StackedMarker[] = [];
-  private batched: boolean = false;
+  private batched = false;
 
   // Indexed by CSS property dome-xHover-nnnn
   private cssmarkers = new Map<string, CSSMarker>();
@@ -222,9 +226,7 @@ export class RichTextBuffer extends Emitter {
       const start = doc.posFromIndex(Infinity);
       if (start.ch > 0) doc.replaceRange('\n', start, undefined, 'buffer');
       this.cacheIndex = -1;
-    } else {
-      if (buf[buf.length - 1] !== '\n') this.bufferedText += '\n';
-    }
+    } else if (buf[buf.length - 1] !== '\n') this.bufferedText += '\n';
   }
 
   /**
@@ -271,11 +273,11 @@ export class RichTextBuffer extends Emitter {
      inserted between its associated [[openTextMarker]] and [[closeTextMarker]]
      calls.
 
-     The returned text marker is actually a _proxy_ to the text marker that will be
-     eventually created by [[closeTextMarker]]. Its methods are automatically
-     forwarded to the actual `CodeMirror.TextMarker`
-     instance, once created.  Hence, you can safely invoke these methods on either
-     the _proxy_ or the _final_ text marker at your convenience.
+     The returned text marker is actually a _proxy_ to the text marker that will
+     be eventually created by [[closeTextMarker]]. Its methods are automatically
+     forwarded to the actual `CodeMirror.TextMarker` instance, once created.
+     Hence, you can safely invoke these methods on either the _proxy_ or the
+     _final_ text marker at your convenience.
   */
   openTextMarker(props: MarkerProps) {
     const { id, hover, className, ...options } = props;
@@ -301,13 +303,14 @@ export class RichTextBuffer extends Emitter {
     }
   }
 
-  /** Lookup for the text markers associated with a marker identifier. */
+  /** Lookup for the text markers associated with a marker identifier.
+      Remove the marked tags from the buffered tag array. */
   findTextMarker(id: string): CodeMirror.TextMarker[] {
     this.doFlushText();
-    this.bufferedTags.forEach((tg, idx, arr) => {
+    this.bufferedTags.forEach((tg, idx) => {
       if (tg?.id === id) {
         this.doMark(tg);
-        arr[idx] = undefined;
+        this.bufferedTags[idx] = undefined;
       }
     });
     return this.textmarkers.get(id) ?? [];
@@ -459,7 +462,7 @@ export class RichTextBuffer extends Emitter {
 
   private onChange(
     _editor: CodeMirror.Editor,
-    change: CodeMirror.EditorChangeLinkedList
+    change: CodeMirror.EditorChangeLinkedList,
   ) {
     if (change.origin !== 'buffer') {
       this.setEdited(true);
@@ -477,7 +480,9 @@ export class RichTextBuffer extends Emitter {
      @param cm - code mirror instance to link this document in.
   */
   link(cm: CodeMirror.Editor) {
-    const newDoc = this.document.linkedDoc({ sharedHist: true, mode: undefined });
+    const newDoc = this.document.linkedDoc(
+      { sharedHist: true, mode: undefined },
+    );
     cm.swapDoc(newDoc);
     cm.on('change', this.onChange);
     this.editors.push(cm);
@@ -511,7 +516,7 @@ export class RichTextBuffer extends Emitter {
     this.editors.forEach((cm) => {
       try { fn(cm); } catch (e) { console.error('[Dome.text]', e); }
     });
-  };
+  }
 
   // --------------------------------------------------------------------------
   // --- Update Operations
@@ -540,7 +545,7 @@ export class RichTextBuffer extends Emitter {
         '',
         { line: p, ch: 0 },
         { line: q, ch: 0 },
-        'buffer'
+        'buffer',
       );
       this.cacheIndex = -1;
     }
@@ -551,7 +556,7 @@ export class RichTextBuffer extends Emitter {
     const { id, hover, className, startIndex, stopIndex } = tag;
     let markerId;
     if (id || hover) {
-      markerId = 'dome-xHover-' + (this.markid++);
+      markerId = `dome-xHover-${this.markid++}`;
       const cmark = {
         id,
         classNameId: markerId,
@@ -562,7 +567,7 @@ export class RichTextBuffer extends Emitter {
     }
     const fullClassName = [
       'dome-xMarked',
-      id && ('dome-xMark-' + id),
+      id && (`dome-xMark-${id}`),
       markerId,
       className,
     ].filter((s) => !!s).join(' ');
@@ -601,13 +606,12 @@ export class RichTextBuffer extends Emitter {
   }
 
   private getLastIndex() {
-    let idx = this.cacheIndex;
-    if (idx < 0) {
+    if (this.cacheIndex < 0) {
       const doc = this.document;
       const line = doc.lastLine() + 1;
-      this.cacheIndex = idx = doc.indexFromPos({ line, ch: 0 });
+      this.cacheIndex = doc.indexFromPos({ line, ch: 0 });
     }
-    return idx;
+    return this.cacheIndex;
   }
 
   // --------------------------------------------------------------------------
diff --git a/ivette/src/dome/src/renderer/text/editors.tsx b/ivette/src/dome/src/renderer/text/editors.tsx
index 6a59be9f1e25361277408aa0d0d1309f3b00b681..0a5d45ec867f57d868dc7e2f85df920f2a71ce25 100644
--- a/ivette/src/dome/src/renderer/text/editors.tsx
+++ b/ivette/src/dome/src/renderer/text/editors.tsx
@@ -11,7 +11,7 @@ import _ from 'lodash';
 import React from 'react';
 import * as Dome from 'dome';
 import { Vfill } from 'dome/layout/boxes';
-import CodeMirror, { EditorConfiguration } from 'codemirror/lib/codemirror.js';
+import CodeMirror, { EditorConfiguration } from 'codemirror/lib/codemirror';
 import { RichTextBuffer, CSSMarker, Decorator } from './buffers';
 
 import './style.css';
@@ -133,33 +133,33 @@ class CodeMirrorWrapper extends React.Component<TextProps> {
       // Mounting...
       const { buffer } = this.props;
       const config = getConfig(this.props);
-      const cm = this.codeMirror = CodeMirror(elt, { value: '' });
+      this.codeMirror = CodeMirror(elt, { value: '' });
       if (buffer) {
-        buffer.link(cm);
+        buffer.link(this.codeMirror);
         buffer.on('decorated', this.handleUpdate);
         buffer.on('scroll', this.handleScrollTo);
       }
       // Passing all options to constructor does not work (Cf. CodeMirror's BTS)
-      forEachOption(config, (opt) => cm.setOption(opt, config[opt]));
+      forEachOption(
+        config, (opt) => this.codeMirror?.setOption(opt, config[opt]),
+      );
       // Binding events to view
-      cm.on('update', this.handleUpdate);
-      cm.on('keyHandled', this.handleKey);
+      this.codeMirror.on('update', this.handleUpdate);
+      this.codeMirror.on('keyHandled', this.handleKey);
       Dome.on('dome.update', this.refresh);
       // Auto refresh
       this.refreshPolling = setInterval(this.autoRefresh, 250);
       this.handleUpdate();
     } else {
       // Unmounting...
-      const polling = this.refreshPolling;
-      if (polling) {
-        clearInterval(polling);
+      if (this.refreshPolling) {
+        clearInterval(this.refreshPolling);
         this.refreshPolling = undefined;
       }
-      const cm = this.codeMirror;
       Dome.off('dome.update', this.refresh);
       const { buffer } = this.props;
-      if (cm && buffer) {
-        buffer.unlink(cm);
+      if (this.codeMirror && buffer) {
+        buffer.unlink(this.codeMirror);
         buffer.off('decorated', this.handleUpdate);
         buffer.off('scroll', this.handleScrollTo);
       }
@@ -210,7 +210,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> {
   _findMarker(elt: Element): CSSMarker | undefined {
     const { buffer } = this.props;
     if (buffer) {
-      var best: CSSMarker | undefined;
+      let best: CSSMarker | undefined;
       elt.classList.forEach((name) => {
         const marker = buffer.findHover(name);
         if (marker && (!best || marker.length < best.length)) best = marker;
@@ -220,33 +220,35 @@ class CodeMirrorWrapper extends React.Component<TextProps> {
     return undefined;
   }
 
+  // eslint-disable-next-line class-methods-use-this
   _findDecoration(
     classes: DOMTokenList,
     buffer: RichTextBuffer,
     decorator: Decorator,
   ) {
-    var best_marker: CSSMarker | undefined;
-    var best_decorated: CSSMarker | undefined;
-    var best_decoration: string | undefined;
+    let bestMarker: CSSMarker | undefined;
+    let bestDecorated: CSSMarker | undefined;
+    let bestDecoration: string | undefined;
     classes.forEach((name) => {
 
       const marker = buffer.findHover(name);
       const id = marker && marker.id;
       const decoration = id && decorator(id);
 
-      if (marker && (!best_marker || marker.length < best_marker.length)) {
-        best_marker = marker;
+      if (marker && (!bestMarker || marker.length < bestMarker.length)) {
+        bestMarker = marker;
       }
 
-      if (marker && decoration && (!best_decorated || marker.length < best_decorated.length)) {
-        best_decorated = marker;
-        best_decoration = decoration;
+      if (marker && decoration &&
+        (!bestDecorated || marker.length < bestDecorated.length)) {
+        bestDecorated = marker;
+        bestDecoration = decoration;
       }
 
     });
-    return best_marker ? {
-      classNameId: best_marker.classNameId,
-      decoration: best_decoration,
+    return bestMarker ? {
+      classNameId: bestMarker.classNameId,
+      decoration: bestDecoration,
     } : undefined;
   }
 
@@ -256,7 +258,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> {
     if (toMark) {
       const n = toMark.length;
       if (n === 0) return;
-      for (var k = 0; k < n; k++) toMark[k].classList.add(className);
+      for (let k = 0; k < n; k++) toMark[k].classList.add(className);
     }
   }
 
@@ -266,21 +268,21 @@ class CodeMirrorWrapper extends React.Component<TextProps> {
     if (toUnmark) {
       const n = toUnmark.length;
       if (n === 0) return;
-      const elts: Element[] = new Array(n);;
-      for (var k = 0; k < n; k++) elts[k] = toUnmark[k];
+      const elts: Element[] = new Array(n);
+      for (let k = 0; k < n; k++) elts[k] = toUnmark[k];
       elts.forEach((elt) => elt.classList.remove(className));
     }
   }
 
   handleHover(target: Element) {
     // Throttled (see constructor)
-    const old_marker = this.marker;
-    const new_marker = this._findMarker(target);
-    if (old_marker !== new_marker) {
-      if (old_marker) this._unmarkElementsWith(CSS_HOVERED);
-      if (new_marker && new_marker.hover)
-        this._markElementsWith(new_marker.classNameId, CSS_HOVERED);
-      this.marker = new_marker;
+    const oldMarker = this.marker;
+    const newMarker = this._findMarker(target);
+    if (oldMarker !== newMarker) {
+      if (oldMarker) this._unmarkElementsWith(CSS_HOVERED);
+      if (newMarker && newMarker.hover)
+        this._markElementsWith(newMarker.classNameId, CSS_HOVERED);
+      this.marker = newMarker;
     }
   }
 
@@ -290,15 +292,15 @@ class CodeMirrorWrapper extends React.Component<TextProps> {
     if (!marked) return;
     const n = marked.length;
     if (n === 0) return;
-    const marker = this.marker;
+    const { marker } = this;
     const hovered = (marker && marker.hover) ? marker.classNameId : undefined;
-    const selection = this.props.selection;
-    const selected = selection && ('dome-xMark-' + selection);
+    const { selection } = this.props;
+    const selected = selection && (`dome-xMark-${selection}`);
     const { buffer } = this.props;
     const decorator = buffer?.getDecorator();
     if (!hovered && !selection && !decorator) return;
     const newDecorations = new Map<string, string>();
-    for (var k = 0; k < n; k++) {
+    for (let k = 0; k < n; k++) {
       const elt = marked[k];
       const classes = elt.classList;
       if (hovered && classes.contains(hovered)) classes.add(CSS_HOVERED);
@@ -327,7 +329,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> {
 
   onMouseClick(evt: MouseEvt, callback: MarkerCallback | undefined) {
     // No need for throttling
-    const target = evt.target;
+    const { target } = evt;
     if (target instanceof Element && callback) {
       const marker = this._findMarker(target);
       if (marker && marker.id) callback(marker.id);
@@ -350,8 +352,10 @@ class CodeMirrorWrapper extends React.Component<TextProps> {
   handleScrollTo(line: number) {
     try {
       const cm = this.codeMirror;
-      cm && cm.scrollIntoView({ line, ch: 0 });
-    } catch (_error) { } // Out of range
+      return cm && cm.scrollIntoView({ line, ch: 0 });
+    } catch (_error) {
+      console.warn(`[Dome] Unable to scroll to line ${line}: out of range.`);
+    }
   }
 
   // --------------------------------------------------------------------------
@@ -392,12 +396,12 @@ class CodeMirrorWrapper extends React.Component<TextProps> {
       const {
         buffer: oldBuffer,
         selection: oldSelect,
-        fontSize: oldFont
+        fontSize: oldFont,
       } = this.props;
       const {
         buffer: newBuffer,
         selection: newSelect,
-        fontSize: newFont
+        fontSize: newFont,
       } = newProps;
       if (oldBuffer !== newBuffer) {
         if (oldBuffer) oldBuffer.unlink(cm);
@@ -423,7 +427,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> {
       });
       // Update selection
       if (oldSelect !== newSelect) {
-        const selected = 'dome-xMark-' + newSelect;
+        const selected = `dome-xMark-${newSelect}`;
         if (oldSelect) this._unmarkElementsWith(CSS_SELECTED);
         if (newSelect) this._markElementsWith(selected, CSS_SELECTED);
       }
@@ -436,7 +440,8 @@ class CodeMirrorWrapper extends React.Component<TextProps> {
 
   render() {
     return (
-      <div className={'dome-xText'}
+      <div
+        className="dome-xText"
         ref={this.mountPoint}
         onClick={this.onClick}
         onContextMenu={this.onContextMenu}
@@ -444,7 +449,8 @@ class CodeMirrorWrapper extends React.Component<TextProps> {
         onFocus={this.onFocus}
         onScroll={this.onScroll}
         onMouseMove={this.onMouseMove}
-      />);
+      />
+    );
   }
 
 }
@@ -459,23 +465,24 @@ class CodeMirrorWrapper extends React.Component<TextProps> {
    A component rendering the content of a text buffer, that shall be instances
    of the `Buffer` base class.
 
-   The view is based on a [CodeMirror](https://codemirror.net) component linked with
-   the internal Code Mirror Document from the associated buffer.
+   The view is based on a [CodeMirror](https://codemirror.net) component linked
+   with the internal Code Mirror Document from the associated buffer.
 
-   Multiple views might share the same buffer as source content. The buffer will be
-   kept in sync with all its linked views.
+   Multiple views might share the same buffer as source content. The buffer will
+   be kept in sync with all its linked views.
 
-   The Text component never update its mounted NODE element, however, all property
-   modifications (including buffer) are propagated to the internal CodeMirror instance.
-   Undefined properties are set (or reset) to the CodeMirror defaults.
+   The Text component never update its mounted NODE element, however, all
+   property modifications (including buffer) are propagated to the internal
+   CodeMirror instance. Undefined properties are set (or reset) to the
+   CodeMirror defaults.
 
    #### Themes
 
    The CodeMirror `theme` option allow you to style your document,
    especially when using modes.
    Themes are only accessible if you load the associated CSS style sheet.
-   For instance, to use the `'ambiance'` theme provided with CodeMirror, you shall
-   import `'codemirror/theme/ambiance.css'` somewhere in your application.
+   For instance, to use the `'ambiance'` theme provided with CodeMirror, you
+   shall import `'codemirror/theme/ambiance.css'` somewhere in your application.
 
    #### Modes & Adds-On
 
@@ -491,16 +498,13 @@ class CodeMirrorWrapper extends React.Component<TextProps> {
    `import CodeMirror from 'codemirror/lib/codemirror.js'` ; using `from
    'codemirror'` returns a different instance of `CodeMirror` class and will
    not work.
-
  */
 export function Text(props: TextProps) {
   let { className, style, fontSize, ...cmprops } = props;
   if (fontSize !== undefined && fontSize < 4) fontSize = 4;
   if (fontSize !== undefined && fontSize > 48) fontSize = 48;
-  const theStyle = Object.assign({}, style);
-  theStyle.fontSize = fontSize;
   return (
-    <Vfill className={className} style={theStyle}>
+    <Vfill className={className} style={{ ...style, fontSize }}>
       <CodeMirrorWrapper fontSize={fontSize} {...cmprops} />
     </Vfill>
   );
diff --git a/ivette/src/frama-c/LabViews.tsx b/ivette/src/frama-c/LabViews.tsx
index 52bc3151cac0951f6f9642dee1e7ceb83b05dc6b..fe74698d0346f10153cd61714e1086a9a3027761 100644
--- a/ivette/src/frama-c/LabViews.tsx
+++ b/ivette/src/frama-c/LabViews.tsx
@@ -329,7 +329,7 @@ const makeGridItem = (customize: any, onClose: any) => (comp: any) => {
       <Vfill className="labview-content">
         <Hbox className="labview-titlebar">
           <Hfill>
-            <Catch title={id}>
+            <Catch label={id}>
               <RenderItem id={`labview.title.${id}`}>
                 <Label className="labview-handle" label={label} title={title} />
               </RenderItem>
@@ -338,7 +338,7 @@ const makeGridItem = (customize: any, onClose: any) => (comp: any) => {
           {CLOSING}
         </Hbox>
         <TitleContext.Provider value={{ id, label, title }}>
-          <Catch title={id}>{children}</Catch>
+          <Catch label={id}>{children}</Catch>
         </TitleContext.Provider>
       </Vfill>
     </Grids.GridItem>
diff --git a/ivette/tsconfig.json b/ivette/tsconfig.json
index 209897047d79c96fd790c4f1efe2a20a49871f7d..0cbb1f1af838676ae822f3048a4df3ca1086e745 100644
--- a/ivette/tsconfig.json
+++ b/ivette/tsconfig.json
@@ -49,7 +49,7 @@
       "dome/system": [ "src/dome/src/misc/system.js" ],
       "dome/misc/*": [ "src/dome/src/misc/*"],
       "dome/*": [ "src/dome/src/renderer/*" ],
-      "codemirror/lib/codemirror.js": ["node_modules/@types/codemirror/index.d.ts"],
+      "codemirror/lib/codemirror": ["node_modules/@types/codemirror/index.d.ts"],
     },
     // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
     // "typeRoots": [],                       /* List of folders to include type definitions from. */
@@ -101,6 +101,7 @@
       "doc/pages",
       "src/frama-c", "api",
       "src/dome/src/renderer",
+      "src/dome/src/misc",
     ]
   }
 }
diff --git a/src/plugins/server/package.ml b/src/plugins/server/package.ml
index 8734ab5fd50b8a46da21ed0eea3e80a9fc2d32b3..309cae632565703288b5cc95ca1a5793215cfba5 100644
--- a/src/plugins/server/package.ml
+++ b/src/plugins/server/package.ml
@@ -170,7 +170,7 @@ type jtype =
   | Jkey of string (* kind of a string used for indexing *)
   | Jindex of string (* kind of an integer used for indexing *)
   | Joption of jtype
-  | Jdict of string * jtype (* kind of keys *)
+  | Jdict of jtype (* dictionaries *)
   | Jlist of jtype (* order does not matter *)
   | Jarray of jtype (* order matters *)
   | Jtuple of jtype list
@@ -291,14 +291,14 @@ let rec isRecursive = function
   | Jdata _ | Jenum _
   | Jany | Jnull | Jboolean | Jnumber
   | Jstring | Jalpha | Jkey _ | Jindex _ | Jtag _ -> false
-  | Joption js | Jdict(_,js)  | Jarray js | Jlist js -> isRecursive js
+  | Joption js | Jdict js  | Jarray js | Jlist js -> isRecursive js
   | Jtuple js | Junion js -> List.exists isRecursive js
   | Jrecord fjs -> List.exists (fun (_,js) -> isRecursive js) fjs
 
 let rec visit_jtype fn = function
   | Jany | Jself | Jnull | Jboolean | Jnumber
   | Jstring | Jalpha | Jkey _ | Jindex _ | Jtag _ -> ()
-  | Joption js | Jdict(_,js)  | Jarray js | Jlist js -> visit_jtype fn js
+  | Joption js | Jdict js  | Jarray js | Jlist js -> visit_jtype fn js
   | Jtuple js | Junion js -> List.iter (visit_jtype fn) js
   | Jrecord fjs -> List.iter (fun (_,js) -> visit_jtype fn js) fjs
   | Jdata id | Jenum id ->
@@ -471,8 +471,8 @@ let rec md_jtype pp = function
   | Junion js -> md_jlist pp "|" js
   | Jarray js | Jlist js -> protect pp js @ Md.code "[]"
   | Jrecord fjs -> Md.code "{" @ fields pp fjs @ Md.code "}"
-  | Jdict (id,js) ->
-    Md.code "{[" @ key id @ Md.code "]:" @ md_jtype pp js @ Md.code "}"
+  | Jdict js ->
+    Md.code "{[key]:" @ md_jtype pp js @ Md.code "}"
 
 and md_jlist pp sep js =
   Md.glue ~sep:(Md.plain sep)  (List.map (md_jtype pp) js)
diff --git a/src/plugins/server/package.mli b/src/plugins/server/package.mli
index 85af7f86923ac5df9fda9ad6fc5b066800ace281..952f0dd05659e32932bce624cb57c19fcc2e8741 100644
--- a/src/plugins/server/package.mli
+++ b/src/plugins/server/package.mli
@@ -38,7 +38,7 @@ type jtype =
   | Jkey of string (** kind of a string used for indexing *)
   | Jindex of string (** kind of an integer used for indexing *)
   | Joption of jtype
-  | Jdict of string * jtype (** kind of keys *)
+  | Jdict of jtype (** dictionaries *)
   | Jlist of jtype (** order does not matter *)
   | Jarray of jtype (** order matters *)
   | Jtuple of jtype list