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 937f2db3d8c1 CAMEL-25771: camel-jbang - the Routes and Diagram views 
show the revision and checksum of a route, and the Source view whether the file 
runs as saved (#28220)
937f2db3d8c1 is described below

commit 937f2db3d8c1cc6c3f977f92926669301f66e2bd
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Oct 23 10:45:15 2026 +0200

    CAMEL-25771: camel-jbang - the Routes and Diagram views show the revision 
and checksum of a route, and the Source view whether the file runs as saved 
(#28220)
    
    Builds on #28213: the Routes view shows the revision of a route that 
changed in this run, the Diagram Info panel its checksum and revision, and the 
Source view whether the file runs as saved. tui_get_table includes the checksum 
and revision.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../jbang/camel-tui-diagram-route-revision.png     | Bin 0 -> 243251 bytes
 .../images/jbang/camel-tui-routes-revision.png     | Bin 0 -> 285743 bytes
 .../jbang/camel-tui-source-running-as-saved.png    | Bin 0 -> 278430 bytes
 .../ROOT/pages/camel-jbang-tui-diagram.adoc        |  15 ++
 .../ROOT/pages/camel-jbang-tui-source-editor.adoc  |  19 ++
 .../core/commands/tui/DiagramDetailSupport.java    |  21 ++
 .../dsl/jbang/core/commands/tui/RouteInfo.java     |   4 +
 .../dsl/jbang/core/commands/tui/RoutesTab.java     |  26 ++-
 .../jbang/core/commands/tui/SourceRunState.java    | 248 +++++++++++++++++++++
 .../dsl/jbang/core/commands/tui/SourceTab.java     |  17 ++
 .../dsl/jbang/core/commands/tui/StatusParser.java  |   3 +
 .../src/main/resources/tui/help/diagram.md         |   9 +
 .../src/main/resources/tui/help/routes.md          |  11 +-
 .../src/main/resources/tui/help/source.md          |  15 ++
 .../core/commands/tui/RouteChecksumViewsTest.java  | 149 +++++++++++++
 .../core/commands/tui/SourceRunStateTest.java      | 232 +++++++++++++++++++
 16 files changed, 766 insertions(+), 3 deletions(-)

diff --git 
a/docs/user-manual/modules/ROOT/images/jbang/camel-tui-diagram-route-revision.png
 
b/docs/user-manual/modules/ROOT/images/jbang/camel-tui-diagram-route-revision.png
new file mode 100644
index 000000000000..2c099b2e975b
Binary files /dev/null and 
b/docs/user-manual/modules/ROOT/images/jbang/camel-tui-diagram-route-revision.png
 differ
diff --git 
a/docs/user-manual/modules/ROOT/images/jbang/camel-tui-routes-revision.png 
b/docs/user-manual/modules/ROOT/images/jbang/camel-tui-routes-revision.png
new file mode 100644
index 000000000000..dc3febdb641f
Binary files /dev/null and 
b/docs/user-manual/modules/ROOT/images/jbang/camel-tui-routes-revision.png 
differ
diff --git 
a/docs/user-manual/modules/ROOT/images/jbang/camel-tui-source-running-as-saved.png
 
b/docs/user-manual/modules/ROOT/images/jbang/camel-tui-source-running-as-saved.png
new file mode 100644
index 000000000000..b2b671a92195
Binary files /dev/null and 
b/docs/user-manual/modules/ROOT/images/jbang/camel-tui-source-running-as-saved.png
 differ
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-diagram.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-diagram.adoc
index 33693e0a9107..c50d9a84a421 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-diagram.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-diagram.adoc
@@ -217,6 +217,21 @@ table of the processors of the selected route.
 
 image::jbang/camel-tui-routes-body-types.png[The Routes tab: the orders route 
has four steps reached with bodies of more than one kind, and its processors 
list the types of each step]
 
+=== Checksum and Revision
+
+The Info panel of a route in the topology has its checksum: a short hash of 
the route as written, the same in every
+run (see xref:routes.adoc#_route_checksum_and_revision[Route Checksum and 
Revision]). `camel cmd route-structure` gives
+the checksum of the routes of a source file without running them, so you can 
tell whether a file has the route that
+runs. A route added again changed in this run, such as by a reload of its 
file, has its revision under it
+(`Revision: 3 · 2 changes`), and the Routes tab has it after the id of the 
route, dimmed: `orders (rev 3)`.
+
+image::jbang/camel-tui-diagram-route-revision.png[The Info panel of the orders 
route in the topology: its checksum, and revision 3 after two reloads changed 
it]
+
+image::jbang/camel-tui-routes-revision.png[The Routes tab: the orders route is 
at revision 3, and the audit route from the same file did not change]
+
+An integration of a Camel version before 4.24, or without camel-xml-io, has 
neither: `camel run` always has
+camel-xml-io, while a Spring Boot or Quarkus application may not.
+
 == Keyboard Shortcuts
 
 === Diagram Tab
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 6048fce36cb6..55a7117b77c2 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
@@ -9,6 +9,7 @@ routes.
 
 * <<_live_run_data,*Live run data*>> -- the exchanges and failures of each 
line of the running integration, next to
   the code.
+* <<_running_as_saved,*Running as saved*>> -- whether the integration runs the 
file as it is saved, or needs a restart.
 * <<_problems_as_you_type,*Problems as you type*>> -- endpoint options, Simple 
expressions and more are checked as soon
   as a file opens and while you type, in YAML, Java and XML routes; a YAML or 
XML file with problems is not saved.
 * <<_quick_fixes,*Quick fixes*>> -- *Shift+F9* applies the fix a problem 
names, such as an option typo.
@@ -54,6 +55,24 @@ In the example the timer fired 227 times; the `filter` let 
137 of those through
 The column keeps its width, so the code does not move as the numbers grow. It 
works for YAML, Java and XML routes, as
 `camel run` records the source line of every processor.
 
+== Running as Saved
+
+The bottom border of the source says whether the integration runs the routes 
of the file as it is saved:
+
+* `● running as saved` -- the integration loaded the file after it was last 
saved.
+* `○ reloading...` -- the file was saved a moment ago, and the integration 
runs in dev mode, which reloads it.
+* `○ not running yet` -- the file was saved after the integration loaded it. 
An integration that runs without `--dev`
+  does not reload a file when it is saved: restart it. The log of one in dev 
mode says why it did not reload the file.
+* `⚠ the reload failed` -- the integration runs the routes it ran before the 
reload, until a reload succeeds.
+
+image::jbang/camel-tui-source-running-as-saved.png[The bottom border of an 
edited route file: the integration runs without --dev, so the saved file is not 
running yet]
+
+The Source tab compares when the file was saved with when the integration 
loaded the routes it has from the file, and
+shows nothing for a file it has no route from. It does not load the file 
itself, as loading a route file runs parts of
+it, such as creating its beans. Edits not saved yet are not part of it: the 
title of the editor marks them with `*`.
+To compare a file with the running routes one by one, `camel cmd 
route-structure <file>` gives the checksum of each
+route of the file, and `camel get route --checksum` the checksum of each 
running route.
+
 == Problems as You Type
 
 The editor checks your routes while you type: endpoint URIs and their options 
against the Camel catalog, Simple
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramDetailSupport.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramDetailSupport.java
index 60d3358369a8..82d2ddaa7ce9 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramDetailSupport.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramDetailSupport.java
@@ -319,6 +319,7 @@ final class DiagramDetailSupport {
                         Span.styled(" Coverage:   ", Theme.muted()),
                         Span.raw(route.coverage)));
             }
+            addRevision(lines, route);
 
             lines.add(Line.from(Span.raw("")));
             int w = numWidth(route.total, route.failed, route.inflight);
@@ -389,6 +390,26 @@ final class DiagramDetailSupport {
         frame.renderWidget(paragraph, area);
     }
 
+    /**
+     * The checksum of the route as written, to compare with the checksum 
camel cmd route-structure gives for the routes
+     * of a file, and its revision when it was added again changed in this 
run, such as by a reload (CAMEL-25771).
+     */
+    static void addRevision(List<Line> lines, RouteInfo route) {
+        if (route.checksum == null) {
+            return;
+        }
+        lines.add(Line.from(
+                Span.styled(" Checksum:   ", Theme.muted()),
+                Span.raw(route.checksum)));
+        if (route.revision > 1) {
+            int changes = route.revision - 1;
+            lines.add(Line.from(
+                    Span.styled(" Revision:   ", Theme.muted()),
+                    Span.raw(String.valueOf(route.revision)),
+                    Span.styled(changes == 1 ? " · 1 change" : " · " + changes 
+ " changes", Theme.muted())));
+        }
+    }
+
     /**
      * A system outside the integration, selected in the topology: where it 
is, the route it is linked to, its counts.
      */
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RouteInfo.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RouteInfo.java
index cbb595c5f1a0..d9649e3a83bd 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RouteInfo.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RouteInfo.java
@@ -55,5 +55,9 @@ class RouteInfo {
     int variedBodyTypes;
     /** The steps reached with bodies of more than one kind, which need 
attention (CAMEL-25750). */
     int mixedBodyKinds;
+    /** The checksum of the route as written, the same in every run; null when 
unknown (CAMEL-25771). */
+    String checksum;
+    /** The revision of the route in this run: 1 when added, one more each 
time it is added again changed. */
+    int revision;
     final List<ProcessorInfo> processors = new ArrayList<>();
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
index 60c1a8f75ccf..9663de324354 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
@@ -451,7 +451,7 @@ class RoutesTab extends AbstractDiagramTab {
                     : Style.EMPTY;
 
             routeRows.add(Row.from(
-                    Cell.from(Span.styled(route.routeId != null ? 
route.routeId : "", Style.EMPTY.fg(Theme.accent()))),
+                    Cell.from(routeIdLine(route, 24)),
                     routeFromCell(route, aiDescriptions, Integer.MAX_VALUE),
                     rightCell(route.total > 0 ? 
formatDurationMs(route.meanTime) : "", 8,
                             topTimeStyle(route.meanTime)),
@@ -600,7 +600,7 @@ class RoutesTab extends AbstractDiagramTab {
 
             List<Cell> cells = new ArrayList<>(
                     List.of(
-                            
Cell.from(Span.styled(TuiHelper.truncate(route.routeId, 24), 
Style.EMPTY.fg(Theme.accent()))),
+                            Cell.from(routeIdLine(route, 24)),
                             routeFromCell(route, aiDescriptions, fromWidth),
                             Cell.from(Span.styled(route.state != null ? 
route.state : "", stateStyle)),
                             rightCell(formatThroughput(route.throughput), 8),
@@ -681,6 +681,23 @@ class RoutesTab extends AbstractDiagramTab {
                 .build();
     }
 
+    /**
+     * The route id, and the revision dimmed after it when the route was added 
again changed in this run, such as by a
+     * reload (CAMEL-25771): "orders (rev 3)" runs the third version of its 
route since the start. The id is cut to
+     * leave room for the revision.
+     */
+    static Line routeIdLine(RouteInfo route, int width) {
+        String id = route.routeId != null ? route.routeId : "";
+        Style idStyle = Style.EMPTY.fg(Theme.accent());
+        if (route.revision <= 1) {
+            return Line.from(Span.styled(TuiHelper.truncate(id, width), 
idStyle));
+        }
+        String rev = " (rev " + route.revision + ")";
+        return Line.from(
+                Span.styled(TuiHelper.truncate(id, Math.max(4, width - 
CharWidth.of(rev))), idStyle),
+                Span.styled(rev, Style.EMPTY.dim()));
+    }
+
     /**
      * The steps of a route reached with more than one body type, across the 
messages (CAMEL-25750): "⚠ 1 mixed · 2
      * varied" when some are reached with bodies of more than one kind, which 
need attention; empty when none varied.
@@ -1403,6 +1420,11 @@ class RoutesTab extends AbstractDiagramTab {
             if (ri.group != null) {
                 row.put("group", ri.group);
             }
+            // the route as written, to compare with its source or an earlier 
run, and its changes in this run
+            if (ri.checksum != null) {
+                row.put("checksum", ri.checksum);
+                row.put("revision", ri.revision);
+            }
             rows.add(row);
         }
         result.put("rows", rows);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceRunState.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceRunState.java
new file mode 100644
index 000000000000..76ee51aaed46
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceRunState.java
@@ -0,0 +1,248 @@
+/*
+ * 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.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+import dev.tamboui.layout.Rect;
+import dev.tamboui.terminal.Frame;
+import dev.tamboui.text.Line;
+import dev.tamboui.text.Span;
+import dev.tamboui.text.Text;
+import dev.tamboui.widgets.paragraph.Paragraph;
+import org.apache.camel.util.TimeUtils;
+import org.apache.camel.util.json.JsonObject;
+
+/**
+ * Whether the selected integration runs the routes of the file open in the 
source editor as the file is saved, which
+ * the Source tab shows on the editor (CAMEL-25771).
+ * <p/>
+ * The TUI does not load the file to tell. Loading a YAML or XML file with 
Camel runs parts of it, such as creating its
+ * beans, and an endpoint written with parameters is built by its component, 
which the TUI does not have, so the
+ * checksums would not be those of the running routes. It compares when the 
file was saved with when the integration
+ * loaded the routes it has from the file:
+ * <ul>
+ * <li>an integration that has not reloaded loaded them when it started (a 
route stopped and started again keeps what it
+ * loaded);</li>
+ * <li>after a reload, a route started after the save runs what is saved. The 
integration does not say when it loaded a
+ * route, so a route stopped and started again by hand after a reload is taken 
as loaded then.</li>
+ * </ul>
+ * A failed reload puts back the routes that ran before it, so until a reload 
succeeds the state is the reload error.
+ */
+final class SourceRunState {
+
+    /** How long after a save an integration in dev mode is taken to be 
reloading the file. */
+    static final long RELOAD_GRACE_MILLIS = 5000;
+
+    enum Kind {
+        /** every route the integration has from the file was loaded after the 
file was saved */
+        RUNNING,
+        /** saved after the routes were loaded, and the integration reloads on 
save: it is about to */
+        RELOADING,
+        /** the last reload failed, so the routes that ran before it run */
+        RELOAD_FAILED,
+        /** saved after the routes were loaded, and the integration did not 
load it again */
+        NOT_RUNNING
+    }
+
+    /**
+     * @param kind   the state
+     * @param routes the routes the integration has from the file
+     * @param stale  how many of them were loaded before the file was saved
+     * @param detail why not running, or the reload error; null when running
+     */
+    record State(Kind kind, int routes, int stale, String detail) {
+    }
+
+    private SourceRunState() {
+    }
+
+    /**
+     * @param  info         the selected integration
+     * @param  filePath     the file open in the editor
+     * @param  fileModified when the file was last saved, as the file system 
has it (the wall clock)
+     * @param  now          the wall clock when the status of the integration 
was read
+     * @return              the state, or null when the integration runs no 
route from the file
+     */
+    static State of(IntegrationInfo info, String filePath, long fileModified, 
long now) {
+        String name = LiveRunLines.nameOf(filePath);
+        if (info == null || name == null || fileModified <= 0) {
+            return null;
+        }
+        // an integration that has not reloaded loaded its routes when it 
started; after a reload, when each started
+        boolean reloaded = info.reloaded > 0 || info.reloadError != null || 
info.uptime <= 0;
+        int routes = 0;
+        int stale = 0;
+        for (RouteInfo r : info.routes) {
+            if (!"Started".equals(r.state) || LiveRunLines.lineOf(r.source, 
name) < 0) {
+                continue;
+            }
+            long loaded = info.uptime;
+            if (reloaded) {
+                long uptime = uptimeMillis(r.uptime);
+                if (uptime < 0) {
+                    continue;
+                }
+                loaded = Math.max(info.uptime, now - uptime);
+            }
+            routes++;
+            if (loaded < fileModified) {
+                stale++;
+            }
+        }
+        if (routes == 0) {
+            return null;
+        }
+        if (info.reloadError != null) {
+            return new State(Kind.RELOAD_FAILED, routes, stale, 
info.reloadError);
+        }
+        if (stale == 0) {
+            return new State(Kind.RUNNING, routes, 0, null);
+        }
+        if (info.devMode) {
+            if (now - fileModified < RELOAD_GRACE_MILLIS) {
+                return new State(Kind.RELOADING, routes, stale, null);
+            }
+            return new State(Kind.NOT_RUNNING, routes, stale, "not reloaded 
since it was saved");
+        }
+        return new State(Kind.NOT_RUNNING, routes, stale, "the integration 
runs without --dev, restart it");
+    }
+
+    /** The uptime of a route as the route console writes it (5s, 1m5s, 2h5m, 
1d3h); -1 when it has none. */
+    static long uptimeMillis(String uptime) {
+        if (uptime == null || uptime.isBlank()) {
+            return -1;
+        }
+        try {
+            return TimeUtils.toMilliSeconds(uptime.trim());
+        } catch (IllegalArgumentException e) {
+            return -1;
+        }
+    }
+
+    /** The state as the editor shows it: "● running as saved", "○ not running 
yet: ..." */
+    static Line line(State state) {
+        return switch (state.kind()) {
+            case RUNNING -> Line.from(Span.styled(TuiIcons.SELECTED + " 
running as saved", Theme.success()));
+            case RELOADING -> Line.from(Span.styled(TuiIcons.IDLE + " 
reloading...", Theme.muted()));
+            case RELOAD_FAILED -> Line.from(
+                    Span.styled(TuiIcons.WARN + " the reload failed", 
Theme.error()),
+                    Span.styled(": " + 
TuiHelper.fixControlChars(state.detail().lines().findFirst().orElse("")),
+                            Theme.muted()));
+            case NOT_RUNNING -> Line.from(
+                    Span.styled(TuiIcons.IDLE + " not running yet", 
Theme.warning()),
+                    Span.styled(": " + state.detail(), Theme.muted()));
+        };
+    }
+
+    /** The state for tui_get_table, so an AI agent can tell whether its edit 
runs. */
+    static JsonObject toJson(State state) {
+        JsonObject jo = new JsonObject();
+        jo.put("state", switch (state.kind()) {
+            case RUNNING -> "running";
+            case RELOADING -> "reloading";
+            case RELOAD_FAILED -> "reloadFailed";
+            case NOT_RUNNING -> "notRunning";
+        });
+        jo.put("routes", state.routes());
+        jo.put("routesLoadedBeforeSave", state.stale());
+        if (state.detail() != null) {
+            jo.put("detail", state.detail());
+        }
+        return jo;
+    }
+
+    /**
+     * What the Source tab keeps of it: the file it last drew, and the state 
computed for that file on the background
+     * thread of the data refresh, as it reads when the file was saved.
+     */
+    static final class Tracker {
+
+        private record Computed(String file, State state) {
+        }
+
+        private volatile String file;
+        private volatile Computed computed;
+
+        /** The file the source panel shows, on the UI thread when it draws; 
null when none is. */
+        void shown(String file) {
+            this.file = file;
+        }
+
+        /** Computes the state of the file shown, on the background thread of 
the data refresh. */
+        void refresh(IntegrationInfo info) {
+            String shown = file;
+            if (shown == null || info == null) {
+                computed = null;
+                return;
+            }
+            try {
+                // the file system keeps the time of the save by the wall 
clock, so it is compared with the wall clock
+                long modified = 
Files.getLastModifiedTime(Path.of(shown)).toMillis();
+                State state = of(info, shown, modified, 
System.currentTimeMillis());
+                computed = state != null ? new Computed(shown, state) : null;
+            } catch (IOException | RuntimeException e) {
+                computed = null;
+            }
+        }
+
+        void reset() {
+            file = null;
+            computed = null;
+        }
+
+        /** The state of the file shown; null when unknown, or when it was 
computed for another file. */
+        State current() {
+            Computed c = computed;
+            String shown = file;
+            return c != null && c.file().equals(shown) ? c.state() : null;
+        }
+
+        /** The state on the bottom border of the source panel, left of the 
cursor position of the editor. */
+        void render(Frame frame, Rect area) {
+            State state = current();
+            // the cursor position takes the right of the border (" row:999 
col:999 ")
+            int width = area.width() - 4 - 18;
+            if (state == null || area.height() < 3 || width < 12) {
+                return;
+            }
+            List<Span> spans = new ArrayList<>();
+            spans.add(Span.raw(" "));
+            spans.addAll(line(state).spans());
+            spans.add(Span.raw(" "));
+            Line line = Line.from(spans);
+            
frame.renderWidget(Paragraph.builder().text(Text.from(line)).build(),
+                    new Rect(area.x() + 2, area.y() + area.height() - 1, 
Math.min(width, line.width()), 1));
+        }
+
+        /** The file shown and its state, for tui_get_table. */
+        void addTo(JsonObject json) {
+            String shown = file;
+            if (shown != null) {
+                json.put("openFile", shown);
+                State state = current();
+                if (state != null) {
+                    json.put("runState", toJson(state));
+                }
+            }
+        }
+    }
+}
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 18c068bc338d..8eb377e8f88f 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
@@ -76,6 +76,7 @@ class SourceTab extends AbstractTab {
     private final ScrollbarState listScrollState = new ScrollbarState();
     private List<FilesBrowser.FileEntry> entries = Collections.emptyList();
     private final SourceViewer sourceViewer = new SourceViewer();
+    private final SourceRunState.Tracker runState = new 
SourceRunState.Tracker();
     private boolean focusOnViewer;
 
     private int leftPanelWidth = -1;
@@ -216,10 +217,18 @@ class SourceTab extends AbstractTab {
             lastSeenPid = ctx.selectedPid;
             refreshFiles();
         }
+        // the run state of the file shown, now rather than at the next 
refresh, which shows the previous one till then
+        ctx.backgroundExecutor.execute(() -> 
runState.refresh(ctx.findSelectedIntegration()));
+    }
+
+    @Override
+    public void refreshWhileVisible() {
+        runState.refresh(ctx.findSelectedIntegration());
     }
 
     @Override
     public void onIntegrationChanged() {
+        runState.reset();
         lastSeenPid = ctx.selectedPid;
         rootDir = null;
         assist.setRootDir(null);
@@ -565,6 +574,10 @@ class SourceTab extends AbstractTab {
         json.put("totalFiles", entries.size());
         json.put("focusOnViewer", focusOnViewer);
         json.put("viewerOpen", sourceViewer.isVisible());
+        // whether the integration runs the file open in the editor as it is 
saved, so an agent can tell its edit runs
+        // (computed now: tui_get_table may read this tab while another one is 
on screen, which it does not refresh)
+        runState.refresh(ctx.findSelectedIntegration());
+        runState.addTo(json);
         return json;
     }
 
@@ -1178,7 +1191,11 @@ class SourceTab extends AbstractTab {
             sourceViewer.setBorderStyle(sourceBorderStyle);
             sourceViewer.setFocused(focusOnViewer);
             sourceViewer.render(frame, area);
+            // whether the integration runs the file shown as it is saved, in 
the view and in the editor (CAMEL-25771)
+            runState.shown(sourceViewer.getCurrentFilePath());
+            runState.render(frame, area);
         } else {
+            runState.shown(null);
             List<Line> lines = emptySourceLines(routeIndex, rootDir);
 
             frame.renderWidget(
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
index 1ba8daab4040..59fc543e072c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
@@ -261,6 +261,9 @@ final class StatusParser {
                 // the steps reached with more than one body type, and with 
more than one kind (CAMEL-25750)
                 ri.variedBodyTypes = rj.getIntegerOrDefault("variedBodyTypes", 
0);
                 ri.mixedBodyKinds = rj.getIntegerOrDefault("mixedBodyKinds", 
0);
+                // the route as written, and how many times it was added again 
changed in this run (CAMEL-25771)
+                ri.checksum = rj.getString("checksum");
+                ri.revision = rj.getIntegerOrDefault("revision", 0);
 
                 Map<String, ?> rs = rj.getMap("statistics");
                 ri.statistics = rs;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/diagram.md 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/diagram.md
index 5d10011bb852..ecce7337aafb 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/diagram.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/diagram.md
@@ -65,6 +65,15 @@ When a route is selected, an **Info panel** appears on the 
left
 showing key metrics: state, uptime, throughput, exchange counts,
 and processing times.
 
+The Info panel also has the **checksum** of the route: a short hash of
+the route as written, the same in every run. `camel cmd route-structure`
+gives the checksum of the routes of a source file, so it tells whether a
+file has the route that runs. A route added again changed in this run,
+such as by a reload, has its **revision** too: `Revision: 3 · 2 changes`.
+An integration of a Camel version before 4.24, or without camel-xml-io, has
+neither: `camel run` always has camel-xml-io, while a Spring Boot or Quarkus
+application may not.
+
 Press `Enter` on a selected route to **drill down** into its
 internal EIP structure (the route diagram). Press `Esc` to
 return to the topology view.
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/routes.md 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/routes.md
index fc49acbdb236..e35f9ce0101a 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/routes.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/routes.md
@@ -6,7 +6,7 @@ they are sent to. A typical integration has multiple routes 
working together.
 
 ## Route Table Columns
 
-- **ROUTE** — Unique route identifier (e.g., `timer-to-log`, `seda-consumer`)
+- **ROUTE** — Unique route identifier (e.g., `timer-to-log`, `seda-consumer`). 
A route added again changed in this run, such as by a reload of its file, has 
its revision after the id, dimmed: `orders (rev 3)` runs the third version of 
its route since the integration started
 - **FROM** — The endpoint that triggers this route (e.g., `timer`, `kafka`, 
`file`). This is the source of messages
 - **STATUS** — Route state: `Started` (running), `Stopped` (not running), or 
`Suspended` (paused, can be resumed)
 - **MSG/S** or **MSG/M** — Current message throughput (per second or per 
minute) for this route. Configure in Settings (F2)
@@ -81,6 +81,15 @@ When a route is selected, an **Info panel** appears on the 
left
 showing key metrics: state, uptime, throughput, exchange counts,
 and processing times.
 
+The Info panel also has the **checksum** of the route: a short hash of
+the route as written, the same in every run. `camel cmd route-structure`
+gives the checksum of the routes of a source file, so it tells whether a
+file has the route that runs. A route added again changed in this run,
+such as by a reload, has its **revision** too: `Revision: 3 · 2 changes`.
+An integration of a Camel version before 4.24, or without camel-xml-io, has
+neither: `camel run` always has camel-xml-io, while a Spring Boot or Quarkus
+application may not.
+
 Press `Enter` on a selected route to **drill down** into its
 internal EIP structure (the route diagram). Press `Esc` to
 return to the topology view.
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 31323cc96049..563f893b0ca8 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
@@ -47,6 +47,21 @@ processors on each line do: the exchanges they handled 
(`79`), how many failed
 its width, so the code does not move as the numbers grow. The source reads as
 a heat map of the route: where messages go, where they fail.
 
+## Running as Saved
+The bottom border of the source says whether the integration runs the routes
+of the file as it is saved:
+- `● running as saved` — the integration loaded the file after it was saved
+- `○ reloading...` — saved a moment ago, and the integration reloads on save 
(`--dev`)
+- `○ not running yet` — saved after the integration loaded it: restart an
+  integration that runs without `--dev`, or see the log of one that did not
+  reload the file
+- `⚠ the reload failed` — the integration runs what it ran before the reload,
+  until a reload succeeds
+
+It compares when the file was saved with when the integration loaded the
+routes it has from it, and shows nothing for a file it has no route from.
+Edits not saved yet are not part of it: the title marks them with `*`.
+
 ## Edit Mode (Shortcuts)
 - **Ctrl+Z** — undo
 - **Ctrl+Y / Ctrl+Shift+Z** — redo
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/RouteChecksumViewsTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/RouteChecksumViewsTest.java
new file mode 100644
index 000000000000..bbf7358c9f63
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/RouteChecksumViewsTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import dev.tamboui.text.Line;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * The checksum and the revision of the routes in the Routes view, the Info 
panel of the diagram and tui_get_table
+ * (CAMEL-25771).
+ */
+class RouteChecksumViewsTest {
+
+    private MonitorContext ctx;
+    private IntegrationInfo info;
+
+    @BeforeEach
+    void setUp() {
+        Theme.resetForTesting();
+        info = new IntegrationInfo();
+        info.pid = "5678";
+        info.name = "orders";
+        ctx = new MonitorContext(new AtomicReference<>(List.of(info)), new 
AtomicReference<>(List.of()));
+        ctx.selectedPid = "5678";
+    }
+
+    @Test
+    void theStatusFileHasThem() {
+        JsonObject status = Jsoner.deserialize("""
+                {"routes": [
+                  {"routeId": "orders", "from": "timer:orders", "state": 
"Started", "checksum": "aa4535cb1725",
+                   "revision": 2},
+                  {"routeId": "stock", "from": "timer:stock", "state": 
"Started"}]}
+                """, (JsonObject) null);
+        IntegrationInfo parsed = new IntegrationInfo();
+
+        StatusParser.parseRoutes(parsed, status);
+
+        assertThat(parsed.routes.get(0).checksum).isEqualTo("aa4535cb1725");
+        assertThat(parsed.routes.get(0).revision).isEqualTo(2);
+        // an integration of an older Camel has neither
+        assertThat(parsed.routes.get(1).checksum).isNull();
+        assertThat(parsed.routes.get(1).revision).isZero();
+    }
+
+    @Test
+    void aRouteChangedInThisRunHasItsRevisionAfterTheId() {
+        route("orders", "aa4535cb1725", 3);
+        route("stock", "71afc6e9b46a", 1);
+
+        String rendered = TuiTestHelper.renderToString(new RoutesTab(ctx), 
160, 30);
+
+        assertThat(rendered).contains("orders (rev 3)");
+        assertThat(rendered).doesNotContain("stock (rev");
+        // not a column of hex
+        assertThat(rendered).doesNotContain("aa4535cb1725");
+    }
+
+    @Test
+    void aLongIdIsCutToLeaveRoomForTheRevision() {
+        RouteInfo route = route("a-route-with-a-rather-long-id", 
"aa4535cb1725", 12);
+
+        assertThat(text(RoutesTab.routeIdLine(route, 
24))).isEqualTo("a-route-with... (rev 12)");
+        assertThat(text(RoutesTab.routeIdLine(route("orders", null, 0), 
24))).isEqualTo("orders");
+    }
+
+    @Test
+    void theInfoPanelHasTheChecksumAndTheRevision() {
+        RouteInfo route = route("orders", "aa4535cb1725", 3);
+        List<Line> lines = new ArrayList<>();
+
+        DiagramDetailSupport.addRevision(lines, route);
+
+        
assertThat(lines).extracting(RouteChecksumViewsTest::text).containsExactly(
+                " Checksum:   aa4535cb1725",
+                " Revision:   3 · 2 changes");
+
+        lines.clear();
+        DiagramDetailSupport.addRevision(lines, route("stock", "71afc6e9b46a", 
2));
+        
assertThat(lines).extracting(RouteChecksumViewsTest::text).containsExactly(
+                " Checksum:   71afc6e9b46a",
+                " Revision:   2 · 1 change");
+
+        // not changed in this run: the checksum only
+        lines.clear();
+        DiagramDetailSupport.addRevision(lines, route("invoices", 
"0c5d1e6b2f9a", 1));
+        
assertThat(lines).extracting(RouteChecksumViewsTest::text).containsExactly(" 
Checksum:   0c5d1e6b2f9a");
+
+        // unknown (an older Camel): nothing
+        lines.clear();
+        DiagramDetailSupport.addRevision(lines, route("legacy", null, 0));
+        assertThat(lines).isEmpty();
+    }
+
+    @Test
+    void theTableHasThemForAnAgent() {
+        route("orders", "aa4535cb1725", 3);
+        route("legacy", null, 0);
+
+        JsonArray rows = (JsonArray) new 
RoutesTab(ctx).getTableDataAsJson().get("rows");
+
+        JsonObject orders = (JsonObject) rows.get(0);
+        assertThat(orders.getString("checksum")).isEqualTo("aa4535cb1725");
+        assertThat(orders.getInteger("revision")).isEqualTo(3);
+        JsonObject legacy = (JsonObject) rows.get(1);
+        assertThat(legacy).doesNotContainKeys("checksum", "revision");
+    }
+
+    private static String text(Line line) {
+        StringBuilder sb = new StringBuilder();
+        line.spans().forEach(span -> sb.append(span.content()));
+        return sb.toString();
+    }
+
+    private RouteInfo route(String id, String checksum, int revision) {
+        RouteInfo route = new RouteInfo();
+        route.routeId = id;
+        route.from = "timer:" + id;
+        route.state = "Started";
+        route.checksum = checksum;
+        route.revision = revision;
+        info.routes.add(route);
+        return route;
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceRunStateTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceRunStateTest.java
new file mode 100644
index 000000000000..9539dea0cc03
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceRunStateTest.java
@@ -0,0 +1,232 @@
+/*
+ * 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.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import dev.tamboui.text.Line;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Whether the integration runs the file open in the source editor as it is 
saved (CAMEL-25771): when the file was
+ * saved, against when the integration loaded the routes it has from the file.
+ */
+class SourceRunStateTest {
+
+    private static final long NOW = 1_800_000_000_000L;
+    private static final String FILE = "/work/orders.camel.yaml";
+
+    @TempDir
+    Path dir;
+
+    private IntegrationInfo info;
+
+    @BeforeEach
+    void setUp() {
+        Theme.resetForTesting();
+        info = new IntegrationInfo();
+        info.pid = "5678";
+        info.name = "orders";
+        // started a minute ago
+        info.uptime = NOW - 60_000;
+    }
+
+    @Test
+    void savedBeforeTheIntegrationStarted() {
+        route("orders", "orders.camel.yaml:3", "59s");
+        route("audit", "file:/work/orders.camel.yaml:20", "59s");
+
+        SourceRunState.State state = SourceRunState.of(info, FILE, NOW - 
3_600_000, NOW);
+
+        assertThat(state.kind()).isEqualTo(SourceRunState.Kind.RUNNING);
+        assertThat(state.routes()).isEqualTo(2);
+        
assertThat(text(SourceRunState.line(state))).isEqualTo(TuiIcons.SELECTED + " 
running as saved");
+    }
+
+    @Test
+    void savedAfterTheIntegrationStartedWithoutDevMode() {
+        route("orders", "orders.camel.yaml:3", "59s");
+
+        SourceRunState.State state = SourceRunState.of(info, FILE, NOW - 
10_000, NOW);
+
+        assertThat(state.kind()).isEqualTo(SourceRunState.Kind.NOT_RUNNING);
+        assertThat(state.stale()).isEqualTo(1);
+        assertThat(text(SourceRunState.line(state)))
+                .isEqualTo(TuiIcons.IDLE + " not running yet: the integration 
runs without --dev, restart it");
+    }
+
+    @Test
+    void aRouteStoppedAndStartedAgainKeepsWhatItLoaded() {
+        // started by hand 2s ago, but never reloaded: it runs what it loaded 
when the integration started
+        route("orders", "orders.camel.yaml:3", "2s");
+
+        SourceRunState.State state = SourceRunState.of(info, FILE, NOW - 
10_000, NOW);
+
+        assertThat(state.kind()).isEqualTo(SourceRunState.Kind.NOT_RUNNING);
+    }
+
+    @Test
+    void devModeIsAboutToReloadARecentSave() {
+        info.devMode = true;
+        route("orders", "orders.camel.yaml:3", "59s");
+
+        SourceRunState.State state = SourceRunState.of(info, FILE, NOW - 
1_000, NOW);
+
+        assertThat(state.kind()).isEqualTo(SourceRunState.Kind.RELOADING);
+        assertThat(text(SourceRunState.line(state))).isEqualTo(TuiIcons.IDLE + 
" reloading...");
+
+        // and when it did not
+        state = SourceRunState.of(info, FILE, NOW - 10_000, NOW);
+        assertThat(state.kind()).isEqualTo(SourceRunState.Kind.NOT_RUNNING);
+        assertThat(state.detail()).isEqualTo("not reloaded since it was 
saved");
+    }
+
+    @Test
+    void devModeReloadedAfterTheSave() {
+        info.devMode = true;
+        info.reloaded = 1;
+        route("orders", "orders.camel.yaml:3", "9s");
+
+        assertThat(SourceRunState.of(info, FILE, NOW - 10_000, 
NOW).kind()).isEqualTo(SourceRunState.Kind.RUNNING);
+
+        // the reload was before the save
+        assertThat(SourceRunState.of(info, FILE, NOW - 8_000, 
NOW).kind()).isEqualTo(SourceRunState.Kind.NOT_RUNNING);
+    }
+
+    @Test
+    void aFailedReloadPutsBackWhatRanBefore() {
+        info.devMode = true;
+        info.reloaded = 1;
+        info.reloadError = "Cannot load orders.camel.yaml: line 7 is not a 
route\n\tat org.apache.camel.Foo";
+        // the routes of before started again
+        route("orders", "orders.camel.yaml:3", "1s");
+
+        SourceRunState.State state = SourceRunState.of(info, FILE, NOW - 
3_000, NOW);
+
+        assertThat(state.kind()).isEqualTo(SourceRunState.Kind.RELOAD_FAILED);
+        assertThat(text(SourceRunState.line(state)))
+                .isEqualTo(TuiIcons.WARN + " the reload failed: Cannot load 
orders.camel.yaml: line 7 is not a route");
+    }
+
+    @Test
+    void onlyTheStartedRoutesOfTheFile() {
+        route("stock", "stock.camel.yaml:3", "59s");
+        route("orders", "orders.camel.yaml:3", "59s").state = "Stopped";
+
+        assertThat(SourceRunState.of(info, FILE, NOW - 10_000, NOW)).isNull();
+
+        // a file the integration has no route from
+        assertThat(SourceRunState.of(info, "/work/README.md", NOW - 10_000, 
NOW)).isNull();
+    }
+
+    @Test
+    void theUptimeOfARoute() {
+        assertThat(SourceRunState.uptimeMillis("0s")).isZero();
+        assertThat(SourceRunState.uptimeMillis("5s")).isEqualTo(5_000);
+        assertThat(SourceRunState.uptimeMillis("1m5s")).isEqualTo(65_000);
+        assertThat(SourceRunState.uptimeMillis("2h5m")).isEqualTo(7_500_000);
+        assertThat(SourceRunState.uptimeMillis("1d3h")).isEqualTo(97_200_000);
+        assertThat(SourceRunState.uptimeMillis("")).isEqualTo(-1);
+        assertThat(SourceRunState.uptimeMillis(null)).isEqualTo(-1);
+        assertThat(SourceRunState.uptimeMillis("soon")).isEqualTo(-1);
+    }
+
+    @Test
+    void theStateForAnAgent() {
+        route("orders", "orders.camel.yaml:3", "59s");
+
+        JsonObject json = SourceRunState.toJson(SourceRunState.of(info, FILE, 
NOW - 10_000, NOW));
+
+        assertThat(json.getString("state")).isEqualTo("notRunning");
+        assertThat(json.getInteger("routes")).isEqualTo(1);
+        assertThat(json.getInteger("routesLoadedBeforeSave")).isEqualTo(1);
+        assertThat(json.getString("detail")).isEqualTo("the integration runs 
without --dev, restart it");
+    }
+
+    @Test
+    void theSourceTabShowsItUnderTheFile() throws Exception {
+        Path file = Files.writeString(dir.resolve("orders.camel.yaml"), "- 
from:\n    uri: timer:orders\n");
+        long now = System.currentTimeMillis();
+        info.uptime = now - 60_000;
+        route("orders", "orders.camel.yaml:1", "59s");
+        Files.setLastModifiedTime(file, FileTime.fromMillis(now - 3_600_000));
+        MonitorContext ctx = new MonitorContext(new 
AtomicReference<>(List.of(info)), new AtomicReference<>(List.of()));
+        ctx.selectedPid = "5678";
+        SourceTab tab = new SourceTab(ctx);
+        // the integration is taken in on the first draw
+        TuiTestHelper.renderToString(tab, 120, 30);
+        tab.openFileForReplay(file);
+        TuiTestHelper.renderToString(tab, 120, 30);
+
+        tab.refreshWhileVisible();
+        String rendered = TuiTestHelper.renderToString(tab, 120, 30);
+        assertThat(rendered).contains(TuiIcons.SELECTED + " running as saved");
+        JsonObject runState = (JsonObject) 
tab.getTableDataAsJson().get("runState");
+        assertThat(runState.getString("state")).isEqualTo("running");
+
+        // saved again: the integration has not loaded it
+        Files.setLastModifiedTime(file, FileTime.fromMillis(now - 1_000));
+        tab.refreshWhileVisible();
+        rendered = TuiTestHelper.renderToString(tab, 120, 30);
+        assertThat(rendered).contains(TuiIcons.IDLE + " not running yet: the 
integration runs without --dev, restart it");
+    }
+
+    @Test
+    void tuiGetTableComputesItWhileAnotherTabIsOnScreen() throws Exception {
+        Path file = Files.writeString(dir.resolve("orders.camel.yaml"), "- 
from:\n    uri: timer:orders\n");
+        long now = System.currentTimeMillis();
+        info.uptime = now - 60_000;
+        route("orders", "orders.camel.yaml:1", "59s");
+        Files.setLastModifiedTime(file, FileTime.fromMillis(now - 3_600_000));
+        MonitorContext ctx = new MonitorContext(new 
AtomicReference<>(List.of(info)), new AtomicReference<>(List.of()));
+        ctx.selectedPid = "5678";
+        SourceTab tab = new SourceTab(ctx);
+        TuiTestHelper.renderToString(tab, 120, 30);
+        tab.openFileForReplay(file);
+        TuiTestHelper.renderToString(tab, 120, 30);
+
+        // the tab is not refreshed (it is not on screen), yet the table has 
the state of now
+        assertThat(((JsonObject) 
tab.getTableDataAsJson().get("runState")).getString("state")).isEqualTo("running");
+        Files.setLastModifiedTime(file, FileTime.fromMillis(now - 1_000));
+        assertThat(((JsonObject) 
tab.getTableDataAsJson().get("runState")).getString("state")).isEqualTo("notRunning");
+    }
+
+    private static String text(Line line) {
+        StringBuilder sb = new StringBuilder();
+        line.spans().forEach(span -> sb.append(span.content()));
+        return sb.toString();
+    }
+
+    private RouteInfo route(String id, String source, String uptime) {
+        RouteInfo route = new RouteInfo();
+        route.routeId = id;
+        route.source = source;
+        route.state = "Started";
+        route.uptime = uptime;
+        info.routes.add(route);
+        return route;
+    }
+}

Reply via email to