This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 67e73b3cb425 chore: camel-jbang - source editor: a note for a direct: 
or seda: endpoint no route consumes on Java and XML routes too
67e73b3cb425 is described below

commit 67e73b3cb4256e075776288d843fa378ca5a3a1d
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Oct 23 12:52:01 2026 +0200

    chore: camel-jbang - source editor: a note for a direct: or seda: endpoint 
no route consumes on Java and XML routes too
    
    Java and XML route files now get the same note as YAML ones: a line that 
sends
    to a direct: or seda: endpoint no route of the project consumes is marked 
with
    a warning that does not block the save.
    
    The checks of a file move from SourceViewer to SourceChecks, which holds the
    validators and runs one sequence (the YAML one was written twice, for a save
    and for typing). What it finds is a Found of errors and notes, which also 
says
    whether the errors keep the file from being saved (YAML, properties and XML,
    not Java). A save still checks the YAML DSL schema, and leaves out the 
notes of
    a file whose errors keep it from being saved. The viewer has one notes hook 
for
    every route file (setNotes), set each time a file opens. SourceViewer 
shrinks
    from 3055 to 2953 lines.
    
    camel-jbang-core has EndpointConsumerChecks.validateRouteConsumers, which 
runs
    only the consumer check of ModelChecks against the endpoints the other route
    files consume, the set ConsumedEndpoints keeps.
    
    Found in a live run: the other route files are now read again each time a 
file
    opens (the set read before is shown meanwhile), and when a read finds other
    endpoints the editor checks its text at once (SourceViewer.recheck) rather 
than
    at the next change. The F1 help of the Source tab says what a note is.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../ROOT/pages/camel-jbang-tui-source-editor.adoc  |  12 +-
 .../core/commands/ai/EndpointConsumerChecks.java   |  23 +++
 .../dsl/jbang/core/commands/ai/ModelChecks.java    |  13 ++
 .../ai/SourceValidatorEndpointConsumersTest.java   |  36 ++++
 dsl/camel-jbang/camel-jbang-plugin-tui/AGENTS.md   |   2 +-
 .../jbang/core/commands/tui/ConsumedEndpoints.java |  35 +++-
 .../dsl/jbang/core/commands/tui/SourceChecks.java  | 181 ++++++++++++++++++
 .../jbang/core/commands/tui/SourceEditAssist.java  |   6 +-
 .../dsl/jbang/core/commands/tui/SourceTab.java     |  15 +-
 .../dsl/jbang/core/commands/tui/SourceViewer.java  | 202 +++++----------------
 .../src/main/resources/tui/help/source.md          |   4 +
 .../jbang/core/commands/tui/SourceChecksTest.java  |  99 ++++++++++
 .../core/commands/tui/SourceConsumerNotesTest.java |  98 ++++++++--
 .../commands/tui/SourceKameletValidationTest.java  |   2 +-
 .../jbang/core/commands/tui/SourceSizeTest.java    |   2 +-
 15 files changed, 542 insertions(+), 188 deletions(-)

diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-source-editor.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-source-editor.adoc
index 0de33d4d0efc..b020d717ec43 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-source-editor.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-source-editor.adoc
@@ -90,12 +90,12 @@ panel at the bottom says what is wrong with the line the 
cursor is on. A note, s
 counted apart in the title as `notes`. A note does not keep the file from 
being saved. They are checked again as you type, and *F9*
 jumps to the next problem.
 
-A YAML route that sends to a `direct` or `seda` endpoint no route consumes 
gets a note on that line, such as
-`to: direct:cheese` while no route has `from: direct:cheese`. The routes of 
the other YAML, Java and XML files of the
-project count too. They are read in the background when the file opens, and 
read again every few seconds. It is a
-note rather than an error, as the route that consumes it is often the next one 
you write. The check says nothing when
-the other files cannot tell, such as a route whose input is a property 
placeholder or a route template, or a project
-with more files than the check reads.
+A route that sends to a `direct` or `seda` endpoint no route consumes gets a 
note on that line, such as
+`to: direct:cheese` while no route has `from: direct:cheese`. This works for 
YAML, Java and XML routes, and the routes
+of the other YAML, Java and XML files of the project count too. They are read 
in the background each time a file
+opens, and again while you type once the last read is a few seconds old. It is 
a note rather than an error, as the
+route that consumes it is often the next one you write. The check says nothing 
when the other files cannot tell, such as a route whose input is a property
+placeholder or a route template, or a project with more files than the check 
reads.
 
 image::jbang/camel-tui-source-problems.png[A misspelled seda option marked 
while typing]
 
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointConsumerChecks.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointConsumerChecks.java
index 1d39f6fb9ae2..fd8f4277bc74 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointConsumerChecks.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointConsumerChecks.java
@@ -30,6 +30,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Supplier;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -97,6 +98,28 @@ public final class EndpointConsumerChecks {
         return answer;
     }
 
+    /**
+     * The Java and XML DSL twin of {@link #validateYamlConsumers(String, 
Set)}: the direct: and seda: endpoints the
+     * routes of the source send to and no route consumes, as "Line N: 
message" on the first line that sends to each.
+     *
+     * @param  javaSources       the Java sources of the project, for the 
constants a route refers to in another class
+     * @param  consumedElsewhere what {@link #consumed(Path, String, 
CamelCatalog)} returned; null says nothing
+     * @return                   the messages; none for a file that is not a 
Java or XML DSL source
+     */
+    public static List<String> validateRouteConsumers(
+            String fileName, String content, CamelCatalog catalog, Map<String, 
Supplier<String>> javaSources,
+            Set<String> consumedElsewhere) {
+        if (consumedElsewhere == null) {
+            return List.of();
+        }
+        RouteModel model = RouteModel.read(fileName, content, catalog, 
javaSources);
+        if (model == null) {
+            return List.of();
+        }
+        List<RouteNodes.Node> nodes = 
RouteNodes.withExpressionLines(RouteNodes.of(model, catalog), content);
+        return ModelChecks.consumers(model, nodes, content, catalog, 
consumedElsewhere);
+    }
+
     /** The lines, from 0, that name the endpoint (direct:cheese, also as 
direct://cheese), comments left out. */
     private static List<Integer> linesNaming(String[] lines, String endpoint) {
         int colon = endpoint.indexOf(':');
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ModelChecks.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ModelChecks.java
index 6ee6897bd72d..21ef8aa173cc 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ModelChecks.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ModelChecks.java
@@ -81,6 +81,19 @@ final class ModelChecks {
         return checks.errors;
     }
 
+    /**
+     * Only the direct: and seda: endpoints the routes send to and no route 
consumes, for an editor that marks them
+     * apart from the other problems: as notes, which do not keep a file from 
being saved.
+     *
+     * @param consumedElsewhere the endpoints the other route files consume; 
null says nothing
+     */
+    static List<String> consumers(
+            RouteModel model, List<Node> nodes, String content, CamelCatalog 
catalog, Set<String> consumedElsewhere) {
+        ModelChecks checks = new ModelChecks(model, nodes, content, catalog);
+        checks.consumers(consumedElsewhere);
+        return checks.errors;
+    }
+
     private void endpoint(Node n) {
         String uri = n.uri() != null ? n.uri().strip() : null;
         if (uri == null || uri.isEmpty() || RouteModel.hasUnknownValue(uri) || 
uri.startsWith("{{")) {
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointConsumersTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointConsumersTest.java
index 640e42bb9790..cc7262700a52 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointConsumersTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointConsumersTest.java
@@ -19,6 +19,7 @@ package org.apache.camel.dsl.jbang.core.commands.ai;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.camel.catalog.CamelCatalog;
@@ -314,4 +315,39 @@ class SourceValidatorEndpointConsumersTest {
         // the other files not known: nothing is said
         assertThat(EndpointConsumerChecks.validateYamlConsumers(route, 
null)).isEmpty();
     }
+
+    @Test
+    void theEditorGetsTheMessageOfAJavaOrXmlRouteOnItsLine() {
+        String java = """
+                import org.apache.camel.builder.RouteBuilder;
+
+                public class Tick extends RouteBuilder {
+                    @Override
+                    public void configure() {
+                        from("timer:tick")
+                            .to("direct:cheese")
+                            .to("direct:other");
+                    }
+                }
+                """;
+        Set<String> consumed = Set.of("direct:other");
+        assertThat(EndpointConsumerChecks.validateRouteConsumers("Tick.java", 
java, CATALOG, Map.of(), consumed))
+                .singleElement().asString()
+                .startsWith("Line 7: sends to direct:cheese, and no route 
consumes it")
+                .endsWith("add a route with from(\"direct:cheese\"), or 
correct the name");
+        String xml = """
+                <routes>
+                    <route>
+                        <from uri="timer:tick"/>
+                        <to uri="direct:cheese"/>
+                    </route>
+                </routes>
+                """;
+        
assertThat(EndpointConsumerChecks.validateRouteConsumers("tick.camel.xml", xml, 
CATALOG, Map.of(), consumed))
+                .singleElement().asString().startsWith("Line 4: sends to 
direct:cheese");
+        // the other files not known, or a file that is not a route source: 
nothing is said
+        assertThat(EndpointConsumerChecks.validateRouteConsumers("Tick.java", 
java, CATALOG, Map.of(), null)).isEmpty();
+        assertThat(EndpointConsumerChecks.validateRouteConsumers("pom.xml", 
"<project/>", CATALOG, Map.of(), consumed))
+                .isEmpty();
+    }
 }
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/AGENTS.md 
b/dsl/camel-jbang/camel-jbang-plugin-tui/AGENTS.md
index 412984c081b5..1b4095abd581 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/AGENTS.md
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/AGENTS.md
@@ -32,7 +32,7 @@ and use each other's package-private members, so there are no 
sub-packages. Clas
 | `DataRefreshService`, `StatusFileReader`, `StatusParser` | read the status 
files of the integrations (`~/.camel/<pid>-status.json`) into `IntegrationInfo` 
and the `*Info` classes |
 | `*Popup`, `PopupManager` | popups; `PopupManager` has the switch integration 
(F3), More and confirm popups |
 | `FilterListPopup`, `ChoicePopup`, `ActivityLogPopup`, `DialogHelper` | what 
popups are built on: a list narrowed down by typing, a short list of choices, a 
log with a detail panel, and centered confirm and input dialogs |
-| `SourceTab`, `SourceViewer`, `SourceEditAssist`, `*Completions`, `*QuickDoc` 
| the source editor; its Tab completion is `SourceCompletion`, its Ctrl+R 
refactorings `SourceRefactorActions`, the live run data at the end of the lines 
`SourceLiveColumn` |
+| `SourceTab`, `SourceViewer`, `SourceEditAssist`, `*Completions`, `*QuickDoc` 
| the source editor; its Tab completion is `SourceCompletion`, its Ctrl+R 
refactorings `SourceRefactorActions`, the live run data at the end of the lines 
`SourceLiveColumn`, the checks of a file (errors, and notes that do not block a 
save) `SourceChecks` |
 | `DiagramSupport`, `DiagramDetailSupport`, `ArchitectureView`, `diagram.*` | 
route and architecture diagrams; what they draw comes from `DiagramTopology` 
(`ctx.diagramTopology`), which asks an integration for the structure of its 
routes only when they change and takes the numbers from the status file |
 | `AbstractDiagramTab` | what the Diagram and Routes tabs share of the route 
diagram: its state, keys, drilling into a route and back |
 | `AiPanel`, `AiSlashCommandRegistry` | the F8 AI panel; its input line is 
`AiInputLine`, its tools and system prompt `AiToolSet`, its usage statistics 
`AiUsageStats`, and an external coding agent (ACP) `AcpSession` over 
`AcpAgentClient` |
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumedEndpoints.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumedEndpoints.java
index 7cbb40507c22..c0e5c7ea8b53 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumedEndpoints.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumedEndpoints.java
@@ -18,10 +18,13 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
 
 import java.nio.file.Path;
 import java.util.List;
+import java.util.Locale;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 import org.apache.camel.catalog.CamelCatalog;
 import org.apache.camel.dsl.jbang.core.commands.ai.EndpointConsumerChecks;
@@ -59,20 +62,28 @@ final class ConsumedEndpoints {
     }
 
     /**
-     * The notes of a YAML route file: each line that sends to a direct: or 
seda: endpoint no route of the project
-     * consumes, as "Line N: message"; none while the other route files are 
being read the first time.
+     * The notes of a YAML, Java or XML route file: each direct: or seda: 
endpoint it sends to that no route of the
+     * project consumes, as "Line N: message"; none while the other route 
files are being read the first time.
      *
-     * @param rootDir  the project directory, whose route files are read; the 
file's own directory when it is not in it
-     * @param onChange called on the UI thread when a read finds other 
endpoints, to mark the file again
+     * @param rootDir     the project directory, whose route files are read; 
the file's own directory when it is not in
+     *                    it
+     * @param javaSources the Java sources of the project, for the constants a 
Java route refers to in another class
+     * @param onChange    called on the UI thread when a read finds other 
endpoints, to mark the file again
      */
-    List<String> notes(Path file, Path rootDir, String content, CamelCatalog 
catalog, Runnable onChange) {
+    List<String> notes(
+            Path file, Path rootDir, String content, CamelCatalog catalog, 
Map<String, Supplier<String>> javaSources,
+            Runnable onChange) {
         Path f = file.toAbsolutePath().normalize();
         Path root = rootDir != null ? rootDir.toAbsolutePath().normalize() : 
null;
         if (root == null || !f.startsWith(root)) {
             root = f.getParent();
         }
-        return EndpointConsumerChecks.validateYamlConsumers(content,
-                get(root, root.relativize(f).toString(), catalog, onChange));
+        Set<String> consumed = get(root, root.relativize(f).toString(), 
catalog, onChange);
+        String name = f.getFileName().toString();
+        String lower = name.toLowerCase(Locale.ROOT);
+        return lower.endsWith(".yaml") || lower.endsWith(".yml")
+                ? EndpointConsumerChecks.validateYamlConsumers(content, 
consumed)
+                : EndpointConsumerChecks.validateRouteConsumers(name, content, 
catalog, javaSources, consumed);
     }
 
     /**
@@ -98,6 +109,16 @@ final class ConsumedEndpoints {
         return consumed;
     }
 
+    /**
+     * Has the next {@link #get} read the files again, keeping what was read 
meanwhile: a file was just opened, and the
+     * other files may have changed since they were read, in the TUI or 
outside it.
+     */
+    synchronized void expire() {
+        if (readTime != 0) {
+            readTime = 1;
+        }
+    }
+
     /** Reads the endpoints of the file asked about last; the caller holds the 
lock. */
     private void startRead() {
         reading = true;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecks.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecks.java
new file mode 100644
index 000000000000..7d5c9141b2a6
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecks.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.tui;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.networknt.schema.Error;
+import org.apache.camel.dsl.yaml.validator.YamlValidator;
+
+/**
+ * The checks of the file in the source editor, by file type: what they find 
is either an error or a note. An error of a
+ * YAML, properties or XML file keeps it from being saved; a Java file is the 
application's code, and is always saved
+ * with its errors said. A note is something that works but is not right: 
marked and said, never blocking a save.
+ * <p/>
+ * The same checks run when a file opens, while typing and on save. A save 
also checks a YAML file against the YAML DSL
+ * schema, and leaves out the notes of a file whose errors keep it from being 
saved.
+ */
+final class SourceChecks {
+
+    /** The kind of file, which decides the checks and whether an error blocks 
the save. */
+    enum Kind {
+        YAML,
+        PROPERTIES,
+        XML,
+        /** a Java route file, or a file the route checks do not read */
+        OTHER
+    }
+
+    /** What the checks found, each as "Line N: message", or a message of no 
line. */
+    record Found(List<String> errors, List<String> notes) {
+
+        static final Found NONE = new Found(List.of(), List.of());
+
+        /** The errors then the notes. */
+        List<String> all() {
+            if (notes.isEmpty()) {
+                return errors;
+            }
+            List<String> answer = new ArrayList<>(errors);
+            answer.addAll(notes);
+            return answer;
+        }
+
+        /** The problem of each line, by line from 0; a line with an error and 
a note shows the error. */
+        Map<Integer, String> byLine(String content) {
+            return SourceValidationSupport.buildInlineErrors(all(), content);
+        }
+
+        /** The lines, from 0, whose problem is a note. */
+        Set<Integer> noteLines(String content) {
+            if (notes.isEmpty()) {
+                return Set.of();
+            }
+            Set<Integer> answer = new 
HashSet<>(SourceValidationSupport.buildInlineErrors(notes, content).keySet());
+            answer.removeAll(SourceValidationSupport.buildInlineErrors(errors, 
content).keySet());
+            return answer;
+        }
+
+        /** Whether the errors keep a file of the kind from being saved. */
+        boolean blocksSave(Kind kind) {
+            return !errors.isEmpty() && kind != Kind.OTHER;
+        }
+    }
+
+    private YamlValidator yamlValidator;
+    private SourceViewer.PropertiesValidator propertiesValidator;
+    private SourceViewer.EndpointValidator endpointValidator;
+    private SourceViewer.EndpointValidator simpleValidator;
+    // the Kamelet checks of a YAML file (CAMEL-25411)
+    private SourceViewer.EndpointValidator kameletValidator;
+    // the Camel checks of a Java or XML DSL route file (CAMEL-25208)
+    private SourceViewer.EndpointValidator routeValidator;
+    // the notes of a YAML, Java or XML route file
+    private SourceViewer.EndpointValidator notes;
+
+    void setPropertiesValidator(SourceViewer.PropertiesValidator 
propertiesValidator) {
+        this.propertiesValidator = propertiesValidator;
+    }
+
+    void setEndpointValidator(SourceViewer.EndpointValidator 
endpointValidator) {
+        this.endpointValidator = endpointValidator;
+    }
+
+    void setSimpleValidator(SourceViewer.EndpointValidator simpleValidator) {
+        this.simpleValidator = simpleValidator;
+    }
+
+    void setKameletValidator(SourceViewer.EndpointValidator kameletValidator) {
+        this.kameletValidator = kameletValidator;
+    }
+
+    void setNotes(SourceViewer.EndpointValidator notes) {
+        this.notes = notes;
+    }
+
+    void setRouteValidator(SourceViewer.EndpointValidator routeValidator) {
+        this.routeValidator = routeValidator;
+    }
+
+    /** Forgets the checks of the file shown. */
+    void clear() {
+        propertiesValidator = null;
+        endpointValidator = null;
+        simpleValidator = null;
+        kameletValidator = null;
+        routeValidator = null;
+        notes = null;
+    }
+
+    /** The checks of a file just opened, or being typed. */
+    Found check(Kind kind, String content) {
+        return check(kind, content, false);
+    }
+
+    /** The checks of a file being saved: a YAML file is checked against the 
YAML DSL schema too. */
+    Found checkOnSave(Kind kind, String content) {
+        return check(kind, content, true);
+    }
+
+    private Found check(Kind kind, String content, boolean save) {
+        List<String> errors = new ArrayList<>();
+        if (kind == Kind.YAML) {
+            if (save) {
+                
errors.addAll(SourceValidationSupport.formatSchemaErrors(validateYaml(content), 
content));
+            }
+            addAll(errors, endpointValidator, content);
+            addAll(errors, simpleValidator, content);
+            addAll(errors, kameletValidator, content);
+        } else if (kind == Kind.PROPERTIES) {
+            if (propertiesValidator != null) {
+                
errors.addAll(SourceValidationSupport.validatePropertiesLines(content, 
propertiesValidator::validate));
+            }
+            return errors.isEmpty() ? Found.NONE : new Found(errors, 
List.of());
+        } else {
+            addAll(errors, routeValidator, content);
+        }
+        List<String> found = new ArrayList<>();
+        if (!save || !new Found(errors, List.of()).blocksSave(kind)) {
+            addAll(found, notes, content);
+        }
+        return errors.isEmpty() && found.isEmpty() ? Found.NONE : new 
Found(errors, found);
+    }
+
+    private static void addAll(List<String> msgs, 
SourceViewer.EndpointValidator validator, String content) {
+        if (validator != null) {
+            List<String> found = validator.validate(content);
+            if (found != null) {
+                msgs.addAll(found);
+            }
+        }
+    }
+
+    private List<Error> validateYaml(String content) {
+        try {
+            if (yamlValidator == null) {
+                yamlValidator = new YamlValidator();
+            }
+            return yamlValidator.validate(content);
+        } catch (Exception e) {
+            return List.of();
+        }
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssist.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssist.java
index bf939d460254..0950b9dec720 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssist.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssist.java
@@ -1737,8 +1737,8 @@ final class SourceEditAssist {
 
     /**
      * The Camel checks of a Java or XML DSL route file (CAMEL-25208): the 
endpoint uris and simple expressions the
-     * compiler cannot see, as "Line N: message". The endpoints no route 
consumes are left to camel validate: the editor
-     * runs this while typing, and would read the whole project each time.
+     * compiler cannot see, as "Line N: message". The endpoints no route 
consumes are notes of their own, as finding
+     * them reads the whole project: ConsumedEndpoints reads it in the 
background.
      */
     List<String> validateRoutes(Path file, String content) {
         List<String> answer = new ArrayList<>();
@@ -1826,7 +1826,7 @@ final class SourceEditAssist {
     }
 
     /** The Java sources of the project, for the constants a route refers to 
in another class; kept for a while. */
-    private Map<String, Supplier<String>> javaSources() {
+    Map<String, Supplier<String>> javaSources() {
         long now = MonotonicClock.millis();
         if (javaSourcesCache == null || now - javaSourcesCacheTime > 
JAVA_SOURCES_TTL_MS
                 || !Objects.equals(javaSourcesCacheDir, rootDir)) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
index 0d4fa880c135..35109301f038 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
@@ -921,15 +921,24 @@ class SourceTab extends AbstractTab {
         }
     }
 
+    /** The direct: and seda: endpoints the file sends to and no route of the 
project consumes, as notes. */
+    private List<String> consumerNotes(Path filePath, String content) {
+        return consumedEndpoints.notes(filePath, rootDir, content, 
assist.validationCatalog(), assist.javaSources(),
+                () -> sourceViewer.recheck(filePath));
+    }
+
     /**
      * Points the viewer's quick-doc, autocomplete, validation and deprecation 
hooks at the providers matching the file
      * type (Camel YAML, other Camel source, properties, or none).
      */
     private void configureEditAssist(Path filePath) {
+        consumedEndpoints.expire();
         // the Camel checks of a Java or XML DSL route file (CAMEL-25208); 
YAML has its own
         boolean routeFile = !SourceEditAssist.isYamlFile(filePath)
                 && (isJavaRouteFile(filePath) || isCamelSourceFile(filePath));
         sourceViewer.setRouteValidator(routeFile ? content -> 
assist.validateRoutes(filePath, content) : null);
+        // a direct: or seda: endpoint no route consumes is a note 
(CAMEL-24955); a YAML file sets its notes below
+        sourceViewer.setNotes(routeFile ? content -> consumerNotes(filePath, 
content) : null);
         sourceViewer.setAskAi(ctx.askAiCallback);
         String name = filePath.getFileName().toString();
         SourceCompletion completion = sourceViewer.completion();
@@ -964,10 +973,8 @@ class SourceTab extends AbstractTab {
                 // the shape of a Kamelet file and the kamelet: endpoints of a 
route (CAMEL-25411)
                 sourceViewer.setKameletValidator(content -> 
SourceEditAssist.validateKamelets(filePath, content));
                 // the notes: a Kamelet's unused dependency, a direct: or 
seda: endpoint no route consumes (CAMEL-24955)
-                sourceViewer.setYamlNotes(content -> 
SourceEditAssist.isKameletFile(filePath)
-                        ? SourceEditAssist.kameletNotes(filePath, content)
-                        : consumedEndpoints.notes(filePath, rootDir, content, 
assist.validationCatalog(),
-                                () -> sourceViewer.reloadIfShowing(filePath)));
+                sourceViewer.setNotes(content -> 
SourceEditAssist.isKameletFile(filePath)
+                        ? SourceEditAssist.kameletNotes(filePath, content) : 
consumerNotes(filePath, content));
                 completion.setListItemNodeChecker(assist::isListChildrenNode);
                 
sourceViewer.setEditQuickDocProvider(withProjectDocs(assist::provideEditQuickDoc));
             } else {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
index 42dd66f70836..0a06ded5bda5 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
@@ -23,7 +23,6 @@ import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -34,7 +33,6 @@ import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 
-import com.networknt.schema.Error;
 import dev.tamboui.layout.Constraint;
 import dev.tamboui.layout.Layout;
 import dev.tamboui.layout.Rect;
@@ -254,16 +252,10 @@ class SourceViewer {
     // the tree of the route the cursor is in, at the top right (Ctrl+T); the 
setting gives the default
     private boolean routeTreeShown;
     private final RouteTreePanel routeTree = new RouteTreePanel();
-    private org.apache.camel.dsl.yaml.validator.YamlValidator yamlValidator;
-    private PropertiesValidator propertiesValidator;
-    private EndpointValidator endpointValidator;
-    private EndpointValidator simpleValidator;
-    // the Kamelet checks of a YAML file (CAMEL-25411), and the notes of a 
YAML file that do not block a save
-    private EndpointValidator kameletValidator;
-    private EndpointValidator yamlNotes;
+    // the checks of the file by its type: the errors, and the notes that do 
not block a save
+    private final SourceChecks checks = new SourceChecks();
     // the lines whose problem is a note, not an error: marked as a warning 
and counted apart (CAMEL-25411)
     private Set<Integer> noteLines = Set.of();
-    private EndpointValidator routeValidator;
     private MonitorContext.AskAi askAi;
     private CursorQuickDocProvider cursorQuickDocProvider;
     private List<String> routeProblems = List.of();
@@ -328,15 +320,15 @@ class SourceViewer {
     }
 
     void setPropertiesValidator(PropertiesValidator propertiesValidator) {
-        this.propertiesValidator = propertiesValidator;
+        checks.setPropertiesValidator(propertiesValidator);
     }
 
     void setEndpointValidator(EndpointValidator endpointValidator) {
-        this.endpointValidator = endpointValidator;
+        checks.setEndpointValidator(endpointValidator);
     }
 
     void setSimpleValidator(EndpointValidator simpleValidator) {
-        this.simpleValidator = simpleValidator;
+        checks.setSimpleValidator(simpleValidator);
     }
 
     /**
@@ -345,15 +337,15 @@ class SourceViewer {
      * saved, as the other problems of a YAML file.
      */
     void setKameletValidator(EndpointValidator kameletValidator) {
-        this.kameletValidator = kameletValidator;
+        checks.setKameletValidator(kameletValidator);
     }
 
     /**
-     * What a YAML file does that works but is not right (a Kamelet's camel: 
dependency its template does not use, a
-     * direct: endpoint no route consumes): marked and said when saved, but 
not blocking the save.
+     * What a YAML, Java or XML route file does that works but is not right (a 
Kamelet's camel: dependency its template
+     * does not use, a direct: endpoint no route consumes): marked and said 
when saved, but not blocking the save.
      */
-    void setYamlNotes(EndpointValidator yamlNotes) {
-        this.yamlNotes = yamlNotes;
+    void setNotes(EndpointValidator notes) {
+        checks.setNotes(notes);
     }
 
     /**
@@ -362,7 +354,7 @@ class SourceViewer {
      * as a Java file is the application's code, and what the checks cannot 
know must never keep it from being saved.
      */
     void setRouteValidator(EndpointValidator routeValidator) {
-        this.routeValidator = routeValidator;
+        checks.setRouteValidator(routeValidator);
     }
 
     /**
@@ -384,13 +376,8 @@ class SourceViewer {
         onLineSelected = null;
         deprecatedLines = Collections.emptySet();
         editableFile = null;
-        propertiesValidator = null;
-        endpointValidator = null;
-        simpleValidator = null;
-        kameletValidator = null;
-        yamlNotes = null;
+        checks.clear();
         noteLines = Set.of();
-        routeValidator = null;
         cursorQuickDocProvider = null;
         completion.clearLanguageProviders();
     }
@@ -425,13 +412,8 @@ class SourceViewer {
         loadedFilePath = null;
         completion.reset();
         editableFile = null;
-        propertiesValidator = null;
-        endpointValidator = null;
-        simpleValidator = null;
-        kameletValidator = null;
-        yamlNotes = null;
+        checks.clear();
         noteLines = Set.of();
-        routeValidator = null;
         cursorQuickDocProvider = null;
     }
 
@@ -566,6 +548,19 @@ class SourceViewer {
         goToLine(Math.min(line, Math.max(0, getLineCount() - 1)));
     }
 
+    /**
+     * Checks the file shown again, as what its checks read besides the file 
changed (the endpoints the other route
+     * files consume): the view is loaded again, and the editor checks its 
text now, as it would after the next change.
+     */
+    void recheck(Path file) {
+        if (!editMode) {
+            reloadIfShowing(file);
+        } else if (validationErrors == null && file != null && editableFile != 
null
+                && 
editableFile.toAbsolutePath().normalize().equals(file.toAbsolutePath().normalize()))
 {
+            inlineErrors = validateContent(editState.text());
+        }
+    }
+
     int getLineCount() {
         if (editMode) {
             return editState.lineCount();
@@ -1249,62 +1244,24 @@ class SourceViewer {
     }
 
     private void validateAndNotify(String content) {
-        routeProblems = List.of();
-        if (validateOnSave && isCamelYamlFile()) {
-            List<String> msgs = new ArrayList<>();
-            
msgs.addAll(SourceValidationSupport.formatSchemaErrors(validateYaml(content), 
content));
-            if (endpointValidator != null) {
-                List<String> endpointErrors = 
endpointValidator.validate(content);
-                if (endpointErrors != null) {
-                    msgs.addAll(endpointErrors);
-                }
-            }
-            if (simpleValidator != null) {
-                List<String> simpleErrors = simpleValidator.validate(content);
-                if (simpleErrors != null) {
-                    msgs.addAll(simpleErrors);
-                }
-            }
-            addAll(msgs, kameletValidator, content);
-            noteLines = Set.of();
-            if (!msgs.isEmpty()) {
-                validationErrors = msgs;
-                validationErrorScroll = 0;
-                inlineErrors = buildInlineErrors(msgs, content);
-                return;
-            }
-            List<String> notes = new ArrayList<>();
-            addAll(notes, yamlNotes, content);
-            if (!notes.isEmpty()) {
-                // marked, and said when saved, but not blocking the save
-                routeProblems = notes;
-                inlineErrors = buildInlineErrors(notes, content);
-                noteLines = new HashSet<>(inlineErrors.keySet());
-                return;
-            }
-        } else if (validateOnSave && isPropertiesFile() && propertiesValidator 
!= null) {
-            List<String> msgs = validateProperties(content);
-            if (!msgs.isEmpty()) {
-                validationErrors = msgs;
-                validationErrorScroll = 0;
-                inlineErrors = buildInlineErrors(msgs, content);
-                return;
-            }
-        } else if (validateOnSave && routeValidator != null) {
-            List<String> msgs = routeValidator.validate(content);
-            msgs = msgs != null ? msgs : List.of();
-            inlineErrors = msgs.isEmpty() ? Collections.emptyMap() : 
buildInlineErrors(msgs, content);
-            if (isXmlFile() && !msgs.isEmpty()) {
-                // XML routes as YAML routes: the problems are shown and the 
file is not saved
-                validationErrors = msgs;
-                validationErrorScroll = 0;
-            } else {
-                // Java: marked, and said when saved, but not blocking the save
-                routeProblems = msgs;
-            }
-            return;
+        SourceChecks.Found found = validateOnSave ? checks.checkOnSave(kind(), 
content) : SourceChecks.Found.NONE;
+        inlineErrors = found.byLine(content);
+        noteLines = found.noteLines(content);
+        if (found.blocksSave(kind())) {
+            validationErrors = found.errors();
+            validationErrorScroll = 0;
+            routeProblems = List.of();
+        } else {
+            // a note, or the problems of a Java file: marked, and said when 
saved, but not blocking the save
+            routeProblems = found.all();
         }
-        inlineErrors = Collections.emptyMap();
+    }
+
+    /** The kind of the file edited, for its checks. */
+    private SourceChecks.Kind kind() {
+        return isCamelYamlFile() ? SourceChecks.Kind.YAML
+                : isPropertiesFile() ? SourceChecks.Kind.PROPERTIES
+                : isXmlFile() ? SourceChecks.Kind.XML : 
SourceChecks.Kind.OTHER;
     }
 
     /** The notice of a save: the file, and the Camel problems of a Java or 
XML route file when it has some. */
@@ -1494,48 +1451,14 @@ class SourceViewer {
         lastBackgroundValidationTime = now;
         lastBackgroundValidationContent = content;
 
-        List<String> msgs = validateContent(content);
-        inlineErrors = msgs.isEmpty() ? Collections.emptyMap() : 
buildInlineErrors(msgs, content);
+        inlineErrors = validateContent(content);
     }
 
-    /** The problems the checks of the file type (Camel YAML, properties, Java 
or XML routes) find in the content. */
-    private List<String> validateContent(String content) {
-        List<String> msgs = new ArrayList<>();
-        noteLines = Set.of();
-        if (isCamelYamlFile()) {
-            if (endpointValidator != null) {
-                List<String> endpointErrors = 
endpointValidator.validate(content);
-                if (endpointErrors != null) {
-                    msgs.addAll(endpointErrors);
-                }
-            }
-            if (simpleValidator != null) {
-                List<String> simpleErrors = simpleValidator.validate(content);
-                if (simpleErrors != null) {
-                    msgs.addAll(simpleErrors);
-                }
-            }
-            addAll(msgs, kameletValidator, content);
-            List<String> notes = new ArrayList<>();
-            addAll(notes, yamlNotes, content);
-            if (!notes.isEmpty()) {
-                // a line with an error is marked as an error
-                Set<Integer> lines = new HashSet<>(buildInlineErrors(notes, 
content).keySet());
-                if (!msgs.isEmpty()) {
-                    lines.removeAll(buildInlineErrors(msgs, content).keySet());
-                }
-                noteLines = lines;
-                msgs.addAll(notes);
-            }
-        } else if (isPropertiesFile() && propertiesValidator != null) {
-            msgs.addAll(validateProperties(content));
-        } else if (routeValidator != null) {
-            List<String> routeErrors = routeValidator.validate(content);
-            if (routeErrors != null) {
-                msgs.addAll(routeErrors);
-            }
-        }
-        return msgs;
+    /** The problems the checks of the file type find in the content, by line; 
the lines of the notes are kept apart. */
+    private Map<Integer, String> validateContent(String content) {
+        SourceChecks.Found found = checks.check(kind(), content);
+        noteLines = found.noteLines(content);
+        return found.byLine(content);
     }
 
     /** The title spans counting the problems of the lines: the errors, and 
apart from them the notes. */
@@ -1562,23 +1485,13 @@ class SourceViewer {
         return noteLines;
     }
 
-    private static void addAll(List<String> msgs, EndpointValidator validator, 
String content) {
-        if (validator != null) {
-            List<String> found = validator.validate(content);
-            if (found != null) {
-                msgs.addAll(found);
-            }
-        }
-    }
-
     /** The problems of the file just loaded, by line; none when the checks 
fail or do not apply. */
     private Map<Integer, String> problemsOnLoad(String content) {
         if (isMarkdownFile) {
             return Collections.emptyMap();
         }
         try {
-            List<String> msgs = validateContent(content);
-            return msgs.isEmpty() ? Collections.emptyMap() : 
buildInlineErrors(msgs, content);
+            return validateContent(content);
         } catch (RuntimeException e) {
             return Collections.emptyMap();
         }
@@ -1612,21 +1525,6 @@ class SourceViewer {
         return visible != null ? visible : inlineErrors;
     }
 
-    private List<String> validateProperties(String content) {
-        return SourceValidationSupport.validatePropertiesLines(content, 
propertiesValidator::validate);
-    }
-
-    private List<Error> validateYaml(String content) {
-        try {
-            if (yamlValidator == null) {
-                yamlValidator = new 
org.apache.camel.dsl.yaml.validator.YamlValidator();
-            }
-            return yamlValidator.validate(content);
-        } catch (Exception e) {
-            return List.of();
-        }
-    }
-
     private void notifySave(String message, boolean error) {
         if (notificationCallback != null) {
             notificationCallback.accept(message, error);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/source.md 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/source.md
index 563f893b0ca8..20faa055005f 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/source.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/source.md
@@ -31,6 +31,10 @@ explorer on the left and a source viewer on the right.
 - The problems of a Camel file are marked as soon as it opens: a red ✗ on
   their lines and the count in the title; the panel at the bottom says the
   problem of the selected line, and **F9** goes to the next one
+- A note, something that works but is not right, is a yellow ⚠ instead, counted
+  apart as `notes`, and does not keep the file from being saved: a `direct:` or
+  `seda:` endpoint no route of the project consumes, a dependency a Kamelet 
does
+  not use
 - **u** — usages: the routes that consume from the endpoint of the line and the
   steps that send to it (direct:, seda:...), across the project; **Enter** 
goes there
 - **/** — search in source
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecksTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecksTest.java
new file mode 100644
index 000000000000..f6cc0ba1c4e3
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecksTest.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.tui;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * The checks of the source editor by file type: errors and notes, and which 
errors keep a file from being saved.
+ */
+class SourceChecksTest {
+
+    private static final String YAML = """
+            - route:
+                from:
+                  uri: timer:tick
+                  steps:
+                    - to: direct:cheese
+            """;
+
+    @Test
+    void anErrorAndANoteOnTheirLines() {
+        SourceChecks checks = new SourceChecks();
+        checks.setEndpointValidator(content -> List.of("Line 3: timer: unknown 
option"));
+        checks.setNotes(content -> List.of("Line 5: no route consumes 
direct:cheese", "Line 3: also a note"));
+        SourceChecks.Found found = checks.check(SourceChecks.Kind.YAML, YAML);
+        assertThat(found.errors()).containsExactly("Line 3: timer: unknown 
option");
+        assertThat(found.notes()).hasSize(2);
+        // a line with an error and a note shows the error, and is not a note 
line
+        assertThat(found.byLine(YAML)).containsEntry(2, "timer: unknown 
option")
+                .containsEntry(4, "no route consumes direct:cheese");
+        assertThat(found.noteLines(YAML)).containsOnly(4);
+    }
+
+    @Test
+    void aSaveChecksTheSchemaAndLeavesOutTheNotesOfAFileItDoesNotSave() {
+        SourceChecks checks = new SourceChecks();
+        checks.setNotes(content -> List.of("Line 5: no route consumes 
direct:cheese"));
+        String misspelled = YAML.replace("steps:", "stepz:");
+        // while typing: no schema check, and the note
+        assertThat(checks.check(SourceChecks.Kind.YAML, 
misspelled).errors()).isEmpty();
+        assertThat(checks.check(SourceChecks.Kind.YAML, 
misspelled).notes()).hasSize(1);
+        // on save: the schema error, which keeps the file from being saved, 
and no note
+        SourceChecks.Found found = checks.checkOnSave(SourceChecks.Kind.YAML, 
misspelled);
+        assertThat(found.errors()).isNotEmpty();
+        assertThat(found.notes()).isEmpty();
+        assertThat(found.blocksSave(SourceChecks.Kind.YAML)).isTrue();
+        // a valid file is saved with its note
+        SourceChecks.Found valid = checks.checkOnSave(SourceChecks.Kind.YAML, 
YAML);
+        assertThat(valid.errors()).isEmpty();
+        assertThat(valid.notes()).hasSize(1);
+        assertThat(valid.blocksSave(SourceChecks.Kind.YAML)).isFalse();
+    }
+
+    @Test
+    void theErrorsOfAJavaFileDoNotBlockItsSave() {
+        SourceChecks checks = new SourceChecks();
+        checks.setRouteValidator(content -> List.of("Line 1: direct: unknown 
option"));
+        SourceChecks.Found java = checks.checkOnSave(SourceChecks.Kind.OTHER, 
"from(\"direct:a?foo=1\")");
+        assertThat(java.errors()).hasSize(1);
+        assertThat(java.blocksSave(SourceChecks.Kind.OTHER)).isFalse();
+        assertThat(checks.checkOnSave(SourceChecks.Kind.XML, "<from 
uri=\"direct:a?foo=1\"/>")
+                .blocksSave(SourceChecks.Kind.XML)).isTrue();
+        // a Java file is saved with its errors, so its notes are said on save 
too; an XML file's errors keep them out
+        checks.setNotes(content -> List.of("Line 1: no route consumes 
direct:a"));
+        assertThat(checks.checkOnSave(SourceChecks.Kind.OTHER, 
"x").notes()).hasSize(1);
+        assertThat(checks.checkOnSave(SourceChecks.Kind.XML, 
"x").notes()).isEmpty();
+        assertThat(checks.check(SourceChecks.Kind.XML, 
"x").notes()).hasSize(1);
+        // the checks of the file forgotten: nothing found
+        checks.clear();
+        assertThat(checks.check(SourceChecks.Kind.OTHER, 
"x")).isSameAs(SourceChecks.Found.NONE);
+    }
+
+    @Test
+    void aPropertiesFileIsCheckedLineByLine() {
+        SourceChecks checks = new SourceChecks();
+        checks.setPropertiesValidator(line -> line.startsWith("camel.bad") ? 
"unknown option" : null);
+        SourceChecks.Found found = checks.check(SourceChecks.Kind.PROPERTIES, 
"camel.main.name=x\ncamel.bad=1\n");
+        assertThat(found.byLine("")).containsOnlyKeys(1);
+        assertThat(found.blocksSave(SourceChecks.Kind.PROPERTIES)).isTrue();
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceConsumerNotesTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceConsumerNotesTest.java
index ff9a16cda9cd..2fdcc5874d03 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceConsumerNotesTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceConsumerNotesTest.java
@@ -22,6 +22,7 @@ import java.nio.file.Path;
 import java.util.ArrayDeque;
 import java.util.Deque;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -37,9 +38,9 @@ import org.junit.jupiter.api.io.TempDir;
 import static org.assertj.core.api.Assertions.assertThat;
 
 /**
- * A line of a YAML route that sends to a direct: or seda: endpoint no route 
of the project consumes is marked as a note
- * in the Source tab (CAMEL-24955): a warning that does not block the save, as 
the consuming route may not be written
- * yet. The other route files are read on a background thread, run here by 
hand.
+ * A line of a YAML, Java or XML route that sends to a direct: or seda: 
endpoint no route of the project consumes is
+ * marked as a note in the Source tab (CAMEL-24955): a warning that does not 
block the save, as the consuming route may
+ * not be written yet. The other route files are read on a background thread, 
run here by hand.
  */
 class SourceConsumerNotesTest {
 
@@ -67,6 +68,7 @@ class SourceConsumerNotesTest {
     Path tempDir;
 
     private final Deque<Runnable> background = new ArrayDeque<>();
+    private final ConsumedEndpoints consumed = new 
ConsumedEndpoints(background::add, Runnable::run);
     private final AtomicReference<String> lastNotification = new 
AtomicReference<>();
 
     @BeforeEach
@@ -100,6 +102,30 @@ class SourceConsumerNotesTest {
                 .contains("sends to direct:cheese");
     }
 
+    @Test
+    void theNoteGoesInTheEditorWhenAnotherFileConsumesTheEndpoint() throws 
Exception {
+        Files.writeString(tempDir.resolve("other.camel.yaml"), OTHER, 
StandardCharsets.UTF_8);
+        Path file = tempDir.resolve("r.camel.yaml");
+        Files.writeString(file, ROUTE, StandardCharsets.UTF_8);
+        SourceViewer viewer = viewer(file);
+        viewer.loadFile(file);
+        runBackground();
+        viewer.enterEditMode();
+        assertThat(viewer.noteLines()).containsOnly(lineOf(ROUTE, 
"direct:cheese"));
+        // another file consumes it now, and a file was opened: the next check 
reads the files again
+        Files.writeString(tempDir.resolve("cheese.camel.yaml"), 
OTHER.replace("direct:other", "direct:cheese"),
+                StandardCharsets.UTF_8);
+        consumed.expire();
+        viewer.recheck(file);
+        // until the read is done, what was read before
+        assertThat(viewer.noteLines()).isNotEmpty();
+        runBackground();
+        // the editor checks its text again, without a change of it
+        assertThat(viewer.inlineErrors()).isEmpty();
+        assertThat(viewer.noteLines()).isEmpty();
+        assertThat(viewer.isDirty()).isFalse();
+    }
+
     @Test
     void theNoteGoesWhenARouteConsumesTheEndpoint() throws Exception {
         Files.writeString(tempDir.resolve("other.camel.yaml"), OTHER, 
StandardCharsets.UTF_8);
@@ -119,31 +145,78 @@ class SourceConsumerNotesTest {
         assertThat(viewer.noteLines()).isEmpty();
     }
 
+    @Test
+    void aJavaOrXmlRouteGetsTheNoteToo() throws Exception {
+        Files.writeString(tempDir.resolve("other.camel.yaml"), OTHER, 
StandardCharsets.UTF_8);
+        String java = """
+                import org.apache.camel.builder.RouteBuilder;
+
+                public class Tick extends RouteBuilder {
+                    @Override
+                    public void configure() {
+                        from("timer:tick")
+                            .to("direct:cheese")
+                            .to("direct:other");
+                    }
+                }
+                """;
+        Path file = tempDir.resolve("Tick.java");
+        Files.writeString(file, java, StandardCharsets.UTF_8);
+        SourceViewer viewer = viewer(file);
+        viewer.loadFile(file);
+        runBackground();
+        int cheese = lineOf(java, "direct:cheese");
+        assertThat(viewer.viewErrors()).containsOnlyKeys(cheese);
+        assertThat(viewer.viewErrors().get(cheese)).startsWith("sends to 
direct:cheese, and no route consumes it");
+        assertThat(viewer.noteLines()).containsOnly(cheese);
+
+        String xml = """
+                <routes>
+                    <route>
+                        <from uri="timer:tick"/>
+                        <to uri="direct:cheese"/>
+                    </route>
+                </routes>
+                """;
+        Path xmlFile = tempDir.resolve("tick.camel.xml");
+        Files.writeString(xmlFile, xml, StandardCharsets.UTF_8);
+        SourceViewer xmlViewer = viewer(xmlFile);
+        xmlViewer.loadFile(xmlFile);
+        runBackground();
+        assertThat(xmlViewer.noteLines()).containsOnly(lineOf(xml, 
"direct:cheese"));
+        // a note does not keep an XML file from being saved, as an error would
+        xmlViewer.enterEditMode();
+        appendSpaceToLine(xmlViewer, 1);
+        xmlViewer.handleKeyEvent(KeyEvent.ofChar('s', KeyModifiers.CTRL));
+        assertThat(Files.readString(xmlFile, 
StandardCharsets.UTF_8)).contains("<route> ");
+        assertThat(lastNotification.get()).startsWith("Saved: tick.camel.xml 
with 1 Camel problem: ");
+    }
+
     @Test
     void theOtherFilesAreReadOnceAndAgainForAnotherFile() throws Exception {
         Files.writeString(tempDir.resolve("other.camel.yaml"), OTHER, 
StandardCharsets.UTF_8);
         ConsumedEndpoints consumed = new ConsumedEndpoints(background::add, 
Runnable::run);
         AtomicInteger changes = new AtomicInteger();
         Path file = tempDir.resolve("r.camel.yaml");
-        assertThat(consumed.notes(file, tempDir, ROUTE, CATALOG, 
changes::incrementAndGet)).isEmpty();
+        assertThat(consumed.notes(file, tempDir, ROUTE, CATALOG, Map.of(), 
changes::incrementAndGet)).isEmpty();
         assertThat(background).hasSize(1);
         // asked again while reading: no second read
-        consumed.notes(file, tempDir, ROUTE, CATALOG, 
changes::incrementAndGet);
+        consumed.notes(file, tempDir, ROUTE, CATALOG, Map.of(), 
changes::incrementAndGet);
         assertThat(background).hasSize(1);
         runBackground();
         assertThat(changes).hasValue(1);
-        assertThat(consumed.notes(file, tempDir, ROUTE, CATALOG, 
changes::incrementAndGet))
+        assertThat(consumed.notes(file, tempDir, ROUTE, CATALOG, Map.of(), 
changes::incrementAndGet))
                 .singleElement().asString().startsWith("Line 6: route tick: 
sends to direct:cheese");
         // read a moment ago: kept
         assertThat(background).isEmpty();
 
         // the other file opened before the read of the first one is done: 
that read is followed by one for it
         Path other = tempDir.resolve("other.camel.yaml");
-        consumed.notes(file, tempDir, ROUTE, CATALOG, 
changes::incrementAndGet);
+        consumed.notes(file, tempDir, ROUTE, CATALOG, Map.of(), 
changes::incrementAndGet);
         Files.writeString(tempDir.resolve("cheese.camel.yaml"), 
OTHER.replace("direct:other", "direct:cheese"),
                 StandardCharsets.UTF_8);
         consumed.get(tempDir, "nowhere.camel.yaml", CATALOG, 
changes::incrementAndGet);
-        assertThat(consumed.notes(other, tempDir, OTHER, CATALOG, 
changes::incrementAndGet)).isEmpty();
+        assertThat(consumed.notes(other, tempDir, OTHER, CATALOG, Map.of(), 
changes::incrementAndGet)).isEmpty();
         runBackground();
         assertThat(consumed.get(tempDir, "other.camel.yaml", CATALOG, 
changes::incrementAndGet))
                 .containsOnly("direct:cheese");
@@ -158,19 +231,18 @@ class SourceConsumerNotesTest {
         Files.writeString(elsewhere.resolve("other.camel.yaml"), OTHER, 
StandardCharsets.UTF_8);
         ConsumedEndpoints consumed = new ConsumedEndpoints(background::add, 
Runnable::run);
         Path file = elsewhere.resolve("r.camel.yaml");
-        consumed.notes(file, project, ROUTE, CATALOG, () -> {
+        consumed.notes(file, project, ROUTE, CATALOG, Map.of(), () -> {
         });
         runBackground();
-        assertThat(consumed.notes(file, project, ROUTE, CATALOG, () -> {
+        assertThat(consumed.notes(file, project, ROUTE, CATALOG, Map.of(), () 
-> {
         })).singleElement().asString().contains("sends to direct:cheese");
     }
 
     private SourceViewer viewer(Path file) {
         SourceViewer viewer = new SourceViewer();
-        ConsumedEndpoints consumed = new ConsumedEndpoints(background::add, 
Runnable::run);
         viewer.setNotificationCallback((msg, error) -> 
lastNotification.set(msg));
-        viewer.setYamlNotes(content -> consumed.notes(file, tempDir, content, 
CATALOG,
-                () -> viewer.reloadIfShowing(file)));
+        viewer.setNotes(content -> consumed.notes(file, tempDir, content, 
CATALOG, Map.of(),
+                () -> viewer.recheck(file)));
         return viewer;
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceKameletValidationTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceKameletValidationTest.java
index d176687e28ed..1ebe851dc05e 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceKameletValidationTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceKameletValidationTest.java
@@ -265,7 +265,7 @@ class SourceKameletValidationTest {
         SourceViewer viewer = new SourceViewer();
         viewer.setNotificationCallback((msg, error) -> 
lastNotification.set(msg));
         viewer.setKameletValidator(content -> 
SourceEditAssist.validateKamelets(file, content));
-        viewer.setYamlNotes(content -> SourceEditAssist.kameletNotes(file, 
content));
+        viewer.setNotes(content -> SourceEditAssist.kameletNotes(file, 
content));
         return viewer;
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceSizeTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceSizeTest.java
index c36832270de2..f6c8c6296167 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceSizeTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceSizeTest.java
@@ -76,7 +76,7 @@ class SourceSizeTest {
             SendMessagePopup.handleKeyEvent 220
             SourceEditAssist.java 2006
             SourceViewer.handleEditKeyEvent 231
-            SourceViewer.java 3055
+            SourceViewer.java 2953
             SourceViewer.renderContent 207
             SourceViewer.renderEditMode 256
             TuiToolDefinitions.addInteractionTools 183

Reply via email to