diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3ec7b56..1485e46 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,25 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
---
+## [Unreleased]
+
+---
+
+## [1.6.2.4] - 2026-08-22
+
+### đ Behoben
+
+#### đ˘ Kanal-Anzeige: Backlog zeigte âKanal N" statt Kanalname
+- Nachrichten aus dem Backlog (DB) zeigten teils âKanal 0"/âKanal 1" statt âMesh Hessen" o.ä., während Live-Nachrichten den Namen zeigten. Ursache: der Backlog nutzt den **gespeicherten** Kanalnamen â der stand als âKanal N" fest, wenn die Nachricht empfangen/gespeichert wurde, **bevor** die Kanäle vom Gerät kamen. Jetzt wird `ChannelName` einheitlich aus dem **aktuellen Kanal-Index** aufgelĂśst (observable) und beim Eintreffen von Kanälen sowie nach dem Backlog-Laden neu berechnet.
+
+#### âŠď¸ Reply wechselt jetzt auf den Kanal der Nachricht
+- âAntworten" auf eine Kanalnachricht schaltete den aktiven Kanal nicht mehr um â die Antwort ging auf dem gerade gewählten Kanal raus. Jetzt wird beim Reply der aktive Kanal auf den umgestellt, auf dem die Nachricht empfangen wurde (Live **und** Backlog, da Live-Nachrichten jetzt auch den `ChannelIndex` tragen).
+
+#### đąď¸ Debug-Log Auto-Scroll sprang nach oben
+- Bei aktivem Auto-Scroll sprang das Debug-Log nach oben statt dem Ende zu folgen: Ab 10000 Zeilen wurde `DebugLogTextBox.Text` neu gesetzt (Trim), was die Scroll-Position auf den Anfang zurĂźcksetzt â und das passierte **nach** `ScrollToEnd()`. Bei viel Traffic (ständiges Trimmen) wurde die Ansicht so bei jeder Zeile nach oben gerissen. Jetzt wird erst getrimmt, dann als letzte Aktion ans Ende gescrollt.
+
+---
+
## [1.6.2.3] - 2026-08-16
### ⨠HinzugefĂźgt / đ§ Geändert
diff --git a/MeshhessenClient/MainWindow.Kiosk.cs b/MeshhessenClient/MainWindow.Kiosk.cs
index aa596aa..886b0f7 100644
--- a/MeshhessenClient/MainWindow.Kiosk.cs
+++ b/MeshhessenClient/MainWindow.Kiosk.cs
@@ -508,7 +508,8 @@ private void LoadMessagesFromDbAsync()
_dbOldestTimestamp = entry.Timestamp;
}
- // Rebuild visible list and scroll to newest
+ // Resolve stored "Kanal N" names against the current channel list, then rebuild.
+ RefreshChannelNames();
RebuildVisibleMessages();
if (_messages.Count > 0)
MessageListView.ScrollIntoView(_messages[^1]);
@@ -560,6 +561,7 @@ private void LazyLoadOlderMessages()
_dbOldestTimestamp = entry.Timestamp;
}
+ RefreshChannelNames();
RebuildVisibleMessages();
// Restore scroll position to the item that was previously first
diff --git a/MeshhessenClient/MainWindow.xaml.cs b/MeshhessenClient/MainWindow.xaml.cs
index d38ef29..b47ee0d 100644
--- a/MeshhessenClient/MainWindow.xaml.cs
+++ b/MeshhessenClient/MainWindow.xaml.cs
@@ -589,14 +589,16 @@ private void OnLogMessageReceived(object? sender, string logMessage)
// on, so the user can scroll up and read without being yanked back down.
if (AutoScrollLogCheckBox?.IsChecked != false)
{
- DebugLogTextBox.ScrollToEnd();
-
- // Begrenze auf maximal 10000 Zeilen
+ // Trim FIRST: reassigning .Text resets the scroll position to the top, so it
+ // must happen before ScrollToEnd â otherwise every append past the limit yanks
+ // the view up instead of following the tail.
var lines = DebugLogTextBox.Text.Split('\n');
if (lines.Length > 10000)
{
DebugLogTextBox.Text = string.Join('\n', lines.Skip(lines.Length - 10000));
}
+
+ DebugLogTextBox.ScrollToEnd();
}
});
}
@@ -1839,11 +1841,12 @@ private void OnMessageReceived(object? sender, MessageItem message)
return; // Nicht anzeigen
}
- // Setze ChannelName basierend auf Channel Index
+ // Setze ChannelName + ChannelIndex basierend auf Channel Index (einheitlich
+ // aufgelĂśst wie beim Backlog, damit die Anzeige konsistent ist).
if (uint.TryParse(message.Channel, out uint channelIndex))
{
- var channel = _channels.FirstOrDefault(c => c.Index == channelIndex);
- message.ChannelName = channel?.Name ?? $"Kanal {channelIndex}";
+ message.ChannelIndex = channelIndex;
+ message.ChannelName = ResolveChannelName(channelIndex);
}
else
{
@@ -2046,6 +2049,9 @@ private void OnChannelInfoReceived(object? sender, ChannelInfo channel)
}
_channels.Insert(insertAt, channel);
+ // A newly-arrived channel lets messages shown as "Kanal N" resolve to the real name.
+ RefreshChannelNames();
+
// Aktiviere Kanal-Auswahl wenn Kanäle vorhanden sind
if (_channels.Count > 0 && !ActiveChannelComboBox.IsEnabled)
{
@@ -4556,10 +4562,37 @@ private void DmMessageContextMenu_React_Click(object sender, RoutedEventArgs e)
// Forwarded from DirectMessagesWindow - handled there via EmojiPickerRequested
}
+ /// Resolve a channel index to its display name from the current channel list,
+ /// falling back to "Kanal N" when the channel isn't (yet) known.
+ private string ResolveChannelName(uint index)
+ {
+ var ch = _channels.FirstOrDefault(c => c.Index == index);
+ return !string.IsNullOrEmpty(ch?.Name) ? ch!.Name : $"Kanal {index}";
+ }
+
+ /// Re-resolve channel display names for all messages from the current channel
+ /// list. Backlog (DB) messages carry the name stored at receive time â often "Kanal N"
+ /// if the channels hadn't arrived yet â and channels can arrive after messages are shown.
+ private void RefreshChannelNames()
+ {
+ foreach (var m in _allMessages)
+ if (uint.TryParse(m.Channel, out var idx))
+ m.ChannelName = ResolveChannelName(idx);
+ }
+
private void MessageContextMenu_Reply_Click(object sender, RoutedEventArgs e)
{
if (MessageListView.SelectedItem is not MessageItem msg) return;
_replyToMessage = msg;
+
+ // Switch the active channel to the one the message arrived on, so the reply goes out
+ // on the right channel (channel messages carry an index 0-7 in msg.Channel).
+ if (uint.TryParse(msg.Channel, out var replyChanIdx))
+ {
+ var ch = _channels.FirstOrDefault(c => c.Index == replyChanIdx);
+ if (ch != null) ActiveChannelComboBox.SelectedItem = ch;
+ }
+
var preview = msg.Message?.Length > 60 ? msg.Message[..60] + "âŚ" : msg.Message ?? string.Empty;
ReplyIndicatorText.Text = string.Format(Loc("StrReplyingTo"), msg.From, preview);
ReplyIndicatorPanel.Visibility = Visibility.Visible;
diff --git a/MeshhessenClient/MeshhessenClient.csproj b/MeshhessenClient/MeshhessenClient.csproj
index ae16904..6ac6af5 100644
--- a/MeshhessenClient/MeshhessenClient.csproj
+++ b/MeshhessenClient/MeshhessenClient.csproj
@@ -18,9 +18,9 @@
..\public\
MeshhessenClient
- 1.6.2.3
- 1.6.2.3
- 1.6.2.3
+ 1.6.2.4
+ 1.6.2.4
+ 1.6.2.4
Meshtastic Community
Meshhessen Client
Offline-fähiger Windows Client fßr Meshtastic Geräte
diff --git a/MeshhessenClient/Models/MessageItem.cs b/MeshhessenClient/Models/MessageItem.cs
index b74ecdd..5b68c73 100644
--- a/MeshhessenClient/Models/MessageItem.cs
+++ b/MeshhessenClient/Models/MessageItem.cs
@@ -42,7 +42,15 @@ public string Message
public string Channel { get; set; } = string.Empty; // Channel Index (legacy, display string)
public uint ChannelIndex { get; set; } // raw channel index the packet arrived on
- public string ChannelName { get; set; } = string.Empty; // Channel Name for display
+
+ // Observable: resolved from the current channel list, so a message shown before the
+ // channels arrived (e.g. DB backlog on connect) updates from "Kanal N" to the real name.
+ private string _channelName = string.Empty;
+ public string ChannelName
+ {
+ get => _channelName;
+ set { _channelName = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ChannelName))); }
+ }
public uint FromId { get; set; }
public uint ToId { get; set; }
public uint Id { get; set; } // Packet ID (for reactions)
diff --git a/README.md b/README.md
index 408c50e..b2fa558 100644
--- a/README.md
+++ b/README.md
@@ -432,6 +432,16 @@ dotnet publish MeshhessenClient/MeshhessenClient.csproj -c Release -r win-x64 --
EXE liegt danach unter `public\MeshhessenClient.exe`. Alternativ: `build.bat` ausfĂźhren.
+### Forks & Weiterverwendung
+
+Der Quellcode darf im Rahmen der Lizenz geforkt und angepasst werden â wir freuen uns Ăźber abgeleitete Projekte.
+
+> â ď¸ **Der Meshhessen-Tile-Server ist davon ausgenommen.** Die Karten-Infrastruktur (`tile.meshhessenclient.de` und die zugehĂśrigen Vektor-/Raster-Endpunkte) wird **ausschlieĂlich fĂźr den offiziellen Meshhessen Client** bereitgestellt und aus Spenden der Community finanziert. Forks, abgeleitete oder umgebaute Clients (auch fĂźr andere Mesh-Protokolle wie MeshCore) dĂźrfen diese Server **nicht** nutzen und mĂźssen **eigene Tile-Infrastruktur betreiben**.
+>
+> Der Client bringt dafĂźr alles mit: der Karten-Modus **âOnline â eigener Tile-Server"** in den Einstellungen erlaubt beliebige eigene Tile-URLs. Ein einfacher Tile-Cache (z. B. eigener OSM/OpenTopo-Proxy) ist schnell und gĂźnstig aufgesetzt.
+>
+> Unautorisierte Zugriffe auf die Meshhessen-Server werden technisch unterbunden.
+
## đ Credits
@@ -703,6 +713,16 @@ dotnet publish MeshhessenClient/MeshhessenClient.csproj -c Release -r win-x64 --
The EXE will be at `public\MeshhessenClient.exe`. Alternatively, run `build.bat`.
+### Forks & Reuse
+
+You are welcome to fork and adapt the source code within the terms of the license â we're happy to see derivative projects.
+
+> â ď¸ **The Meshhessen tile server is not part of that.** The map infrastructure (`tile.meshhessenclient.de` and the associated vector/raster endpoints) is provided **exclusively for the official Meshhessen Client** and is funded by community donations. Forks, derivative or repurposed clients (including ports to other mesh protocols such as MeshCore) **may not** use these servers and must **run their own tile infrastructure**.
+>
+> The client ships with everything you need for that: the **"Online â custom tile server"** map mode in settings accepts any tile URLs you like. A simple tile cache (e.g. your own OSM/OpenTopo proxy) is quick and cheap to set up.
+>
+> Unauthorized access to the Meshhessen servers is blocked at the technical level.
+
## đ Credits