fix(channels): dedupe Meshtastic channel list, un-bury MeshCore channels card (#105)

The Meshtastic connection page listed channels twice (the pre-existing
Observe-Channels selector plus a redundant read-only Index/Name/Role
table); remove the redundant table and its now-unused getChannels() /
MeshtasticChannel API helpers. On the MeshCore companion page, move the
Channels (name/hash/key) card up to sit right after the Status card,
above the Advertising settings, so it is no longer buried at the bottom.
No behavior change; layout/UX only.

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
malice 2026-07-09 10:55:29 -06:00 committed by GitHub
commit fed63b2344
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 41 additions and 92 deletions

View file

@ -606,18 +606,6 @@ export async function getMeshcoreChannelsDetail(): Promise<MeshcoreChannelsDetai
return fetchJson<MeshcoreChannelsDetail>('/api/meshcore/channels/detail')
}
// Meshtastic radio channels (routes by channel index).
export interface MeshtasticChannel {
index: number
name: string
role: string
enabled: boolean
}
export async function getChannels(): Promise<MeshtasticChannel[]> {
return fetchJson<MeshtasticChannel[]>('/api/channels')
}
export interface MeshcoreContact {
name: string | null
pubkey: string

View file

@ -240,45 +240,6 @@ export default function MeshCoreCompanion() {
)}
</div>
{/* Advertising settings */}
<div className="bg-bg-card border border-border">
<div className="px-4 py-3 border-b border-border">
<h3 className="text-sm font-medium text-slate-200">Advertising</h3>
</div>
<div className="px-4 py-4 space-y-4">
<div className="space-y-1">
<label className="text-xs font-medium text-[#777] uppercase tracking-wide">
Auto-advert interval
</label>
<div className="flex items-center gap-3">
<select
value={advertIntervalHours}
onChange={(e) => setAdvertIntervalHours(Number(e.target.value))}
className="bg-[#0a0e17] border border-[#1e2a3a] text-slate-200 text-sm rounded px-2 py-1.5 focus:outline-none focus:border-accent"
>
<option value={0}>Disabled</option>
<option value={1}>Every 1 hour</option>
<option value={3}>Every 3 hours (default)</option>
<option value={6}>Every 6 hours</option>
<option value={12}>Every 12 hours</option>
<option value={24}>Every 24 hours</option>
</select>
<button
onClick={handleSaveAdvertInterval}
disabled={advertIntervalSaving}
className="px-3 py-1.5 text-sm bg-accent/10 hover:bg-accent/20 text-accent border border-accent/30 rounded disabled:opacity-50 transition-colors"
>
{advertIntervalSaving ? 'Saving…' : advertIntervalSaved ? 'Saved' : 'Save'}
</button>
</div>
<p className="text-xs text-[#555]">
AIDA sends a flood advertisement at this interval so it stays discoverable.
Stored in <code className="text-accent/80">connection.meshcore_advert_interval_seconds</code>.
</p>
</div>
</div>
</div>
{/* Channel list — Name · Hash · Key (read-only) */}
<div className="bg-bg-card border border-border">
<div className="px-4 py-3 border-b border-border">
@ -329,6 +290,46 @@ export default function MeshCoreCompanion() {
<div className="px-4 py-3 text-sm text-[#777]">No channels</div>
)}
</div>
{/* Advertising settings */}
<div className="bg-bg-card border border-border">
<div className="px-4 py-3 border-b border-border">
<h3 className="text-sm font-medium text-slate-200">Advertising</h3>
</div>
<div className="px-4 py-4 space-y-4">
<div className="space-y-1">
<label className="text-xs font-medium text-[#777] uppercase tracking-wide">
Auto-advert interval
</label>
<div className="flex items-center gap-3">
<select
value={advertIntervalHours}
onChange={(e) => setAdvertIntervalHours(Number(e.target.value))}
className="bg-[#0a0e17] border border-[#1e2a3a] text-slate-200 text-sm rounded px-2 py-1.5 focus:outline-none focus:border-accent"
>
<option value={0}>Disabled</option>
<option value={1}>Every 1 hour</option>
<option value={3}>Every 3 hours (default)</option>
<option value={6}>Every 6 hours</option>
<option value={12}>Every 12 hours</option>
<option value={24}>Every 24 hours</option>
</select>
<button
onClick={handleSaveAdvertInterval}
disabled={advertIntervalSaving}
className="px-3 py-1.5 text-sm bg-accent/10 hover:bg-accent/20 text-accent border border-accent/30 rounded disabled:opacity-50 transition-colors"
>
{advertIntervalSaving ? 'Saving…' : advertIntervalSaved ? 'Saved' : 'Save'}
</button>
</div>
<p className="text-xs text-[#555]">
AIDA sends a flood advertisement at this interval so it stays discoverable.
Stored in <code className="text-accent/80">connection.meshcore_advert_interval_seconds</code>.
</p>
</div>
</div>
</div>
</>
)}
</div>

View file

@ -4,7 +4,7 @@ import { ConnectionSection, TextInput, NumberInput, Toggle, type ConnectionConfi
import ChannelPicker from '@/components/ChannelPicker'
import NodePicker from '@/components/NodePicker'
import { notifyRestartRequired } from '@/components/RestartBanner'
import { fetchConfig as apiFetchConfig, updateConfig as apiUpdateConfig, sendTestMessage, getChannels, type MeshtasticChannel } from '@/lib/api'
import { fetchConfig as apiFetchConfig, updateConfig as apiUpdateConfig, sendTestMessage } from '@/lib/api'
import { useDirty } from '@/context/DirtyContext'
// Only the fields the "Bot behavior" section edits are typed explicitly; the
@ -37,9 +37,6 @@ export default function MeshtasticConnection() {
const [success, setSuccess] = useState<string | null>(null)
const [hasChanges, setHasChanges] = useState(false)
// Read-only channel listing (index ↔ name ↔ role), fetched from the radio.
const [channels, setChannels] = useState<MeshtasticChannel[]>([])
// Test send state
const [testChannel, setTestChannel] = useState(0)
const [testText, setTestText] = useState('')
@ -89,9 +86,6 @@ export default function MeshtasticConnection() {
useEffect(() => {
document.title = 'Meshtastic Connection - MeshAI'
fetchConfig()
getChannels()
.then(setChannels)
.catch(() => setChannels([]))
}, [fetchConfig])
useEffect(() => {
@ -296,40 +290,6 @@ export default function MeshtasticConnection() {
</div>
)}
{/* Channels card — read-only Index · Name · Role */}
<div className="bg-bg-card border border-border">
<div className="px-4 py-3 border-b border-border">
<h3 className="text-sm font-medium text-slate-200">Channels</h3>
<p className="text-xs text-[#555] mt-1">
Channels configured on the radio. Meshtastic routes by channel index. Read-only.
</p>
</div>
{channels.length > 0 ? (
<div className="overflow-x-auto">
<table className="w-full text-sm text-slate-200">
<thead className="bg-[#161616] border-b border-border">
<tr>
<th className="px-3 py-2 font-sans text-[9px] uppercase tracking-widest text-[#666] text-left">Index</th>
<th className="px-3 py-2 font-sans text-[9px] uppercase tracking-widest text-[#666] text-left">Name</th>
<th className="px-3 py-2 font-sans text-[9px] uppercase tracking-widest text-[#666] text-left">Role</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{channels.map((ch) => (
<tr key={ch.index} className="hover:bg-bg-hover">
<td className="px-3 py-2 font-mono text-xs">{ch.index}</td>
<td className="px-3 py-2">{ch.name}</td>
<td className="px-3 py-2 text-xs text-[#999]">{ch.role}</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<div className="px-4 py-3 text-sm text-[#777]">Node offline channels unavailable</div>
)}
</div>
{/* Send test message card */}
<div className="bg-bg-card border border-border p-6 space-y-4">
<div className="text-xs text-slate-500 uppercase tracking-wide">Send Test Message</div>