clean up
diff --git a/pkgs/hooks/test/update_snippets_test.dart b/pkgs/hooks/test/update_snippets_test.dart
new file mode 100644
index 0000000..25b8292
--- /dev/null
+++ b/pkgs/hooks/test/update_snippets_test.dart
@@ -0,0 +1,165 @@
+// Copyright (c) 2026, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:test/test.dart';
+
+import '../tool/update_snippets.dart';
+
+void main() {
+  late Directory tempDir;
+  late File snippetFile;
+
+  setUp(() async {
+    tempDir = await Directory.systemTemp.createTemp('update_snippets_test');
+    snippetFile = File.fromUri(tempDir.uri.resolve('snippet.dart'));
+    await snippetFile.writeAsString('''
+// snippet-start#my_snippet
+void hello() {
+  print('hello');
+}
+// snippet-end#my_snippet
+''');
+  });
+
+  tearDown(() async {
+    await tempDir.delete(recursive: true);
+  });
+
+  const tag =
+      '<!-- '
+      'file://./snippet.dart#my_snippet -->';
+  const multilineTag =
+      '<!-- '
+      'file://./snippet_multiline.dart#my_snippet -->';
+  const fence =
+      '```'
+      'dart';
+  const endFence = '```';
+
+  test('ordinary code block', () {
+    const docContent =
+        '''
+$tag
+$fence
+old content
+$endFence
+''';
+    final docUri = tempDir.uri.resolve('doc.md');
+    final errors = <String>[];
+    final updated = updateSnippets(docContent, docUri, errors);
+    expect(errors, isEmpty);
+    expect(updated, '''
+$tag
+$fence
+void hello() {
+  print('hello');
+}
+$endFence
+''');
+  });
+
+  test('doc comments (/// )', () {
+    const docContent =
+        '''
+/// $tag
+/// $fence
+/// old content
+/// $endFence
+''';
+    final docUri = tempDir.uri.resolve('doc.dart');
+    final errors = <String>[];
+    final updated = updateSnippets(docContent, docUri, errors);
+    expect(errors, isEmpty);
+    expect(updated, '''
+/// $tag
+/// $fence
+/// void hello() {
+///   print('hello');
+/// }
+/// $endFence
+''');
+  });
+
+  test('blockquoted code block (> )', () {
+    const docContent =
+        '''
+> $tag
+> $fence
+> old content
+> $endFence
+''';
+    final docUri = tempDir.uri.resolve('doc.md');
+    final errors = <String>[];
+    final updated = updateSnippets(docContent, docUri, errors);
+    expect(errors, isEmpty);
+    expect(updated, '''
+> $tag
+> $fence
+> void hello() {
+>   print('hello');
+> }
+> $endFence
+''');
+  });
+
+  test('blockquoted code block without space (>```dart)', () {
+    const docContent =
+        '''
+>$tag
+>$fence
+>old content
+>$endFence
+''';
+    final docUri = tempDir.uri.resolve('doc.md');
+    final errors = <String>[];
+    final updated = updateSnippets(docContent, docUri, errors);
+    expect(errors, isEmpty);
+    expect(updated, '''
+>$tag
+>$fence
+>void hello() {
+>  print('hello');
+>}
+>$endFence
+''');
+  });
+
+  test('blockquoted code block with empty lines', () {
+    const docContent =
+        '''
+> $multilineTag
+> $fence
+> old content
+> $endFence
+''';
+    final snippetMultiline = File.fromUri(
+      tempDir.uri.resolve('snippet_multiline.dart'),
+    );
+    snippetMultiline.writeAsStringSync('''
+// snippet-start#my_snippet
+void hello() {
+  print('hello');
+
+  print('world');
+}
+// snippet-end#my_snippet
+''');
+    final docUri = tempDir.uri.resolve('doc.md');
+    final errors = <String>[];
+    final updated = updateSnippets(docContent, docUri, errors);
+    expect(errors, isEmpty);
+    expect(updated, '''
+> $multilineTag
+> $fence
+> void hello() {
+>   print('hello');
+>
+>   print('world');
+> }
+> $endFence
+''');
+  });
+}
diff --git a/pkgs/hooks/tool/update_snippets.dart b/pkgs/hooks/tool/update_snippets.dart
index 97ceeb3..e843f97 100644
--- a/pkgs/hooks/tool/update_snippets.dart
+++ b/pkgs/hooks/tool/update_snippets.dart
@@ -91,7 +91,7 @@
   var newContent = oldContent;
 
   final markers = RegExp(
-    r'^([ \t]*(?:/{3}[ ]?)?)```(\w*)',
+    r'^([ \t]*(?:(?:>[ \t]*)+|/{3}[ ]?)?)```(\w*)',
     multiLine: true,
   ).allMatches(oldContent);
 
@@ -153,7 +153,7 @@
     final lineBeforeText = oldContent.split('\n')[lastLineOfContentBefore];
 
     final fileLineMatch = RegExp(
-      r'^(.*?)<!-- (?:file://./(\S+?)(?:#(\S+))?|(no-source-file)) -->\s*$',
+      r'^([ \t]*(?:(?:>[ \t]*)+|/{3}[ ]?)?)<!-- (?:file://./(\S+?)(?:#(\S+))?|(no-source-file)) -->\s*$',
     ).firstMatch(lineBeforeText);
 
     if (fileLineMatch == null) {
diff --git a/pkgs/jnigen/README.md b/pkgs/jnigen/README.md
index a5ca5de..2d37533 100644
--- a/pkgs/jnigen/README.md
+++ b/pkgs/jnigen/README.md
@@ -71,13 +71,16 @@
      final packageRoot = Platform.script.resolve('../');
      final generator = JniGenerator(
        input: Input(
-         sourcePath: [packageRoot.resolve('android/app/src/main/java')],
+         // Required. List of classes or packages for which bindings should be generated.
          classes: [
            'com.example.in_app_java', // Generate the entire package
            'androidx.emoji2.text.EmojiCompat', // From gradle's compile classpath
            'androidx.emoji2.text.DefaultEmojiCompatConfig', // From gradle's compile classpath
            'android.os.Build', // from gradle's compile classpath
          ],
+         // Optional. List of directories that contain the source files for which to generate bindings.
+         sourcePath: [packageRoot.resolve('android/app/src/main/java')],
+         // Optional. Configuration to search for Android SDK libraries.
          androidSdk: AndroidSdk(
            addGradleDeps: true,
            androidExample: packageRoot,
@@ -85,7 +88,9 @@
        ),
        output: Output(
          dart: DartOutput(
+           // Required. Output path for generated bindings.
            path: packageRoot.resolve('lib/android_utils.g.dart'),
+           // Optional. Write bindings into a single file (instead of one file per class).
            structure: OutputStructure.singleFile,
          ),
        ),
@@ -105,23 +110,12 @@
 
    <!-- file://./example/in_app_java/lib/main.dart#show_toast -->
    ```dart
-   /// Display device model number and the number of times this was called
-   /// as Toast.
    void showToast() {
-     final toastCount = hashmap.getOrDefault(
-       "toastCount".toJString(),
-       0.toJString(),
-     );
-     final newToastCount = (int.parse(toastCount!.toDartString()) + 1).toJString();
-     hashmap.put("toastCount".toJString(), newToastCount);
-     final emoji =
-         emojiCompat.hasEmojiGlyph(sunglassEmoji.toJString().as(CharSequence.type))
-             ? sunglassEmoji
-             : ':cool:';
-     final message =
-         '${newToastCount.toDartString()} - ${Build.MODEL!.toDartString()} $emoji';
+     final activity =
+         androidActivity(PlatformDispatcher.instance.engineId!)?.as(Activity.type);
+     final message = 'This is a native toast shown from a Flutter app via JNI.';
      AndroidUtils.showToast(
-       androidActivity(PlatformDispatcher.instance.engineId!)?.as(Activity.type),
+       activity,
        message.toJString().as(CharSequence.type),
        0,
      );
diff --git a/pkgs/jnigen/doc/lifecycle.md b/pkgs/jnigen/doc/lifecycle.md
index 8778f29..0e19fd7 100644
--- a/pkgs/jnigen/doc/lifecycle.md
+++ b/pkgs/jnigen/doc/lifecycle.md
@@ -79,13 +79,13 @@
 - When an original Java object is no longer needed, set `releaseOriginal` to
   `true` during conversion to Dart equivalents or casting.
 
-  <!-- no-source-file -->
+  <!-- file://./../example/api/lifecycle_snippet.dart#release_original -->
   ```dart
   final foo = Foo();
   final String string = foo.someJString().toDartString(releaseOriginal: true);
   final JInteger jint =
-        foo.someJNumber().as(JInteger.type, releaseOriginal: true);
-  final int dartInt = castedAsInteger.intValue(releaseOriginal: true);
+      foo.someJNumber().as(JInteger.type, releaseOriginal: true);
+  final int dartInt = jint.toDartInt(releaseOriginal: true);
   foo.release();
   // All references are removed.
   ```
@@ -103,13 +103,13 @@
 One could create cycles between Dart and Java GC's when implementing interfaces.
 For example consider the following:
 
-<!-- no-source-file -->
+<!-- file://./../example/api/lifecycle_snippet.dart#cycle -->
 ```dart
 final foo = Foo();
 foo.bar = Bar.implement($Bar(
   f: () {
-   return foo;
-  }
+    return foo;
+  },
 ));
 ```
 
@@ -127,17 +127,17 @@
 To prevent cycles, use
 [`WeakReference`](https://api.dart.dev/dart-core/WeakReference-class.html)s.
 
-<!-- no-source-file -->
+<!-- file://./../example/api/lifecycle_snippet.dart#cycle_breaker -->
 ```dart
 final weakFoo = WeakReference(foo);
 foo.bar = Bar.implement($Bar(
   f: () {
     final foo = weakFoo.target;
     if (foo == null) {
-      throw StateError();
+      throw StateError('Foo was collected');
     }
     return foo;
-  }
+  },
 ));
 ```
 
@@ -156,7 +156,7 @@
 > overcapturing, implement your logic in a separate function or create a class
 > that implements `$Bar`.
 >
-> <!-- no-source-file -->
+> <!-- file://./../example/api/lifecycle_snippet.dart#cycle_breaker_class -->
 > ```dart
 > final class BarImpl with $Bar {
 >   final WeakReference<Foo> weakFoo;
@@ -164,16 +164,16 @@
 >   BarImpl(this.weakFoo);
 >
 >   @override
->   void f() {
+>   Foo f() {
 >     final foo = weakFoo.target;
 >     if (foo == null) {
->       throw StateError();
+>       throw StateError('Foo was collected');
 >     }
 >     return foo;
 >   }
 > }
 >
-> void main() {
+> void cycleBreakerClassMain(Foo foo) {
 >   final weakFoo = WeakReference(foo);
 >   foo.bar = Bar.implement(BarImpl(weakFoo));
 >   // ...
diff --git a/pkgs/jnigen/example/api/lifecycle_snippet.dart b/pkgs/jnigen/example/api/lifecycle_snippet.dart
index e173e27..18a2096 100644
--- a/pkgs/jnigen/example/api/lifecycle_snippet.dart
+++ b/pkgs/jnigen/example/api/lifecycle_snippet.dart
@@ -4,13 +4,45 @@
 
 // dart format width=74
 
-// ignore_for_file: avoid_print, unused_local_variable
+// ignore_for_file: avoid_dynamic_calls, avoid_print
+// ignore_for_file: non_constant_identifier_names, omit_local_variable_types
+// ignore_for_file: unused_local_variable
 
-import 'package:ffi/ffi.dart';
 import 'package:jni/jni.dart';
 
 dynamic JList;
 
+class Foo {
+  Bar? bar;
+
+  JString someJString() => throw UnimplementedError();
+
+  JObject someJNumber() => throw UnimplementedError();
+
+  void release() {}
+}
+
+class Bar {
+  Bar._();
+
+  factory Bar.implement($Bar impl) => Bar._();
+}
+
+abstract base mixin class $Bar {
+  factory $Bar({required Foo Function() f}) = _BarImpl;
+
+  Foo f();
+}
+
+final class _BarImpl with $Bar {
+  final Foo Function() _f;
+
+  _BarImpl({required Foo Function() f}) : _f = f;
+
+  @override
+  Foo f() => _f();
+}
+
 void releaseManualExample() {
   // snippet-start#release_manual
   // Construct the object.
@@ -47,3 +79,65 @@
   print(jstrings.length); // prints 2.
   // snippet-end#java_collections
 }
+
+void releaseOriginalExample() {
+  // snippet-start#release_original
+  final foo = Foo();
+  final String string = foo.someJString().toDartString(releaseOriginal: true);
+  final JInteger jint =
+      foo.someJNumber().as(JInteger.type, releaseOriginal: true);
+  final int dartInt = jint.toDartInt(releaseOriginal: true);
+  foo.release();
+  // All references are removed.
+  // snippet-end#release_original
+}
+
+void cycleExample() {
+  // snippet-start#cycle
+  final foo = Foo();
+  foo.bar = Bar.implement($Bar(
+    f: () {
+      return foo;
+    },
+  ));
+  // snippet-end#cycle
+}
+
+void cycleBreakerExample() {
+  final foo = Foo();
+  // snippet-start#cycle_breaker
+  final weakFoo = WeakReference(foo);
+  foo.bar = Bar.implement($Bar(
+    f: () {
+      final foo = weakFoo.target;
+      if (foo == null) {
+        throw StateError('Foo was collected');
+      }
+      return foo;
+    },
+  ));
+  // snippet-end#cycle_breaker
+}
+
+// snippet-start#cycle_breaker_class
+final class BarImpl with $Bar {
+  final WeakReference<Foo> weakFoo;
+
+  BarImpl(this.weakFoo);
+
+  @override
+  Foo f() {
+    final foo = weakFoo.target;
+    if (foo == null) {
+      throw StateError('Foo was collected');
+    }
+    return foo;
+  }
+}
+
+void cycleBreakerClassMain(Foo foo) {
+  final weakFoo = WeakReference(foo);
+  foo.bar = Bar.implement(BarImpl(weakFoo));
+  // ...
+}
+// snippet-end#cycle_breaker_class
diff --git a/pkgs/jnigen/example/in_app_java/lib/main.dart b/pkgs/jnigen/example/in_app_java/lib/main.dart
index 3c1e43c..82f8d12 100644
--- a/pkgs/jnigen/example/in_app_java/lib/main.dart
+++ b/pkgs/jnigen/example/in_app_java/lib/main.dart
@@ -26,9 +26,21 @@
 const sunglassEmoji = "😎";
 
 // snippet-start#show_toast
+void showToast() {
+  final activity =
+      androidActivity(PlatformDispatcher.instance.engineId!)?.as(Activity.type);
+  final message = 'This is a native toast shown from a Flutter app via JNI.';
+  AndroidUtils.showToast(
+    activity,
+    message.toJString().as(CharSequence.type),
+    0,
+  );
+}
+// snippet-end#show_toast
+
 /// Display device model number and the number of times this was called
 /// as Toast.
-void showToast() {
+void showToastFancy() {
   final toastCount = hashmap.getOrDefault(
     "toastCount".toJString(),
     0.toJString(),
@@ -47,7 +59,6 @@
     0,
   );
 }
-// snippet-end#show_toast
 
 void main() {
   EmojiCompat.init(context.as(Context.type));
@@ -82,7 +93,7 @@
           children: [
             ElevatedButton(
               child: const Text('Show Device Model'),
-              onPressed: () => showToast(),
+              onPressed: () => showToastFancy(),
             ),
           ],
         ),
diff --git a/pkgs/jnigen/example/in_app_java/tool/jnigen.dart b/pkgs/jnigen/example/in_app_java/tool/jnigen.dart
index 35321b7..9d5fe67 100644
--- a/pkgs/jnigen/example/in_app_java/tool/jnigen.dart
+++ b/pkgs/jnigen/example/in_app_java/tool/jnigen.dart
@@ -6,13 +6,16 @@
   final packageRoot = Platform.script.resolve('../');
   final generator = JniGenerator(
     input: Input(
-      sourcePath: [packageRoot.resolve('android/app/src/main/java')],
+      // Required. List of classes or packages for which bindings should be generated.
       classes: [
         'com.example.in_app_java', // Generate the entire package
         'androidx.emoji2.text.EmojiCompat', // From gradle's compile classpath
         'androidx.emoji2.text.DefaultEmojiCompatConfig', // From gradle's compile classpath
         'android.os.Build', // from gradle's compile classpath
       ],
+      // Optional. List of directories that contain the source files for which to generate bindings.
+      sourcePath: [packageRoot.resolve('android/app/src/main/java')],
+      // Optional. Configuration to search for Android SDK libraries.
       androidSdk: AndroidSdk(
         addGradleDeps: true,
         androidExample: packageRoot,
@@ -20,7 +23,9 @@
     ),
     output: Output(
       dart: DartOutput(
+        // Required. Output path for generated bindings.
         path: packageRoot.resolve('lib/android_utils.g.dart'),
+        // Optional. Write bindings into a single file (instead of one file per class).
         structure: OutputStructure.singleFile,
       ),
     ),