Index: trunk/src/org/openstreetmap/josm/actions/JoinAreasAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/JoinAreasAction.java	(revision 13728)
+++ trunk/src/org/openstreetmap/josm/actions/JoinAreasAction.java	(revision 13729)
@@ -456,12 +456,12 @@
 
     /**
-     * Constructs a new {@code JoinAreasAction} with optional shortcut.
-     * @param addShortcut controls whether the shortcut should be registered or not, as for toolbar registration
+     * Constructs a new {@code JoinAreasAction} with optional shortcut and adapters.
+     * @param addShortcutToolbarAdapters controls whether the shortcut should be registered or not, as for toolbar registration and adapters
      * @since 11611
      */
-    public JoinAreasAction(boolean addShortcut) {
-        super(tr("Join overlapping Areas"), "joinareas", tr("Joins areas that overlap each other"), addShortcut ?
+    public JoinAreasAction(boolean addShortcutToolbarAdapters) {
+        super(tr("Join overlapping Areas"), "joinareas", tr("Joins areas that overlap each other"), addShortcutToolbarAdapters ?
         Shortcut.registerShortcut("tools:joinareas", tr("Tool: {0}", tr("Join overlapping Areas")), KeyEvent.VK_J, Shortcut.SHIFT)
-        : null, addShortcut);
+        : null, addShortcutToolbarAdapters, null, addShortcutToolbarAdapters);
     }
 
Index: trunk/src/org/openstreetmap/josm/data/UndoRedoHandler.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/UndoRedoHandler.java	(revision 13728)
+++ trunk/src/org/openstreetmap/josm/data/UndoRedoHandler.java	(revision 13729)
@@ -2,6 +2,8 @@
 package org.openstreetmap.josm.data;
 
+import java.util.EventObject;
 import java.util.Iterator;
 import java.util.LinkedList;
+import java.util.Objects;
 
 import org.openstreetmap.josm.Main;
@@ -30,4 +32,5 @@
 
     private final LinkedList<CommandQueueListener> listenerCommands = new LinkedList<>();
+    private final LinkedList<CommandQueuePreciseListener> preciseListenerCommands = new LinkedList<>();
 
     /**
@@ -39,5 +42,6 @@
 
     /**
-     * A listener that gets notified of command queue (undo/redo) size changes.
+     * A simple listener that gets notified of command queue (undo/redo) size changes.
+     * @see CommandQueuePreciseListener
      * @since 12718 (moved from {@code OsmDataLayer}
      */
@@ -50,4 +54,161 @@
          */
         void commandChanged(int queueSize, int redoSize);
+    }
+
+    /**
+     * A listener that gets notified of command queue (undo/redo) operations individually.
+     * @see CommandQueueListener
+     * @since 13729
+     */
+    public interface CommandQueuePreciseListener {
+
+        /**
+         * Notifies the listener about a new command added to the queue.
+         * @param e event
+         */
+        void commandAdded(CommandAddedEvent e);
+
+        /**
+         * Notifies the listener about commands being cleaned.
+         * @param e event
+         */
+        void cleaned(CommandQueueCleanedEvent e);
+
+        /**
+         * Notifies the listener about a command that has been undone.
+         * @param e event
+         */
+        void commandUndone(CommandUndoneEvent e);
+
+        /**
+         * Notifies the listener about a command that has been redone.
+         * @param e event
+         */
+        void commandRedone(CommandRedoneEvent e);
+    }
+
+    abstract static class CommandQueueEvent extends EventObject {
+        protected CommandQueueEvent(UndoRedoHandler source) {
+            super(Objects.requireNonNull(source));
+        }
+
+        /**
+         * Calls the appropriate method of the listener for this event.
+         * @param listener dataset listener to notify about this event
+         */
+        abstract void fire(CommandQueuePreciseListener listener);
+
+        @Override
+        public final UndoRedoHandler getSource() {
+            return (UndoRedoHandler) super.getSource();
+        }
+    }
+
+    /**
+     * Event fired after a command has been added to the command queue.
+     * @since xxx
+     */
+    public static final class CommandAddedEvent extends CommandQueueEvent {
+
+        private final Command cmd;
+
+        private CommandAddedEvent(UndoRedoHandler source, Command cmd) {
+            super(source);
+            this.cmd = Objects.requireNonNull(cmd);
+        }
+
+        /**
+         * Returns the added command.
+         * @return the added command
+         */
+        public Command getCommand() {
+            return cmd;
+        }
+
+        @Override
+        void fire(CommandQueuePreciseListener listener) {
+            listener.commandAdded(this);
+        }
+    }
+
+    /**
+     * Event fired after the command queue has been cleaned.
+     * @since xxx
+     */
+    public static final class CommandQueueCleanedEvent extends CommandQueueEvent {
+
+        private final DataSet ds;
+
+        private CommandQueueCleanedEvent(UndoRedoHandler source, DataSet ds) {
+            super(source);
+            this.ds = ds;
+        }
+
+        /**
+         * Returns the affected dataset.
+         * @return the affected dataset, or null if the queue has been globally emptied
+         */
+        public DataSet getDataSet() {
+            return ds;
+        }
+
+        @Override
+        void fire(CommandQueuePreciseListener listener) {
+            listener.cleaned(this);
+        }
+    }
+
+    /**
+     * Event fired after a command has been undone.
+     * @since xxx
+     */
+    public static final class CommandUndoneEvent extends CommandQueueEvent {
+
+        private final Command cmd;
+
+        private CommandUndoneEvent(UndoRedoHandler source, Command cmd) {
+            super(source);
+            this.cmd = Objects.requireNonNull(cmd);
+        }
+
+        /**
+         * Returns the undone command.
+         * @return the undone command
+         */
+        public Command getCommand() {
+            return cmd;
+        }
+
+        @Override
+        void fire(CommandQueuePreciseListener listener) {
+            listener.commandUndone(this);
+        }
+    }
+
+    /**
+     * Event fired after a command has been redone.
+     * @since xxx
+     */
+    public static final class CommandRedoneEvent extends CommandQueueEvent {
+
+        private final Command cmd;
+
+        private CommandRedoneEvent(UndoRedoHandler source, Command cmd) {
+            super(source);
+            this.cmd = Objects.requireNonNull(cmd);
+        }
+
+        /**
+         * Returns the redone command.
+         * @return the redone command
+         */
+        public Command getCommand() {
+            return cmd;
+        }
+
+        @Override
+        void fire(CommandQueuePreciseListener listener) {
+            listener.commandRedone(this);
+        }
     }
 
@@ -80,6 +241,10 @@
     /**
      * Fires a commands change event after adding a command.
-     */
-    public void afterAdd() {
+     * @param cmd command added
+     */
+    public void afterAdd(Command cmd) {
+        if (cmd != null) {
+            fireEvent(new CommandAddedEvent(this, cmd));
+        }
         fireCommandsChanged();
     }
@@ -91,5 +256,5 @@
     public synchronized void add(final Command c) {
         addNoRedraw(c);
-        afterAdd();
+        afterAdd(c);
     }
 
@@ -117,4 +282,5 @@
                 c.undoCommand();
                 redoCommands.addFirst(c);
+                fireEvent(new CommandUndoneEvent(this, c));
                 if (commands.isEmpty()) {
                     break;
@@ -147,4 +313,5 @@
             c.executeCommand();
             commands.add(c);
+            fireEvent(new CommandRedoneEvent(this, c));
             if (redoCommands.isEmpty()) {
                 break;
@@ -163,4 +330,8 @@
     }
 
+    private void fireEvent(CommandQueueEvent e) {
+        preciseListenerCommands.forEach(e::fire);
+    }
+
     /**
      * Resets the undo/redo list.
@@ -169,4 +340,5 @@
         redoCommands.clear();
         commands.clear();
+        fireEvent(new CommandQueueCleanedEvent(this, null));
         fireCommandsChanged();
     }
@@ -194,4 +366,5 @@
         }
         if (changed) {
+            fireEvent(new CommandQueueCleanedEvent(this, dataSet));
             fireCommandsChanged();
         }
@@ -208,5 +381,5 @@
     /**
      * Adds a command queue listener.
-     * @param l The commands queue listener to add
+     * @param l The command queue listener to add
      * @return {@code true} if the listener has been added, {@code false} otherwise
      */
@@ -214,3 +387,22 @@
         return listenerCommands.add(l);
     }
+
+    /**
+     * Removes a precise command queue listener.
+     * @param l The precise command queue listener to remove
+     * @since 13729
+     */
+    public void removeCommandQueuePreciseListener(CommandQueuePreciseListener l) {
+        preciseListenerCommands.remove(l);
+    }
+
+    /**
+     * Adds a precise command queue listener.
+     * @param l The precise command queue listener to add
+     * @return {@code true} if the listener has been added, {@code false} otherwise
+     * @since 13729
+     */
+    public boolean addCommandQueuePreciseListener(CommandQueuePreciseListener l) {
+        return preciseListenerCommands.add(l);
+    }
 }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java	(revision 13728)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java	(revision 13729)
@@ -33,4 +33,5 @@
 import javax.swing.tree.DefaultTreeCellRenderer;
 import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.MutableTreeNode;
 import javax.swing.tree.TreePath;
 import javax.swing.tree.TreeSelectionModel;
@@ -39,5 +40,9 @@
 import org.openstreetmap.josm.command.Command;
 import org.openstreetmap.josm.command.PseudoCommand;
-import org.openstreetmap.josm.data.UndoRedoHandler.CommandQueueListener;
+import org.openstreetmap.josm.data.UndoRedoHandler.CommandAddedEvent;
+import org.openstreetmap.josm.data.UndoRedoHandler.CommandQueueCleanedEvent;
+import org.openstreetmap.josm.data.UndoRedoHandler.CommandQueuePreciseListener;
+import org.openstreetmap.josm.data.UndoRedoHandler.CommandRedoneEvent;
+import org.openstreetmap.josm.data.UndoRedoHandler.CommandUndoneEvent;
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
@@ -56,5 +61,5 @@
  * @since 94
  */
-public class CommandStackDialog extends ToggleDialog implements CommandQueueListener {
+public class CommandStackDialog extends ToggleDialog implements CommandQueuePreciseListener {
 
     private final DefaultTreeModel undoTreeModel = new DefaultTreeModel(new DefaultMutableTreeNode());
@@ -63,4 +68,7 @@
     private final JTree undoTree = new JTree(undoTreeModel);
     private final JTree redoTree = new JTree(redoTreeModel);
+
+    private DefaultMutableTreeNode undoRoot;
+    private DefaultMutableTreeNode redoRoot;
 
     private final transient UndoRedoSelectionListener undoSelectionListener;
@@ -229,5 +237,5 @@
             listener.updateEnabledState();
         }
-        MainApplication.undoRedo.addCommandQueueListener(this);
+        MainApplication.undoRedo.addCommandQueuePreciseListener(this);
     }
 
@@ -243,7 +251,7 @@
     @Override
     public void hideNotify() {
-        undoTreeModel.setRoot(new DefaultMutableTreeNode());
-        redoTreeModel.setRoot(new DefaultMutableTreeNode());
-        MainApplication.undoRedo.removeCommandQueueListener(this);
+        undoTreeModel.setRoot(undoRoot = new DefaultMutableTreeNode());
+        redoTreeModel.setRoot(redoRoot = new DefaultMutableTreeNode());
+        MainApplication.undoRedo.removeCommandQueuePreciseListener(this);
     }
 
@@ -254,20 +262,30 @@
     private void buildTrees() {
         setTitle(tr("Command Stack"));
-        if (MainApplication.getLayerManager().getEditLayer() == null)
-            return;
-
+        buildUndoTree();
+        buildRedoTree();
+        ensureTreesConsistency();
+    }
+
+    private void buildUndoTree() {
         List<Command> undoCommands = MainApplication.undoRedo.commands;
-        DefaultMutableTreeNode undoRoot = new DefaultMutableTreeNode();
+        undoRoot = new DefaultMutableTreeNode();
         for (int i = 0; i < undoCommands.size(); ++i) {
             undoRoot.add(getNodeForCommand(undoCommands.get(i), i));
         }
         undoTreeModel.setRoot(undoRoot);
-
+    }
+
+    private void buildRedoTree() {
         List<Command> redoCommands = MainApplication.undoRedo.redoCommands;
-        DefaultMutableTreeNode redoRoot = new DefaultMutableTreeNode();
+        redoRoot = new DefaultMutableTreeNode();
         for (int i = 0; i < redoCommands.size(); ++i) {
             redoRoot.add(getNodeForCommand(redoCommands.get(i), i));
         }
         redoTreeModel.setRoot(redoRoot);
+    }
+
+    private void ensureTreesConsistency() {
+        List<Command> undoCommands = MainApplication.undoRedo.commands;
+        List<Command> redoCommands = MainApplication.undoRedo.redoCommands;
         if (redoTreeModel.getChildCount(redoRoot) > 0) {
             redoTree.scrollRowToVisible(0);
@@ -342,8 +360,41 @@
 
     @Override
-    public void commandChanged(int queueSize, int redoSize) {
-        if (!isVisible())
-            return;
-        buildTrees();
+    public void cleaned(CommandQueueCleanedEvent e) {
+        if (isVisible()) {
+            buildTrees();
+        }
+    }
+
+    @Override
+    public void commandAdded(CommandAddedEvent e) {
+        if (isVisible()) {
+            undoRoot.add(getNodeForCommand(e.getCommand(), undoRoot.getChildCount()));
+            undoTreeModel.nodeStructureChanged(undoRoot);
+            ensureTreesConsistency();
+        }
+    }
+
+    @Override
+    public void commandUndone(CommandUndoneEvent e) {
+        if (isVisible()) {
+            swapNode(undoTreeModel, undoRoot, undoRoot.getChildCount() - 1, redoTreeModel, redoRoot, 0);
+        }
+    }
+
+    @Override
+    public void commandRedone(CommandRedoneEvent e) {
+        if (isVisible()) {
+            swapNode(redoTreeModel, redoRoot, 0, undoTreeModel, undoRoot, undoRoot.getChildCount());
+        }
+    }
+
+    private void swapNode(DefaultTreeModel srcModel, DefaultMutableTreeNode srcRoot, int srcIndex,
+                          DefaultTreeModel dstModel, DefaultMutableTreeNode dstRoot, int dstIndex) {
+        MutableTreeNode node = (MutableTreeNode) srcRoot.getChildAt(srcIndex);
+        srcRoot.remove(node);
+        srcModel.nodeStructureChanged(srcRoot);
+        dstRoot.insert(node, dstIndex);
+        dstModel.nodeStructureChanged(dstRoot);
+        ensureTreesConsistency();
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/ValidatorDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/ValidatorDialog.java	(revision 13728)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/ValidatorDialog.java	(revision 13729)
@@ -647,5 +647,5 @@
                 monitor.subTask(tr("Updating map ..."));
                 SwingUtilities.invokeAndWait(() -> {
-                    MainApplication.undoRedo.afterAdd();
+                    MainApplication.undoRedo.afterAdd(null);
                     invalidateValidatorLayers();
                     tree.resetErrors();
Index: trunk/src/org/openstreetmap/josm/tools/RightAndLefthandTraffic.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/RightAndLefthandTraffic.java	(revision 13728)
+++ trunk/src/org/openstreetmap/josm/tools/RightAndLefthandTraffic.java	(revision 13729)
@@ -30,4 +30,5 @@
 import org.openstreetmap.josm.data.osm.UploadPolicy;
 import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.io.IllegalDataException;
 import org.openstreetmap.josm.io.OsmReader;
@@ -135,4 +136,6 @@
             optimizedWays.addAll(ways);
         }
+        // Clean command stack
+        MainApplication.undoRedo.clean(data);
         return optimizedWays;
     }
