refactor(ui): rename contact type label CLI to COM (companion)

The MeshCore community uses "companion" not "client" for type 1 nodes.
Rename the CLI label to COM across all UI, API, JS, and docs to align
with official terminology. Includes cache migration for old CLI entries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-03-21 14:37:30 +01:00
parent 8ba5381921
commit 33a71bed17
18 changed files with 68 additions and 65 deletions
+1 -1
View File
@@ -296,7 +296,7 @@ def _cleanup_job():
return
# Convert to list format (same as preview-cleanup endpoint)
type_labels = {1: 'CLI', 2: 'REP', 3: 'ROOM', 4: 'SENS'}
type_labels = {1: 'COM', 2: 'REP', 3: 'ROOM', 4: 'SENS'}
contacts = []
for public_key, details in contacts_detailed.items():
contacts.append({
+5 -2
View File
@@ -6,7 +6,7 @@ so @mention autocomplete works even for removed contacts.
File format: JSONL ({device_name}.contacts_cache.jsonl)
Each line: {"public_key": "...", "name": "...", "first_seen": ts, "last_seen": ts,
"source": "advert"|"device", "lat": float, "lon": float, "type_label": "CLI"|"REP"|...}
"source": "advert"|"device", "lat": float, "lon": float, "type_label": "COM"|"REP"|...}
"""
import json
@@ -63,6 +63,9 @@ def load_cache() -> dict:
entry = json.loads(line)
pk = entry.get('public_key', '').lower()
if pk:
# Migrate old "CLI" label to "COM"
if entry.get('type_label') == 'CLI':
entry['type_label'] = 'COM'
_cache[pk] = entry
except json.JSONDecodeError:
continue
@@ -248,7 +251,7 @@ def scan_new_adverts() -> int:
return updated
_TYPE_LABELS = {1: 'CLI', 2: 'REP', 3: 'ROOM', 4: 'SENS'}
_TYPE_LABELS = {1: 'COM', 2: 'REP', 3: 'ROOM', 4: 'SENS'}
def initialize_from_device(contacts_detailed: dict):
+1 -1
View File
@@ -365,7 +365,7 @@ def _execute_console_command(args: list) -> str:
elif cmd == 'contacts':
# Show device-only contacts with path info (like meshcore-cli)
type_names = {0: 'NONE', 1: 'CLI', 2: 'REP', 3: 'ROOM', 4: 'SENS'}
type_names = {0: 'NONE', 1: 'COM', 2: 'REP', 3: 'ROOM', 4: 'SENS'}
if not device_manager.mc or not device_manager.mc.contacts:
return "No contacts on device"
try:
+1 -1
View File
@@ -130,7 +130,7 @@ def get_all_contacts_detailed() -> Tuple[bool, List[Dict], int, str]:
result.append({
'name': c.get('name', ''),
'public_key_prefix': pk[:12] if len(pk) >= 12 else pk,
'type_label': {0: 'CLI', 1: 'CLI', 2: 'REP', 3: 'ROOM', 4: 'SENS'}.get(c.get('type', 1), 'UNKNOWN'),
'type_label': {0: 'COM', 1: 'COM', 2: 'REP', 3: 'ROOM', 4: 'SENS'}.get(c.get('type', 1), 'UNKNOWN'),
'path_or_mode': c.get('out_path', '') or 'Flood',
'raw_line': '',
})
+14 -14
View File
@@ -696,7 +696,7 @@ def get_cached_contacts():
'source': c.get('source', ''),
'adv_lat': c.get('adv_lat'),
'adv_lon': c.get('adv_lon'),
'type_label': {0: 'CLI', 1: 'CLI', 2: 'REP', 3: 'ROOM', 4: 'SENS'}.get(c.get('type', 1), 'UNKNOWN'),
'type_label': {0: 'COM', 1: 'COM', 2: 'REP', 3: 'ROOM', 4: 'SENS'}.get(c.get('type', 1), 'UNKNOWN'),
'is_ignored': pk in ignored_keys,
'is_blocked': pk in blocked_keys,
})
@@ -811,7 +811,7 @@ def preview_cleanup_contacts():
JSON body:
{
"name_filter": "", # Partial name match (empty = ignore)
"types": [1, 2, 3, 4], # Contact types (1=CLI, 2=REP, 3=ROOM, 4=SENS)
"types": [1, 2, 3, 4], # Contact types (1=COM, 2=REP, 3=ROOM, 4=SENS)
"date_field": "last_advert", # "last_advert" or "lastmod"
"days": 2 # Days of inactivity (0 = ignore)
}
@@ -865,7 +865,7 @@ def preview_cleanup_contacts():
}), 500
# Convert to list format (same as /api/contacts/detailed)
type_labels = {1: 'CLI', 2: 'REP', 3: 'ROOM', 4: 'SENS'}
type_labels = {1: 'COM', 2: 'REP', 3: 'ROOM', 4: 'SENS'}
contacts = []
for public_key, details in contacts_detailed.items():
out_path_len = details.get('out_path_len', -1)
@@ -907,7 +907,7 @@ def cleanup_contacts():
JSON body:
{
"name_filter": "", # Partial name match (empty = ignore)
"types": [1, 2, 3, 4], # Contact types (1=CLI, 2=REP, 3=ROOM, 4=SENS)
"types": [1, 2, 3, 4], # Contact types (1=COM, 2=REP, 3=ROOM, 4=SENS)
"date_field": "last_advert", # "last_advert" or "lastmod"
"days": 2 # Days of inactivity (0 = ignore)
}
@@ -965,7 +965,7 @@ def cleanup_contacts():
}), 500
# Convert to list format
type_labels = {1: 'CLI', 2: 'REP', 3: 'ROOM', 4: 'SENS'}
type_labels = {1: 'COM', 2: 'REP', 3: 'ROOM', 4: 'SENS'}
contacts = []
for public_key, details in contacts_detailed.items():
out_path_len = details.get('out_path_len', -1)
@@ -2254,7 +2254,7 @@ def get_dm_updates():
@api_bp.route('/contacts/detailed', methods=['GET'])
def get_contacts_detailed_api():
"""
Get detailed list of ALL existing contacts on the device (CLI, REP, ROOM, SENS).
Get detailed list of ALL existing contacts on the device (COM, REP, ROOM, SENS).
Returns full contact_info data from meshcli including GPS coordinates, paths, etc.
@@ -2269,7 +2269,7 @@ def get_contacts_detailed_api():
"name": "TK Zalesie Test 🦜", // adv_name
"public_key": "df2027d3f2ef...", // Full public key (64 chars)
"public_key_prefix": "df2027d3f2ef", // First 12 chars
"type": 2, // 1=CLI, 2=REP, 3=ROOM, 4=SENS
"type": 2, // 1=COM, 2=REP, 3=ROOM, 4=SENS
"type_label": "REP", // Human-readable type
"flags": 0,
"out_path_len": -1, // -1 = Flood mode
@@ -2298,7 +2298,7 @@ def get_contacts_detailed_api():
}), 500
# Convert dict to list and add computed fields
type_labels = {1: 'CLI', 2: 'REP', 3: 'ROOM', 4: 'SENS'}
type_labels = {1: 'COM', 2: 'REP', 3: 'ROOM', 4: 'SENS'}
contacts = []
# Get protected/ignored/blocked contacts for status fields
@@ -2868,8 +2868,8 @@ def get_pending_contacts_api():
Query parameters:
types (list[int]): Filter by contact types (optional)
Example: ?types=1&types=2 (CLI and REP only)
Valid values: 1 (CLI), 2 (REP), 3 (ROOM), 4 (SENS)
Example: ?types=1&types=2 (COM and REP only)
Valid values: 1 (COM), 2 (REP), 3 (ROOM), 4 (SENS)
If not provided, returns all pending contacts
Returns:
@@ -2906,7 +2906,7 @@ def get_pending_contacts_api():
if invalid_types:
return jsonify({
'success': False,
'error': f'Invalid types: {invalid_types}. Valid types: 1 (CLI), 2 (REP), 3 (ROOM), 4 (SENS)',
'error': f'Invalid types: {invalid_types}. Valid types: 1 (COM), 2 (REP), 3 (ROOM), 4 (SENS)',
'pending': []
}), 400
@@ -2922,9 +2922,9 @@ def get_pending_contacts_api():
pending = [c for c in pending if c.get('public_key', '').lower() not in excluded]
# Add type_label for frontend display
type_labels = {1: 'CLI', 2: 'REP', 3: 'ROOM', 4: 'SENS'}
type_labels = {1: 'COM', 2: 'REP', 3: 'ROOM', 4: 'SENS'}
for c in pending:
c['type_label'] = type_labels.get(c.get('type', 0), 'CLI')
c['type_label'] = type_labels.get(c.get('type', 0), 'COM')
pk = c.get('public_key', '')
c['public_key_prefix'] = pk[:12] if len(pk) >= 12 else pk
@@ -2964,7 +2964,7 @@ def approve_pending_contact_api():
}
IMPORTANT: Always send the full public_key (not name or prefix).
Full public key works for all contact types (CLI, ROOM, REP, SENS).
Full public key works for all contact types (COM, ROOM, REP, SENS).
Returns:
JSON with approval result:
+7 -7
View File
@@ -40,14 +40,14 @@ let isMentionMode = false; // Is mention dropdown active
// Contact type colors for map markers
const CONTACT_TYPE_COLORS = {
1: '#2196F3', // CLI - blue
1: '#2196F3', // COM - blue
2: '#4CAF50', // REP - green
3: '#9C27B0', // ROOM - purple
4: '#FF9800' // SENS - orange
};
const CONTACT_TYPE_NAMES = {
1: 'CLI',
1: 'COM',
2: 'REP',
3: 'ROOM',
4: 'SENS'
@@ -140,7 +140,7 @@ window.showContactOnMap = showContactOnMap;
*/
function getSelectedMapTypes() {
const types = [];
if (document.getElementById('mapFilterCLI')?.classList.contains('active')) types.push(1);
if (document.getElementById('mapFilterCOM')?.classList.contains('active')) types.push(1);
if (document.getElementById('mapFilterREP')?.classList.contains('active')) types.push(2);
if (document.getElementById('mapFilterROOM')?.classList.contains('active')) types.push(3);
if (document.getElementById('mapFilterSENS')?.classList.contains('active')) types.push(4);
@@ -162,7 +162,7 @@ function updateMapMarkers() {
const filteredContacts = allContactsWithGps.filter(c => selectedTypes.includes(c.type));
// Cache-only contacts (not on device) filtered by type
const TYPE_LABEL_TO_NUM = { 'CLI': 1, 'REP': 2, 'ROOM': 3, 'SENS': 4 };
const TYPE_LABEL_TO_NUM = { 'COM': 1, 'REP': 2, 'ROOM': 3, 'SENS': 4 };
let cachedFiltered = [];
if (showCached) {
cachedFiltered = allCachedContactsWithGps
@@ -295,7 +295,7 @@ async function showAllContactsOnMap() {
};
// Setup filter badge listeners
['mapFilterCLI', 'mapFilterREP', 'mapFilterROOM', 'mapFilterSENS'].forEach(id => {
['mapFilterCOM', 'mapFilterREP', 'mapFilterROOM', 'mapFilterSENS'].forEach(id => {
const badge = document.getElementById(id);
if (badge) {
badge.onclick = () => {
@@ -3303,7 +3303,7 @@ async function updatePendingContactsBadge() {
/**
* Load pending contacts type filter from localStorage.
* This is a duplicate of the function in contacts.js for use in app.js
* @returns {Array<number>} Array of contact types (default: [1] for CLI only)
* @returns {Array<number>} Array of contact types (default: [1] for COM only)
*/
function loadPendingTypeFilter() {
try {
@@ -3318,7 +3318,7 @@ function loadPendingTypeFilter() {
} catch (e) {
console.error('Failed to load pending type filter from localStorage:', e);
}
// Default: CLI only (most common use case)
// Default: COM only (most common use case)
return [1];
}
+14 -14
View File
@@ -769,7 +769,7 @@ function attachPendingEventListeners() {
}
// Type filter badges - toggle on click, save to localStorage and reload
['typeFilterCLI', 'typeFilterREP', 'typeFilterROOM', 'typeFilterSENS'].forEach(id => {
['typeFilterCOM', 'typeFilterREP', 'typeFilterROOM', 'typeFilterSENS'].forEach(id => {
const badge = document.getElementById(id);
if (badge) {
badge.addEventListener('click', () => {
@@ -1154,7 +1154,7 @@ function isContactProtected(publicKey) {
* This allows the filter to persist across page reloads and be used
* in different parts of the app (Pending page, Contact Management badge, etc.)
*
* @param {Array<number>} types - Array of contact types to filter (1=CLI, 2=REP, 3=ROOM, 4=SENS)
* @param {Array<number>} types - Array of contact types to filter (1=COM, 2=REP, 3=ROOM, 4=SENS)
*/
function savePendingTypeFilter(types) {
try {
@@ -1168,7 +1168,7 @@ function savePendingTypeFilter(types) {
/**
* Load pending contacts type filter from localStorage.
*
* @returns {Array<number>} Array of contact types (default: [1] for CLI only)
* @returns {Array<number>} Array of contact types (default: [1] for COM only)
*/
function loadPendingTypeFilter() {
try {
@@ -1184,17 +1184,17 @@ function loadPendingTypeFilter() {
} catch (e) {
console.error('Failed to load pending type filter from localStorage:', e);
}
// Default: CLI only (most common use case)
// Default: COM only (most common use case)
return [1];
}
/**
* Set type filter badges based on types array.
* @param {Array<number>} types - Array of contact types (1=CLI, 2=REP, 3=ROOM, 4=SENS)
* @param {Array<number>} types - Array of contact types (1=COM, 2=REP, 3=ROOM, 4=SENS)
*/
function setTypeFilterBadges(types) {
const badges = {
1: document.getElementById('typeFilterCLI'),
1: document.getElementById('typeFilterCOM'),
2: document.getElementById('typeFilterREP'),
3: document.getElementById('typeFilterROOM'),
4: document.getElementById('typeFilterSENS')
@@ -1218,7 +1218,7 @@ function setTypeFilterBadges(types) {
*/
function getSelectedTypes() {
const types = [];
if (document.getElementById('typeFilterCLI')?.classList.contains('active')) types.push(1);
if (document.getElementById('typeFilterCOM')?.classList.contains('active')) types.push(1);
if (document.getElementById('typeFilterREP')?.classList.contains('active')) types.push(2);
if (document.getElementById('typeFilterROOM')?.classList.contains('active')) types.push(3);
if (document.getElementById('typeFilterSENS')?.classList.contains('active')) types.push(4);
@@ -1269,7 +1269,7 @@ async function loadPendingContacts() {
if (filteredCountBadge) filteredCountBadge.textContent = '0';
filteredPendingContacts = [];
} else {
// Initialize filtered list and apply filters (default: CLI only)
// Initialize filtered list and apply filters (default: COM only)
filteredPendingContacts = [...pendingContacts];
applyPendingFilters();
@@ -1338,11 +1338,11 @@ function createContactCard(contact, index) {
const typeBadge = document.createElement('span');
typeBadge.className = 'badge type-badge';
typeBadge.textContent = contact.type_label || 'CLI';
typeBadge.textContent = contact.type_label || 'COM';
// Color-code by type (same as existing contacts)
switch (contact.type_label) {
case 'CLI':
case 'COM':
typeBadge.classList.add('bg-primary');
break;
case 'REP':
@@ -1441,7 +1441,7 @@ async function approveContact(contact, index) {
'Content-Type': 'application/json'
},
body: JSON.stringify({
public_key: contact.public_key // ALWAYS use full public_key (works for CLI, ROOM, etc.)
public_key: contact.public_key // ALWAYS use full public_key (works for COM, ROOM, etc.)
})
});
@@ -1556,7 +1556,7 @@ function showBatchApprovalModal() {
typeBadge.textContent = contact.type_label;
switch (contact.type_label) {
case 'CLI':
case 'COM':
typeBadge.classList.add('bg-primary');
break;
case 'REP':
@@ -1675,7 +1675,7 @@ function showBatchIgnoreModal() {
typeBadge.textContent = contact.type_label;
switch (contact.type_label) {
case 'CLI': typeBadge.classList.add('bg-primary'); break;
case 'COM': typeBadge.classList.add('bg-primary'); break;
case 'REP': typeBadge.classList.add('bg-success'); break;
case 'ROOM': typeBadge.classList.add('bg-info'); break;
case 'SENS': typeBadge.classList.add('bg-warning', 'text-dark'); break;
@@ -2180,7 +2180,7 @@ function createExistingContactCard(contact, index) {
if (contact.type_label) {
typeBadge.textContent = contact.type_label;
switch (contact.type_label) {
case 'CLI': typeBadge.classList.add('bg-primary'); break;
case 'COM': typeBadge.classList.add('bg-primary'); break;
case 'REP': typeBadge.classList.add('bg-success'); break;
case 'ROOM': typeBadge.classList.add('bg-info'); break;
case 'SENS': typeBadge.classList.add('bg-warning'); break;
+2 -2
View File
@@ -508,7 +508,7 @@ function createDropdownItem(name, conversationId, isUnread, contact) {
if (contact && contact.type_label) {
const badge = document.createElement('span');
badge.className = 'badge';
const colors = { CLI: 'bg-primary', REP: 'bg-success', ROOM: 'bg-info', SENS: 'bg-warning' };
const colors = { COM: 'bg-primary', REP: 'bg-success', ROOM: 'bg-info', SENS: 'bg-warning' };
badge.classList.add(colors[contact.type_label] || 'bg-secondary');
badge.textContent = contact.type_label;
el.appendChild(badge);
@@ -695,7 +695,7 @@ function populateContactInfoModal() {
if (contact.type_label) {
const badge = document.createElement('span');
badge.className = 'badge';
const colors = { CLI: 'bg-primary', REP: 'bg-success', ROOM: 'bg-info', SENS: 'bg-warning' };
const colors = { COM: 'bg-primary', REP: 'bg-success', ROOM: 'bg-info', SENS: 'bg-warning' };
badge.classList.add(colors[contact.type_label] || 'bg-secondary');
badge.textContent = contact.type_label;
nameRow.appendChild(badge);
+1 -1
View File
@@ -421,7 +421,7 @@
<!-- Type filter (hidden for single contact view) -->
<div id="mapTypeFilter" class="d-none px-3 py-2 border-bottom bg-light">
<div class="d-flex flex-wrap gap-2 align-items-center">
<span class="map-filter-badge active" data-type="1" id="mapFilterCLI">CLI</span>
<span class="map-filter-badge active" data-type="1" id="mapFilterCOM">COM</span>
<span class="map-filter-badge active" data-type="2" id="mapFilterREP">REP</span>
<span class="map-filter-badge active" data-type="3" id="mapFilterROOM">ROOM</span>
<span class="map-filter-badge active" data-type="4" id="mapFilterSENS">SENS</span>
+1 -1
View File
@@ -41,7 +41,7 @@
<!-- Type Filter -->
<select class="form-select" id="typeFilter">
<option value="ALL">All Types</option>
<option value="CLI">CLI</option>
<option value="COM">COM</option>
<option value="REP">REP</option>
<option value="ROOM">ROOM</option>
<option value="SENS">SENS</option>
+2 -2
View File
@@ -85,8 +85,8 @@
<label class="form-label">Contact Types:</label>
<div class="d-flex flex-wrap gap-2">
<div class="form-check">
<input class="form-check-input cleanup-type-filter" type="checkbox" value="1" id="cleanupTypeCLI" checked>
<label class="form-check-label" for="cleanupTypeCLI">CLI</label>
<input class="form-check-input cleanup-type-filter" type="checkbox" value="1" id="cleanupTypeCOM" checked>
<label class="form-check-label" for="cleanupTypeCOM">COM</label>
</div>
<div class="form-check">
<input class="form-check-input cleanup-type-filter" type="checkbox" value="2" id="cleanupTypeREP" checked>
+1 -1
View File
@@ -35,7 +35,7 @@
<!-- Type Filter Badges -->
<div class="d-flex flex-wrap gap-2 mb-2">
<span class="type-filter-badge active" data-type="CLI" id="typeFilterCLI">CLI</span>
<span class="type-filter-badge active" data-type="COM" id="typeFilterCOM">COM</span>
<span class="type-filter-badge" data-type="REP" id="typeFilterREP">REP</span>
<span class="type-filter-badge" data-type="ROOM" id="typeFilterROOM">ROOM</span>
<span class="type-filter-badge" data-type="SENS" id="typeFilterSENS">SENS</span>
+1 -1
View File
@@ -286,7 +286,7 @@
<input type="text" class="form-control" id="searchInput" placeholder="Search by name or public key...">
<select class="form-select" id="typeFilter" style="max-width: 150px;">
<option value="ALL">All Types</option>
<option value="CLI">CLI</option>
<option value="COM">COM</option>
<option value="REP">REP</option>
<option value="ROOM">ROOM</option>
<option value="SENS">SENS</option>
+3 -3
View File
@@ -165,12 +165,12 @@
user-select: none;
}
.type-filter-badge[data-type="CLI"] {
.type-filter-badge[data-type="COM"] {
color: #0d6efd;
background-color: white;
border: 2px solid #0d6efd;
}
.type-filter-badge[data-type="CLI"].active {
.type-filter-badge[data-type="COM"].active {
color: white;
background-color: #0d6efd;
}
@@ -482,7 +482,7 @@
<div id="mapTypeFilter" class="d-none px-3 py-2 border-bottom bg-light">
<div class="d-flex flex-wrap gap-2 align-items-center">
<span class="small text-muted me-1">Show:</span>
<span class="map-filter-badge active" data-type="1" id="mapFilterCLI">CLI</span>
<span class="map-filter-badge active" data-type="1" id="mapFilterCOM">COM</span>
<span class="map-filter-badge active" data-type="2" id="mapFilterREP">REP</span>
<span class="map-filter-badge active" data-type="3" id="mapFilterROOM">ROOM</span>
<span class="map-filter-badge active" data-type="4" id="mapFilterSENS">SENS</span>
+1 -1
View File
@@ -19,7 +19,7 @@ This guide explains how to manage a MeshCore repeater device directly from the m
<img src="../images/RPT-Mgmt-03-new-rpt-approve.png" alt="Approve repeater" width="200px">
- Reset your search filter (it is recommended to leave CLI selected only) and return to the main chat view
- Reset your search filter (it is recommended to leave COM selected only) and return to the main chat view
<img src="../images/RPT-Mgmt-04-back-to-home.png" alt="Return to home" width="200px">
+1 -1
View File
@@ -330,7 +330,7 @@ After successful installation:
1. **Join channels** - Create or join encrypted channels with other users
2. **Configure contacts** - Enable manual approval if desired
3. **Test Direct Messages** - Send DM to other CLI contacts
3. **Test Direct Messages** - Send DM to other COM contacts
4. **Set up backups** - Schedule regular backups of `data/` directory
5. **Read full documentation** - See [User Guide](user-guide.md) for all features
+9 -9
View File
@@ -149,7 +149,7 @@ Access the Direct Messages feature:
1. **Select a recipient** from the dropdown at the top:
- **Existing conversations** are shown first (with message history)
- Separator: "--- Available contacts ---"
- **All client contacts** from your device (only CLI type, no repeaters/rooms)
- **All companion contacts** from your device (only COM type, no repeaters/rooms)
- You can start a new conversation with anyone in your contacts list
2. Type your message in the input field (max 140 bytes, same as channels)
3. Use the emoji picker button to insert emojis
@@ -162,7 +162,7 @@ Access the Direct Messages feature:
- When you return to the DM page, it automatically opens the last conversation you were viewing
- This works similarly to how the main page remembers your selected channel
**Note:** Only client contacts (CLI) are shown in the dropdown. Repeaters (REP), rooms (ROOM), and sensors (SENS) are automatically filtered out.
**Note:** Only companion contacts (COM) are shown in the dropdown. Repeaters (REP), rooms (ROOM), and sensors (SENS) are automatically filtered out.
### Message Status Indicators
@@ -206,8 +206,8 @@ When manual approval is enabled, new contacts appear in the Pending Contacts lis
**View contact details:**
- Contact name with emoji (if present)
- Type badge (CLI, REP, ROOM, SENS) with color coding:
- CLI (blue): Regular clients
- Type badge (COM, REP, ROOM, SENS) with color coding:
- COM (blue): Companions (clients)
- REP (green): Repeaters
- ROOM (cyan): Room servers
- SENS (yellow): Sensors
@@ -216,7 +216,7 @@ When manual approval is enabled, new contacts appear in the Pending Contacts lis
- Map button (when GPS coordinates are available)
**Filter contacts:**
- By type: Use checkboxes to show only specific contact types (default: CLI only)
- By type: Use checkboxes to show only specific contact types (default: COM only)
- By name or key: Search by partial contact name or public key prefix
**Approve contacts:**
@@ -234,7 +234,7 @@ When manual approval is enabled, new contacts appear in the Pending Contacts lis
### Existing Contacts
The Existing Contacts section displays all contacts currently stored on your device (CLI, REP, ROOM, SENS types).
The Existing Contacts section displays all contacts currently stored on your device (COM, REP, ROOM, SENS types).
**Features:**
- **Counter badge** - Shows current contact count vs. 350 limit (MeshCore device max)
@@ -242,7 +242,7 @@ The Existing Contacts section displays all contacts currently stored on your dev
- Yellow: Warning (300-339 contacts)
- Red (pulsing): Alarm (≥ 340 contacts)
- **Search** - Filter contacts by name or public key prefix
- **Type filter** - Show only specific contact types (All / CLI / REP / ROOM / SENS)
- **Type filter** - Show only specific contact types (All / COM / REP / ROOM / SENS)
- **Contact cards** - Display name, type badge, public key prefix, path info, and last seen timestamp
- **Last Seen** - Shows when each contact was last active with activity indicators:
- 🟢 **Active** (seen < 5 minutes ago)
@@ -253,7 +253,7 @@ The Existing Contacts section displays all contacts currently stored on your dev
**Managing contacts:**
1. **Search contacts:** Type in the search box to filter by name or public key prefix
2. **Filter by type:** Use the type dropdown to show only CLI, REP, ROOM, or SENS
2. **Filter by type:** Use the type dropdown to show only COM, REP, ROOM, or SENS
3. **Copy public key:** Click "Copy Key" button to copy the public key prefix to clipboard
4. **Delete a contact:** Click the "Delete" button (red trash icon) and confirm
@@ -286,7 +286,7 @@ The advanced cleanup tool allows you to filter and remove contacts based on mult
3. Configure filters:
- **Name Filter:** Enter partial contact name to search (optional)
- **Advanced Filters** (collapsible):
- **Contact Types:** Select which types to include (CLI, REP, ROOM, SENS)
- **Contact Types:** Select which types to include (COM, REP, ROOM, SENS)
- **Date Field:** Choose between "Last Advert" (recommended) or "Last Modified"
- **Days of Inactivity:** Contacts inactive for more than X days (0 = ignore)
4. Click **Preview Cleanup** to see matching contacts
+3 -3
View File
@@ -30,7 +30,7 @@ EXPECTED_CONTACT_FIELDS = {
}
# Valid contact types in text format
VALID_CONTACT_TYPES = {"CLI", "REP", "ROOM", "SENS"}
VALID_CONTACT_TYPES = {"COM", "REP", "ROOM", "SENS"}
# Expected fields in /health response
EXPECTED_HEALTH_FIELDS = {
@@ -148,7 +148,7 @@ class CompatChecker:
return
# Parse using same logic as cli.py parse_contacts()
type_counts = {"CLI": 0, "REP": 0, "ROOM": 0, "SENS": 0}
type_counts = {"COM": 0, "REP": 0, "ROOM": 0, "SENS": 0}
parsed = 0
unparsed_lines = []
@@ -247,7 +247,7 @@ class CompatChecker:
stdout = data.get("stdout", "").strip()
if not stdout:
self.add(self.WARN, cat, "empty response (no CLI contacts)")
self.add(self.WARN, cat, "empty response (no COM contacts)")
return
# contact_info returns multiple JSON objects (one per contact)