feat(demo): mock coverage for v1.7.117/118 features
Some checks failed
Demo images / Build & push demo images (push) Failing after 3m19s

- mesh.transport-advice: peer 1 federated (LoRa/FIPS/Tor pills in the
  image modal), others radio-only.
- Scripted Flash LoRa job: flash-list-firmware + flash-device +
  flash-status advancing download → erase → write → done over ~35s
  with live log tail and percent, plus cancel.
- Demo messages carry per-message transport (meshcore/reticulum/fips/
  tor variety for the pills + animated route modal) and
  sender_pubkey/sender_seq so reactions/replies work.
- Chat-action acks: send-reaction/reply/read-receipt, edit/delete/
  forward, send-channel, mesh.refresh, reboot-radio.
- Services classification demo: self-deployed "podsteadr" stack — main
  app launchable, its MediaMTX backend (ui:null) files under Services
  with no Launch button, mirroring ui_detection's verdicts.

All verified against the running mock: 11/11 runtime checks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-07-29 10:20:41 -04:00
parent 14feb1feb9
commit bea7f24a4f

View File

@ -777,7 +777,7 @@ const SEED_MOCKDATA = {
}
// Helper to build a static app entry
function staticApp({ id, title, version, shortDesc, longDesc, license, state, lanPort, torHost, icon }) {
function staticApp({ id, title, version, shortDesc, longDesc, license, state, lanPort, torHost, icon, noUi }) {
return {
title,
version,
@ -800,8 +800,13 @@ function staticApp({ id, title, version, shortDesc, longDesc, license, state, la
'support-site': '#',
'marketing-site': '#',
'donation-url': null,
// ui:null mirrors the real backend's ui_detection verdict for headless
// services (databases/APIs/workers): address present, no Launch button,
// files under the Services tab.
interfaces: {
main: { name: 'Web Interface', description: `${title} web interface`, ui: true },
main: noUi
? { name: 'Service', description: `${title} backend service`, ui: null }
: { name: 'Web Interface', description: `${title} web interface`, ui: true },
},
},
installed: {
@ -891,6 +896,28 @@ const staticDevApps = {
lanPort: 4403,
icon: '/assets/img/app-icons/meshcore.svg',
}),
// Self-deployed compose stack (v1.7.117+ services classification demo):
// the main app serves a real web page → launchable; its media backend has
// an exposed port but no UI → Services tab, no Launch button.
podsteadr: staticApp({
id: 'podsteadr',
title: 'Podsteadr',
version: '0.4.1',
shortDesc: 'Self-hosted podcast studio',
longDesc: 'Record, host, and publish podcasts from your own node — deployed by hand outside the app store to demo third-party stacks.',
state: 'running',
lanPort: 8095,
}),
'podsteadr-mediamtx': staticApp({
id: 'podsteadr-mediamtx',
title: 'Podsteadr MediaMTX',
version: '1.9.3',
shortDesc: 'RTMP/WebRTC media backend',
longDesc: 'Streaming backend for Podsteadr. Speaks RTMP and WebRTC on exposed ports — no web page of its own.',
state: 'running',
lanPort: 8889,
noUi: true,
}),
filebrowser: staticApp({
id: 'filebrowser',
title: 'File Browser',
@ -3030,18 +3057,18 @@ app.post('/rpc/v1', (req, res) => {
const limit = params?.limit || 100
const now = Date.now()
const allMessages = [
{ id: 1, direction: 'received', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Node online. Bitcoin Knots synced to tip.', timestamp: new Date(now - 3600000).toISOString(), delivered: true, encrypted: true, message_type: 'text' },
{ id: 2, direction: 'sent', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Good. Electrs index at 98%. Channel capacity 2.5M sats.', timestamp: new Date(now - 3540000).toISOString(), delivered: true, encrypted: true, message_type: 'text' },
{ id: 3, direction: 'received', peer_contact_id: 2, peer_name: 'satoshi-relay', plaintext: 'Block #890,413 relayed. Fees avg 12 sat/vB.', timestamp: new Date(now - 3000000).toISOString(), delivered: true, encrypted: true, message_type: 'block_header', typed_payload: { alert_type: 'block_header', message: 'Block #890,413 — 2,847 txs, 12 sat/vB avg fee', signed: true } },
{ id: 4, direction: 'received', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Invoice: 50,000 sats — Channel opening fee', timestamp: new Date(now - 1800000).toISOString(), delivered: true, encrypted: true, message_type: 'invoice', typed_payload: { bolt11: 'lnbc500000n1pjmesh...truncated...', amount_sats: 50000, memo: 'Channel opening fee', paid: false } },
{ id: 5, direction: 'sent', peer_contact_id: 4, peer_name: 'bunker-alpha', plaintext: 'Running mesh-only mode. No internet for 48h. All good.', timestamp: new Date(now - 900000).toISOString(), delivered: true, encrypted: true, message_type: 'text' },
{ id: 6, direction: 'received', peer_contact_id: 4, peer_name: 'bunker-alpha', plaintext: 'Copy. Block height 890,412 via compact headers.', timestamp: new Date(now - 840000).toISOString(), delivered: true, encrypted: true, message_type: 'text' },
{ id: 7, direction: 'received', peer_contact_id: 3, peer_name: 'mountain-node', plaintext: 'EMERGENCY: Solar array failure. Running on battery reserve.', timestamp: new Date(now - 600000).toISOString(), delivered: true, encrypted: false, message_type: 'alert', typed_payload: { alert_type: 'emergency', message: 'Solar array failure. Running on battery reserve. ETA 4h before shutdown.', coordinate: { lat: 39507400, lng: -106042800, label: 'Mountain relay site' }, signed: true } },
{ id: 8, direction: 'sent', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Opening 1M sat channel to your node. Approve?', timestamp: new Date(now - 300000).toISOString(), delivered: true, encrypted: true, message_type: 'text' },
{ id: 9, direction: 'received', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Approved. Waiting for funding tx confirmation.', timestamp: new Date(now - 240000).toISOString(), delivered: true, encrypted: true, message_type: 'text' },
{ id: 10, direction: 'sent', peer_contact_id: 3, peer_name: 'mountain-node', plaintext: 'Location shared', timestamp: new Date(now - 120000).toISOString(), delivered: true, encrypted: false, message_type: 'coordinate', typed_payload: { lat: 30267200, lng: -97743100, label: 'Supply drop point' } },
{ id: 11, direction: 'received', peer_contact_id: 4, peer_name: 'bunker-alpha', plaintext: 'Dead man switch check-in. All systems nominal. Battery 78%.', timestamp: new Date(now - 60000).toISOString(), delivered: true, encrypted: true, message_type: 'alert', typed_payload: { alert_type: 'status', message: 'All systems nominal. Battery 78%. Mesh uptime 14d.', signed: true } },
{ id: 12, direction: 'received', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Invoice paid: 50,000 sats', timestamp: new Date(now - 30000).toISOString(), delivered: true, encrypted: true, message_type: 'invoice', typed_payload: { bolt11: 'lnbc500000n1pjmesh...truncated...', amount_sats: 50000, memo: 'Channel opening fee', paid: true, payment_hash: 'a1b2c3d4e5f6...' } },
{ id: 1, direction: 'received', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Node online. Bitcoin Knots synced to tip.', timestamp: new Date(now - 3600000).toISOString(), delivered: true, encrypted: true, transport: 'meshcore', sender_pubkey: 'demo01abababababababababababababababababababababababababababab', sender_seq: 1, message_type: 'text' },
{ id: 2, direction: 'sent', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Good. Electrs index at 98%. Channel capacity 2.5M sats.', timestamp: new Date(now - 3540000).toISOString(), delivered: true, encrypted: true, transport: 'meshcore', sender_pubkey: 'demo02abababababababababababababababababababababababababababab', sender_seq: 2, message_type: 'text' },
{ id: 3, direction: 'received', peer_contact_id: 2, peer_name: 'satoshi-relay', plaintext: 'Block #890,413 relayed. Fees avg 12 sat/vB.', timestamp: new Date(now - 3000000).toISOString(), delivered: true, encrypted: true, transport: 'reticulum', sender_pubkey: 'demo03abababababababababababababababababababababababababababab', sender_seq: 3, message_type: 'block_header', typed_payload: { alert_type: 'block_header', message: 'Block #890,413 — 2,847 txs, 12 sat/vB avg fee', signed: true } },
{ id: 4, direction: 'received', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Invoice: 50,000 sats — Channel opening fee', timestamp: new Date(now - 1800000).toISOString(), delivered: true, encrypted: true, transport: 'fips', sender_pubkey: 'demo04abababababababababababababababababababababababababababab', sender_seq: 4, message_type: 'invoice', typed_payload: { bolt11: 'lnbc500000n1pjmesh...truncated...', amount_sats: 50000, memo: 'Channel opening fee', paid: false } },
{ id: 5, direction: 'sent', peer_contact_id: 4, peer_name: 'bunker-alpha', plaintext: 'Running mesh-only mode. No internet for 48h. All good.', timestamp: new Date(now - 900000).toISOString(), delivered: true, encrypted: true, transport: 'reticulum', sender_pubkey: 'demo05abababababababababababababababababababababababababababab', sender_seq: 5, message_type: 'text' },
{ id: 6, direction: 'received', peer_contact_id: 4, peer_name: 'bunker-alpha', plaintext: 'Copy. Block height 890,412 via compact headers.', timestamp: new Date(now - 840000).toISOString(), delivered: true, encrypted: true, transport: 'reticulum', sender_pubkey: 'demo06abababababababababababababababababababababababababababab', sender_seq: 6, message_type: 'text' },
{ id: 7, direction: 'received', peer_contact_id: 3, peer_name: 'mountain-node', plaintext: 'EMERGENCY: Solar array failure. Running on battery reserve.', timestamp: new Date(now - 600000).toISOString(), delivered: true, encrypted: false, transport: 'meshcore', sender_pubkey: 'demo07abababababababababababababababababababababababababababab', sender_seq: 7, message_type: 'alert', typed_payload: { alert_type: 'emergency', message: 'Solar array failure. Running on battery reserve. ETA 4h before shutdown.', coordinate: { lat: 39507400, lng: -106042800, label: 'Mountain relay site' }, signed: true } },
{ id: 8, direction: 'sent', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Opening 1M sat channel to your node. Approve?', timestamp: new Date(now - 300000).toISOString(), delivered: true, encrypted: true, transport: 'fips', sender_pubkey: 'demo08abababababababababababababababababababababababababababab', sender_seq: 8, message_type: 'text' },
{ id: 9, direction: 'received', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Approved. Waiting for funding tx confirmation.', timestamp: new Date(now - 240000).toISOString(), delivered: true, encrypted: true, transport: 'tor', sender_pubkey: 'demo09abababababababababababababababababababababababababababab', sender_seq: 9, message_type: 'text' },
{ id: 10, direction: 'sent', peer_contact_id: 3, peer_name: 'mountain-node', plaintext: 'Location shared', timestamp: new Date(now - 120000).toISOString(), delivered: true, encrypted: false, transport: 'meshcore', sender_pubkey: 'demo10abababababababababababababababababababababababababababab', sender_seq: 10, message_type: 'coordinate', typed_payload: { lat: 30267200, lng: -97743100, label: 'Supply drop point' } },
{ id: 11, direction: 'received', peer_contact_id: 4, peer_name: 'bunker-alpha', plaintext: 'Dead man switch check-in. All systems nominal. Battery 78%.', timestamp: new Date(now - 60000).toISOString(), delivered: true, encrypted: true, transport: 'reticulum', sender_pubkey: 'demo11abababababababababababababababababababababababababababab', sender_seq: 11, message_type: 'alert', typed_payload: { alert_type: 'status', message: 'All systems nominal. Battery 78%. Mesh uptime 14d.', signed: true } },
{ id: 12, direction: 'received', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Invoice paid: 50,000 sats', timestamp: new Date(now - 30000).toISOString(), delivered: true, encrypted: true, transport: 'fips', sender_pubkey: 'demo12abababababababababababababababababababababababababababab', sender_seq: 12, message_type: 'invoice', typed_payload: { bolt11: 'lnbc500000n1pjmesh...truncated...', amount_sats: 50000, memo: 'Channel opening fee', paid: true, payment_hash: 'a1b2c3d4e5f6...' } },
]
return res.json({
result: {
@ -4243,6 +4270,100 @@ app.post('/rpc/v1', (req, res) => {
return res.json({ result: { rejected: true, id: params?.id || '' } })
}
// ── Mesh chat polish + flash flow (v1.7.117/118 demo coverage) ──────
// Image-modal "Send via" pills: peer 1 is federation-reachable (LoRa +
// FIPS + Tor all offered), everyone else is radio-only.
case 'mesh.transport-advice': {
const federated = params?.contact_id === 1
return res.json({
result: {
tier: federated ? 'choose' : 'auto-mesh',
est_seconds: federated ? 4 : 90,
has_tor: federated,
has_fips: federated,
last_transport: federated ? 'fips' : null,
reason: federated
? 'Peer reachable over the FIPS overlay — radio optional'
: 'Radio-only peer',
size: params?.size || 0,
mesh_auto_max: 24576,
mesh_hard_max: 262144,
},
})
}
case 'mesh.flash-list-firmware': {
const versions = {
meshcore: ['v1.7.4', 'v1.7.2', 'v1.6.9'],
meshtastic: ['2.5.15', '2.5.13', '2.4.2'],
reticulum: ['latest (rnodeconf autoinstall)'],
}
return res.json({ result: { versions: versions[params?.family] || [] } })
}
case 'mesh.flash-device': {
mockState.flashJob = {
startedAt: Date.now(),
board: params?.board || 'heltec-v3',
family: params?.family || 'meshcore',
path: params?.path || '/dev/ttyUSB0',
cancelled: false,
}
return res.json({ result: { started: true } })
}
case 'mesh.flash-status': {
const job = mockState.flashJob
if (!job) return res.json({ result: { active: false, done: true, log_tail: [] } })
const t = (Date.now() - job.startedAt) / 1000
// Scripted 35s job: download → erase → write → done.
let stage = 'downloading'
let percent = Math.min(100, Math.round((t / 8) * 100))
if (job.cancelled) stage = 'failed'
else if (t > 30) { stage = 'done'; percent = 100 }
else if (t > 14) { stage = 'writing'; percent = Math.min(100, Math.round(((t - 14) / 16) * 100)) }
else if (t > 8) { stage = 'erasing'; percent = null }
const log = [
`Fetching ${job.family} firmware for ${job.board}...`,
...(t > 4 ? ['Downloaded 2.1 MB firmware image'] : []),
...(t > 8 ? ['esptool erase_flash: chip erase completed'] : []),
...(t > 14 ? [`Writing at 0x00010000... (${percent || 0} %)`] : []),
...(stage === 'done' ? ['Hash of data verified.', 'Hard resetting via RTS pin...'] : []),
...(job.cancelled ? ['ERROR: flash cancelled by user'] : []),
]
const active = stage !== 'done' && stage !== 'failed'
if (!active) mockState.flashJob = active ? job : job // keep for final poll
return res.json({
result: {
active,
board: job.board,
family: job.family,
path: job.path,
stage,
percent,
log_tail: log,
done: !active,
error: job.cancelled ? 'Flash cancelled' : null,
},
})
}
case 'mesh.flash-cancel': {
if (mockState.flashJob) mockState.flashJob.cancelled = true
return res.json({ result: { cancelled: true } })
}
// Chat actions the demo only needs to acknowledge.
case 'mesh.send-reaction':
case 'mesh.send-reply':
case 'mesh.send-read-receipt':
case 'mesh.edit-message':
case 'mesh.delete-message':
case 'mesh.forward-message':
case 'mesh.send-channel':
case 'mesh.refresh':
case 'mesh.reboot-radio': {
return res.json({ result: { ok: true, sent: true } })
}
default: {
console.log(`[RPC] Unknown method: ${method}`)
return res.json({