diff --git a/ivette/src/dome/renderer/text/editors.tsx b/ivette/src/dome/renderer/text/editors.tsx
index e94492b865230fbb10e1520557f884015d0f662d..b0fe6f55e6fee056b640935f7e6f04e8f111f47a 100644
--- a/ivette/src/dome/renderer/text/editors.tsx
+++ b/ivette/src/dome/renderer/text/editors.tsx
@@ -40,6 +40,10 @@ import { RichTextBuffer, CSSMarker, Decorator } from './buffers';
 import './style.css';
 import './dark-code.css';
 import 'codemirror/lib/codemirror.css';
+import 'codemirror/addon/fold/foldcode';
+import 'codemirror/addon/fold/foldgutter';
+import 'codemirror/addon/fold/comment-fold';
+import 'codemirror/addon/fold/foldgutter.css';
 
 const CSS_HOVERED = 'dome-xText-hover';
 const CSS_SELECTED = 'dome-xText-select';
diff --git a/ivette/src/frama-c/kernel/ASTinfo.tsx b/ivette/src/frama-c/kernel/ASTinfo.tsx
index 8eeff601e8c0a51ecd5b6460654bccddf4ce9e49..907787a07bc9b6d5ee54ea6b5263e640ff437866 100644
--- a/ivette/src/frama-c/kernel/ASTinfo.tsx
+++ b/ivette/src/frama-c/kernel/ASTinfo.tsx
@@ -362,7 +362,7 @@ export default function ASTinfo(): JSX.Element {
         />
       </TitleBar>
       <Boxes.Scroll>
-        {markers.getSelected().map(renderMark)}
+        {React.Children.toArray(markers.getSelected().map(renderMark))}
       </Boxes.Scroll>
     </>
   );
diff --git a/ivette/src/frama-c/kernel/ASTview.tsx b/ivette/src/frama-c/kernel/ASTview.tsx
index c2f657e33a1df75a9bfdec9fb8784c17dbf9e58b..46085ef7d6b25c994b01b2e88fea5f45be04c56c 100644
--- a/ivette/src/frama-c/kernel/ASTview.tsx
+++ b/ivette/src/frama-c/kernel/ASTview.tsx
@@ -26,12 +26,16 @@
 
 import React from 'react';
 import _ from 'lodash';
+import CodeMirror from 'codemirror/lib/codemirror';
 
 import * as Dome from 'dome';
 import * as Settings from 'dome/data/settings';
+import { TitleBar } from 'ivette';
+import { IconButton } from 'dome/controls/buttons';
 import type { key } from 'dome/data/json';
 import { RichTextBuffer } from 'dome/text/buffers';
 import { Text } from 'dome/text/editors';
+import { Hfill } from 'dome/layout/boxes';
 
 import * as Preferences from 'ivette/prefs';
 
@@ -152,10 +156,10 @@ function getBulletColor(status: States.Tag): string {
 function makeBullet(status: States.Tag): HTMLDivElement {
   const marker = document.createElement('div');
   marker.style.color = getBulletColor(status);
+  marker.style.textAlign = 'center';
   if (status.descr)
     marker.title = status.descr;
   marker.innerHTML = 'â—‰';
-  marker.align = 'center';
   return marker;
 }
 
@@ -182,25 +186,19 @@ export default function ASTview(): JSX.Element {
   const statusDict = States.useTags(Properties.propStatusTags);
 
   const setBullets = React.useCallback(() => {
-    if (theFunction) {
-      propertyStatus.forEach((prop) => {
-        if (prop.fct === theFunction) {
-          const status = statusDict.get(prop.status);
-          if (status) {
-            const bullet = makeBullet(status);
-            const markers = buffer.findTextMarker(prop.key);
-            markers.forEach((marker) => {
-              const pos = marker.find();
-              if (pos) {
-                buffer.forEach((cm) => {
-                  cm.setGutterMarker(pos.from.line, 'bullet', bullet);
-                });
-              }
-            });
-          }
-        }
-      });
-    }
+    propertyStatus.forEach((prop) => {
+      const status = statusDict.get(prop.status);
+      if (theFunction && prop.fct === theFunction && status) {
+        const bullet = makeBullet(status);
+        const markers = buffer.findTextMarker(prop.key);
+        buffer.forEach((cm) => {
+          markers.forEach((marker) => {
+            const line = marker.find()?.from.line;
+            if (line) cm.setGutterMarker(line, 'bullet', bullet);
+          });
+        });
+      }
+    });
   }, [buffer, theFunction, propertyStatus, statusDict]);
 
   React.useEffect(() => {
@@ -208,19 +206,19 @@ export default function ASTview(): JSX.Element {
     return () => { buffer.off('change', setBullets); };
   }, [buffer, setBullets]);
 
-  async function reload(): Promise<void> {
+  const reload = React.useCallback(async () => {
     printed.current = theFunction;
     loadAST(buffer, theFunction, theMarker);
-  }
+  }, [buffer, theFunction, theMarker]);
 
   // Hook: async loading
-  React.useEffect(() => {
-    if (printed.current !== theFunction)
-      reload();
-  });
+  React.useEffect(() => { if (printed.current !== theFunction) reload(); });
 
   // Also reload the buffer when the AST is recomputed.
-  Server.onSignal(Ast.changed, reload);
+  React.useEffect(() => {
+    Server.onSignal(Ast.changed, reload);
+    return () => { Server.offSignal(Ast.changed, reload); };
+  });
 
   React.useEffect(() => {
     const decorator = (marker: string): string | undefined => {
@@ -313,9 +311,49 @@ export default function ASTview(): JSX.Element {
       Dome.popupMenu(items);
   }
 
+  const foldAll = (): void => buffer.forEach(CodeMirror.commands.foldAll);
+  const unfoldAll = (): void => buffer.forEach(CodeMirror.commands.unfoldAll);
+
+  const defaultFold = React.useCallback((): void => {
+    buffer.forEach((cm) => {
+      CodeMirror.commands.foldAll(cm);
+      cm.foldCode(0, undefined, 'unfold');
+    });
+  }, [buffer]);
+
+  React.useEffect(() => {
+    buffer.on('change', defaultFold);
+    return () => { buffer.off('change', defaultFold); };
+  });
+
+  const foldOptions: CodeMirror.FoldOptions = {
+    rangeFinder: (cm, pos) => {
+      const range = CodeMirror.fold.auto(cm, pos);
+      if (!range) return undefined;
+      const text = cm.getRange(range.from, range.to);
+      if (text.includes('sequence')) return undefined;
+      return range;
+    }
+  };
+  
   // Component
   return (
     <>
+      <TitleBar>
+        <Hfill />
+        <IconButton
+          icon='ANGLE.RIGHT'
+          visible={true}
+          onClick={foldAll}
+          title='Fold every specifications'
+        />
+        <IconButton
+          icon='ANGLE.DOWN'
+          visible={true}
+          onClick={unfoldAll}
+          title='Unfold every specifications'
+        />
+      </TitleBar>
       <Text
         buffer={buffer}
         mode="text/x-csrc"
@@ -324,7 +362,9 @@ export default function ASTview(): JSX.Element {
         onHover={onHover}
         onSelection={onSelection}
         onContextMenu={onContextMenu}
-        gutters={['bullet']}
+        foldGutter={true}
+        foldOptions={foldOptions}
+        gutters={['bullet', 'CodeMirror-foldgutter']}
         readOnly
       />
     </>
diff --git a/src/plugins/server/kernel_ast.ml b/src/plugins/server/kernel_ast.ml
index 61134be680a1e7fbd1fb15fddabbc0d372a4a22e..9b0ee6e61e2565b834785548bda0bec8c4e89dd2 100644
--- a/src/plugins/server/kernel_ast.ml
+++ b/src/plugins/server/kernel_ast.ml
@@ -478,7 +478,8 @@ let () = Request.register ~package
       try
         if not libc then Kernel.PrintLibc.set true ;
         let global = Kernel_function.get_global kf in
-        let ast = Jbuffer.to_json Printer.pp_global global in
+        let pp_glb = Printer.(with_unfold_precond (fun _ -> true) pp_global) in
+        let ast = Jbuffer.to_json pp_glb global in
         if not libc then Kernel.PrintLibc.set false ; ast
       with err ->
         if not libc then Kernel.PrintLibc.set false ; raise err