Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 29 additions & 29 deletions lxc/sparse-cone.txt
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
# lxc/sparse-cone.txt
# ---------------------------------------------------------------------------
# Sparse-checkout cone list for the MeshMonitor LXC template build.
#
# WHAT THIS FILE CONTROLS
# build-lxc-template.sh reads this file at build time and passes the listed
# directories to git sparse-checkout set. Only these directories, plus
# all root-level files (package.json, tsconfig*.json, vite.config.ts, etc.),
# which cone mode always includes automatically, are materialized inside
# /opt/meshmonitor in the container rootfs. Everything else (docs/, desktop/,
# .github/, etc.) is never fetched, keeping the template lean (~8MB .git vs
# ~51MB for a full clone).
#
# MAINTENANCE
# If you add a new top-level directory that is required at runtime, add it
# here or the LXC template will silently omit those files on the next build.
# After updating: rebuild the template and confirm meshmonitor-update works.
#
# FOR AI ASSISTANTS
# This is the single source of truth for LXC template directory inclusion.
# Update this file in the same commit whenever you add a runtime-required
# top-level directory. See CLAUDE.md "LXC Template Build" for the full rule.
# ---------------------------------------------------------------------------
src
public
docker
protobufs
scripts
# lxc/sparse-cone.txt
# ---------------------------------------------------------------------------
# Sparse-checkout cone list for the MeshMonitor LXC template build.
#
# WHAT THIS FILE CONTROLS
# build-lxc-template.sh reads this file at build time and passes the listed
# directories to git sparse-checkout set. Only these directories, plus
# all root-level files (package.json, tsconfig*.json, vite.config.ts, etc.),
# which cone mode always includes automatically, are materialized inside
# /opt/meshmonitor in the container rootfs. Everything else (docs/, desktop/,
# .github/, etc.) is never fetched, keeping the template lean (~8MB .git vs
# ~51MB for a full clone).
#
# MAINTENANCE
# If you add a new top-level directory that is required at runtime, add it
# here or the LXC template will silently omit those files on the next build.
# After updating: rebuild the template and confirm meshmonitor-update works.
#
# FOR AI ASSISTANTS
# This is the single source of truth for LXC template directory inclusion.
# Update this file in the same commit whenever you add a runtime-required
# top-level directory. See CLAUDE.md "LXC Template Build" for the full rule.
# ---------------------------------------------------------------------------

src
public
docker
protobufs
scripts
lxc
44 changes: 44 additions & 0 deletions src/server/meshcoreManager.contactPersistence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,4 +284,48 @@ describe('MeshCoreManager contact persistence (issue #3092)', () => {
advType: MeshCoreDeviceType.REPEATER,
});
});

it('keeps the known name when a later advert has an empty adv_name (issue #3756)', async () => {
const manager = new MeshCoreManager('src-a');

// First advert carries the real name.
dispatchBridgeEvent(manager, {
event_type: 'contact_advertised',
data: {
public_key: REPEATER_PUBKEY,
adv_name: 'MyRepeater',
adv_type: MeshCoreDeviceType.REPEATER,
},
});
await Promise.resolve();
await Promise.resolve();

// Second advert (e.g. a zero-hop repeater) arrives with an empty name.
// With `??` this would overwrite the stored name with ""; `||` keeps it.
dispatchBridgeEvent(manager, {
event_type: 'contact_advertised',
data: {
public_key: REPEATER_PUBKEY,
adv_name: '',
adv_type: MeshCoreDeviceType.REPEATER,
},
});
await Promise.resolve();
await Promise.resolve();

// In-memory contact retains the original name.
expect(manager.getContact(REPEATER_PUBKEY)).toMatchObject({
publicKey: REPEATER_PUBKEY,
advName: 'MyRepeater',
});

// The DB write from the empty-name advert also preserves the name.
expect(upsertNode).toHaveBeenLastCalledWith(
expect.objectContaining({
publicKey: REPEATER_PUBKEY,
name: 'MyRepeater',
}),
'src-a',
);
});
});
9 changes: 7 additions & 2 deletions src/server/meshcoreManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1109,7 +1109,10 @@ class MeshCoreManager extends EventEmitter {
const updated: MeshCoreContact = {
...existing,
publicKey,
advName: data.adv_name ?? existing.advName,
// `||` not `??`: zero-hop repeaters (and some firmware builds) emit
// `contact_advertised` with adv_name === "", which `??` would pass
// through and overwrite the known name with an empty string (#3756).
advName: data.adv_name || existing.advName,
advType: data.adv_type ?? existing.advType,
lastAdvert: data.last_advert ?? existing.lastAdvert,
latitude: data.latitude ?? existing.latitude,
Expand Down Expand Up @@ -1470,7 +1473,9 @@ class MeshCoreManager extends EventEmitter {
await databaseService.meshcore.upsertNode(
{
publicKey: contact.publicKey,
name: contact.advName ?? contact.name ?? null,
// `||` not `??` so an empty advName falls back to name rather than
// persisting "" into the node row (#3756).
name: contact.advName || contact.name || null,
advType: contact.advType ?? null,
latitude: atNullIsland ? null : (contact.latitude ?? null),
longitude: atNullIsland ? null : (contact.longitude ?? null),
Expand Down
Loading