diff --git a/app/static/css/style.css b/app/static/css/style.css index 97e50e3..102b0e1 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -472,6 +472,17 @@ main { /* Per-hop hash size in the route line: 1 byte collides easily on a big mesh (least desirable), 3 bytes is the best. Tokens per theme in theme.css. */ +/* One icon-labelled fact: never break between the icon and its value, or + inside "(5) 2 bytes" — a mid-fact wrap is what made the line hard to read */ +.message-meta .meta-fact { + white-space: nowrap; +} + +.message-meta .meta-fact .bi { + margin-right: 0.25rem; + opacity: 0.8; +} + .path-hash { font-weight: 600; } /* colour alone was too faint at this size on the light theme */ .path-hash-1 { color: var(--path-hash-1); } .path-hash-2 { color: var(--path-hash-2); } diff --git a/app/static/js/app.js b/app/static/js/app.js index 10978d0..e2310fb 100644 --- a/app/static/js/app.js +++ b/app/static/js/app.js @@ -1535,11 +1535,22 @@ function buildEchoPaths(src) { return paths; } +/** One "icon + value" fact of the meta line, with the label it replaces as tooltip. */ +function metaFact(iconClass, valueHtml, titleHtml) { + return `${valueHtml}`; +} + /** - * Build the "SNR | Hops | Route | Region" line shown under an incoming + * Build the "SNR | hops | route | region" line shown under an incoming * channel message. `src` is a /api/messages row or a /api/messages/meta * entry (same field names); `paths` comes from buildEchoPaths(). * + * Everything but the SNR is labelled by an icon rather than a word: spelled + * out, the line wrapped onto a second row on a phone. The icons follow the + * rest of the UI \u2014 bi-signpost-split is the project's path icon and + * bi-pin-map the status bar's region indicator \u2014 and each carries the word + * it replaces in its tooltip. + * * SNR and hops describe the copy the node decoded and arrive with the * message itself, so they show even when no echo was heard \u2014 a hop count * at a glance says whether the popup is worth opening. The route part names @@ -1555,26 +1566,32 @@ function buildMessageMetaInfo(src, paths) { const displaySnr = (src.snr !== undefined && src.snr !== null) ? src.snr : (src.echo_snrs && src.echo_snrs.length > 0) ? src.echo_snrs[0] : null; if (typeof displaySnr === 'number') { - metaParts.push(`SNR: ${displaySnr.toFixed(1)} dB`); + metaParts.push(`SNR: ${displaySnr.toFixed(1)} dB`); } const hopCount = src.hop_count ?? (src.path_len !== null && src.path_len !== undefined ? (src.path_len & 0x3F) : null); if (hopCount !== null && hopCount !== undefined) { - metaParts.push(tHtml('chat.route_hops', { count: hopCount })); + metaParts.push(metaFact('bi-broadcast-pin', hopCount, + tHtml('chat.route_hops', { count: hopCount }))); } if (paths && paths.length > 0) { const hashSize = paths[0].hash_size || 1; - // The translated string is escaped as a whole, so the coloured span is - // spliced in afterwards through a placeholder the catalog never contains. - const TOKEN = '%%ROUTE%%'; - const sizeHtml = `${escapeHtml(tn('chat.route_hash_bytes', hashSize))}`; - const routeText = (paths.length > 1 - ? tHtml('chat.route_multi', { count: paths.length, route: TOKEN }) - : tHtml('chat.route', { route: TOKEN })).replace(TOKEN, sizeHtml); + // Shown as "2B" — the SI/IEC symbol, upper case for byte (lower case + // is bit). The words stay in the tooltip, where there is room. + const sizeShort = tHtml('chat.route_hash_bytes_short', { count: hashSize }); + const sizeLabel = tn('chat.route_hash_bytes', hashSize); + // Copies heard, shown only when there is more than one \u2014 as before, + // no count means the message was heard once. + const countHtml = paths.length > 1 ? `(${paths.length}) ` : ''; + const title = (paths.length > 1 + ? tHtml('chat.route_multi', { count: paths.length, route: sizeLabel }) + : tHtml('chat.route', { route: sizeLabel })) + ' \u2014 ' + tHtml('chat.route_hash_title'); + const value = `${countHtml}${sizeShort}`; const pathsData = encodeURIComponent(JSON.stringify(paths)); - metaParts.push(`${routeText}`); + metaParts.push(`${value}`); } if (src.region) { - metaParts.push(tHtml('chat.route_region', { name: src.region })); + metaParts.push(metaFact('bi-pin-map', escapeHtml(src.region), + tHtml('chat.route_region', { name: src.region }))); } return metaParts.join(' | '); } diff --git a/app/translations/en.json b/app/translations/en.json index ea5b746..fdf6bc3 100644 --- a/app/translations/en.json +++ b/app/translations/en.json @@ -155,6 +155,7 @@ "one": "{count} byte", "other": "{count} bytes" }, + "chat.route_hash_bytes_short": "{count}B", "chat.route_hash_title": "Per-hop hash size of the route. Tap to list every route this message was heard on", "chat.route_hops": "Hops: {count}", "chat.route_multi": "Route ({count}): {route}", diff --git a/app/translations/pl.json b/app/translations/pl.json index 94a9885..882fb27 100644 --- a/app/translations/pl.json +++ b/app/translations/pl.json @@ -159,6 +159,7 @@ "many": "{count} bajtów", "other": "{count} bajtu" }, + "chat.route_hash_bytes_short": "{count}B", "chat.route_hash_title": "Rozmiar hasha na hop w trasie. Dotknij, aby zobaczyć wszystkie trasy, którymi dotarła ta wiadomość", "chat.route_hops": "Hopy: {count}", "chat.route_multi": "Trasa ({count}): {route}", diff --git a/docs/user-guide.md b/docs/user-guide.md index 315c6bc..ac70561 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -213,7 +213,9 @@ Your own messages carry a small row of action buttons: ### Group Chat Message Routes -Under every incoming channel message a line shows the **SNR** and **hop count** of the copy your node decoded — they arrive with the message itself, so they are there even when nothing else was heard. When your node also heard the message being repeated, a **Route** part follows, saying how many copies were heard and how wide their per-hop hashes are, e.g. `SNR: 9.3 dB | Hops: 5 | Route (3): 2 bytes`. The hash size is coloured: **red** for 1 byte (such hashes collide easily on a big mesh), **orange** for 2 bytes and **green** for 3 bytes. Tap the Route part to open a popup listing every copy in the order they were heard, each with its route, SNR and hop count: +Under every incoming channel message a line shows the **SNR** and **hop count** of the copy your node decoded — they arrive with the message itself, so they are there even when nothing else was heard. When your node also heard the message being repeated, a route part follows, saying how many copies were heard and how wide their per-hop hashes are — written as `2B`, meaning 2 bytes per hop. The size is coloured: **red** for 1 byte (such hashes collide easily on a big mesh), **orange** for 2 bytes and **green** for 3 bytes. + +Apart from the SNR, each value is labelled by a small icon rather than a word, so the line fits one row on a phone — an antenna 📡 for the hop count, a signpost for the routes and a map pin for the region. Hovering an icon (or a long press on a phone) names it. Tap the signpost part to open a popup listing every copy in the order they were heard, each with its route, SNR and hop count: - The copy your node actually decoded is marked **✓ received**; the others arrived later and were dropped as duplicates, but they still show where else the message travelled. A copy heard straight from the sender, with no repeater in between, is listed as **Direct** with 0 hops. - Tap a route to open the **[Path Analyzer](#path-analyzer)** on the Map view, with that message selected and that exact route drawn — the quickest way to see where a message physically travelled. diff --git a/docs/whatsnew.md b/docs/whatsnew.md index d205b03..ab713c3 100644 --- a/docs/whatsnew.md +++ b/docs/whatsnew.md @@ -12,7 +12,7 @@ For deep technical notes, see [architecture.md](architecture.md). For the full g ### Features -- **The line under an incoming channel message now names the region it was sent in, and no longer spells out the route.** A MeshCore sender can stamp a packet with a *region* — a flood scope such as `pl` that only repeaters allowing it pass on (see the previous release). The stamp is not the name but a 16-bit code derived from the region's key, so the interface can recognise it only for regions you have added under **Settings → Channels**: a message stamped with one of them now ends its line with **Region: pl**, while an unstamped message, or one from a region you have not configured, shows nothing extra. The route part changed too. It used to print the first route inline (`SNR: -5.5 dB | Hops: 4 | Route (3): 142C→1122→271C→EE6C`), which with 2- or 3-byte hashes crowded everything else off a phone screen. It now only says how many copies of the message were heard and how wide their per-hop hashes are, `Route (3): 2 bytes`, with the size coloured **red**, **orange** or **green** for 1, 2 or 3 bytes, and tapping it opens the same popup as before. The SNR and hop count stay where they were: they describe the copy your node decoded, arrive with the message itself, and so are there even when no repeat was heard. The popup is now complete: it lists every copy in the order heard, including one that came **Direct** from the sender with no repeater in between (previously left out, which made "Hops: 0" under a message look wrong next to popup entries with one hop), and marks the copy your node actually decoded with **✓ received**. The whole line is also a size larger and darker, since it was hard to read on a big screen. The region stamp is kept from this version on, so messages received before the update have no region to show. +- **The line under an incoming channel message now names the region it was sent in, and no longer spells out the route.** A MeshCore sender can stamp a packet with a *region* — a flood scope such as `pl` that only repeaters allowing it pass on (see the previous release). The stamp is not the name but a 16-bit code derived from the region's key, so the interface can recognise it only for regions you have added under **Settings → Channels**: a message stamped with one of them now ends its line with **Region: pl**, while an unstamped message, or one from a region you have not configured, shows nothing extra. The route part changed too. It used to print the first route inline (`SNR: -5.5 dB | Hops: 4 | Route (3): 142C→1122→271C→EE6C`), which with 2- or 3-byte hashes crowded everything else off a phone screen. It now only says how many copies of the message were heard and how wide their per-hop hashes are — `(3) 2B`, three copies with 2-byte hashes — with the size coloured **red**, **orange** or **green** for 1, 2 or 3 bytes, and tapping it opens the same popup as before. The words *Hops*, *Route* and *Region* are gone as well, replaced by a small antenna, signpost and map-pin icon, each naming itself when hovered or long-pressed: spelled out, the line took two rows on a phone and pushed the messages apart. The SNR and hop count stay where they were: they describe the copy your node decoded, arrive with the message itself, and so are there even when no repeat was heard. The popup is now complete: it lists every copy in the order heard, including one that came **Direct** from the sender with no repeater in between (previously left out, which made "Hops: 0" under a message look wrong next to popup entries with one hop), and marks the copy your node actually decoded with **✓ received**. The whole line is also a size larger and darker, since it was hard to read on a big screen. The region stamp is kept from this version on, so messages received before the update have no region to show. ---