web: fix federation for multi protocol (#722)

* web: fix federation for multi protocol

* web: fix short name emojis

* web: address review comments

* ci: fix the codeql gap

* ci: fix the codeql gap

* ci: fix the codeql gap

* ci: remove swift
This commit is contained in:
l5y
2026-04-08 14:36:43 +02:00
committed by GitHub
parent 5b9e6e3d48
commit 083de6418f
7 changed files with 88 additions and 16 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ on:
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
runs-on: ubuntu-latest
permissions:
security-events: write
packages: read
@@ -297,9 +297,12 @@ module PotatoMesh
def shutdown_federation_background_work!(timeout: nil)
request_federation_shutdown!
timeout_value = timeout || PotatoMesh::Config.federation_shutdown_timeout_seconds
# Drain the worker pool first so federation threads blocked in
# wait_for_federation_tasks unblock promptly instead of waiting
# for each task's individual timeout to expire.
shutdown_federation_worker_pool!
stop_federation_thread!(:initial_federation_thread, timeout: timeout_value)
stop_federation_thread!(:federation_thread, timeout: timeout_value)
shutdown_federation_worker_pool!
clear_federation_crawl_state!
end
@@ -346,3 +346,46 @@ test('createMessageChatEntry: meshtastic message with @[Name] is NOT resolved as
assert.ok(shortNameCount <= 1, 'only the sender badge should be present, no mention badge');
});
});
// --- renderShortHtml badge padding ---
test('renderShortHtml leaves 4-char ASCII names unpadded', () => {
withApp(() => {
const html = globalThis.PotatoMesh.renderShortHtml('0ac7', 'CLIENT');
assert.ok(!html.includes('&nbsp;0ac7'), 'should not add leading space');
assert.ok(!html.includes('0ac7&nbsp;'), 'should not add trailing space');
});
});
test('renderShortHtml adds single space padding for short emoji names', () => {
withApp(() => {
const html = globalThis.PotatoMesh.renderShortHtml('\u26A1', 'CLIENT');
// Should produce " ⚡ " — one leading, one trailing space (as &nbsp;)
assert.ok(html.includes('&nbsp;\u26A1&nbsp;'), 'emoji should have one space on each side');
// Should NOT have double leading spaces
assert.ok(!html.includes('&nbsp;&nbsp;\u26A1'), 'should not double-pad emoji');
});
});
test('renderShortHtml adds single space padding for surrogate pair emoji', () => {
withApp(() => {
const html = globalThis.PotatoMesh.renderShortHtml('\uD83D\uDE43', 'CLIENT');
// 🙃 is a surrogate pair (length 2 in JS) but 1 grapheme
assert.ok(html.includes('&nbsp;\uD83D\uDE43&nbsp;'), 'surrogate emoji should have one space on each side');
});
});
test('renderShortHtml adds single space padding for ZWJ emoji sequence', () => {
withApp(() => {
const zwj = '\u{1F3C3}\u{200D}\u{2642}\u{FE0F}'; // 🏃‍♂️ — length 5, 1 grapheme
const html = globalThis.PotatoMesh.renderShortHtml(zwj, 'CLIENT');
assert.ok(html.includes(`&nbsp;${zwj}&nbsp;`), 'ZWJ emoji should have one space on each side');
});
});
test('renderShortHtml adds single space padding for plain 2-char name', () => {
withApp(() => {
const html = globalThis.PotatoMesh.renderShortHtml('ab', 'CLIENT');
assert.ok(html.includes('&nbsp;ab&nbsp;'), '2-char name should have one space on each side');
});
});
+11 -11
View File
@@ -1853,22 +1853,22 @@ export function initializeApp(config) {
infoAttr = attrParts.join('');
}
if (!short) {
return `<span class="short-name" style="background:#ccc"${titleAttr}${infoAttr}>?&nbsp;&nbsp;&nbsp;</span>`;
return `<span class="short-name" style="background:#ccc"${titleAttr}${infoAttr}>&nbsp;?&nbsp;</span>`;
}
// Centre the label within a 4-column badge. padStart alone only adds
// leading spaces, producing " C" for a 1-char name with no trailing
// space. Instead distribute padding evenly: 1-char → " C ", 2-char →
// " AB ", 3-char → " ABC", 4-char → unchanged. Names already at 4+
// chars are left as-is (meshtastic always stores exactly 4; the Ruby
// COMPANION override also produces exactly 4).
// Pad the label for the badge. For plain-ASCII names that are already
// 4 characters (meshtastic always stores exactly 4) no padding is added.
// Shorter names or names containing emoji/non-ASCII get a single space
// on each side — grapheme width varies too much for character-count
// centering to work reliably.
const raw = String(short);
const graphemeCount = typeof Intl !== 'undefined' && Intl.Segmenter
? [...new Intl.Segmenter().segment(raw)].length
: raw.length;
let centred;
if (raw.length >= 4) {
if (graphemeCount >= 4) {
centred = raw;
} else {
const leading = Math.ceil((4 - raw.length) / 2);
const trailing = 4 - raw.length - leading;
centred = ' '.repeat(leading) + raw + ' '.repeat(trailing);
centred = ` ${raw} `;
}
const padded = escapeHtml(centred).replace(/ /g, '&nbsp;');
const protocol = nodeData?.protocol ?? null;
+9
View File
@@ -274,6 +274,15 @@ h1 {
min-width: 0;
}
.site-title__link {
display: inline-flex;
align-items: center;
gap: inherit;
color: inherit;
text-decoration: none;
min-width: 0;
}
.site-title-text {
min-width: 0;
max-width: 100%;
+15
View File
@@ -1311,6 +1311,13 @@ RSpec.describe "Potato Mesh Sinatra app" do
expect(last_response.body).to include('class="footer-content"')
end
it "renders the site title as a link to the dashboard" do
get "/"
expect(last_response.body).to include('class="site-title__link"')
expect(last_response.body).to match(%r{<a href="/" class="site-title__link">})
end
it "renders the federation instance selector when federation is enabled" do
get "/"
@@ -6980,6 +6987,14 @@ RSpec.describe "Potato Mesh Sinatra app" do
expect(last_response.body).to include(node["node_id"])
end
it "does not render the meta row on the node detail page" do
node = nodes_fixture.first
get "/nodes/#{node["node_id"]}"
expect(last_response).to be_ok
expect(last_response.body).not_to include('id="metaRow"')
expect(last_response.body).not_to include('id="refreshBtn"')
end
it "returns 404 when the node cannot be located" do
get "/nodes/!deadbeef"
expect(last_response.status).to eq(404)
+5 -3
View File
@@ -78,7 +78,7 @@
show_footer = !full_screen_view || %i[charts federation].include?(view_mode)
footer_slim = %i[charts federation].include?(view_mode)
show_filter_input = !%i[node_detail charts federation].include?(view_mode)
show_meta_row = !%i[charts federation].include?(view_mode)
show_meta_row = !%i[node_detail charts federation].include?(view_mode)
nodes_nav_href = "/nodes"
nodes_nav_active = %i[nodes node_detail].include?(view_mode)
federation_nav_enabled = !private_mode && federation_enabled
@@ -99,8 +99,10 @@
<header class="site-header">
<div class="site-header__left">
<h1 class="site-title">
<img src="/potatomesh-logo.svg" alt="" aria-hidden="true" />
<span class="site-title-text"><%= site_name %></span>
<a href="/" class="site-title__link">
<img src="/potatomesh-logo.svg" alt="" aria-hidden="true" />
<span class="site-title-text"><%= site_name %></span>
</a>
</h1>
<% if federation_nav_enabled %>
<div class="header-federation">