'use strict' const { dispatchUiMessage } = require('./index') const { AUDIT_FILTER } = require('pearcord-delivery-receipts') async function ipc (platform, msg) { const ok = await dispatchUiMessage(platform, msg, {}) if (!ok) throw new Error(`unhandled IPC type: ${msg?.type}`) } /** * Full desktop UI journey: onboarding, settings, navigation, invite join, restore. */ async function runFullUiFlowScenario (platform) { await ipc(platform, { type: 'register', username: 'ui_flow_user', displayName: 'Pearcord UI' }) let view = await platform.view() if (!view.onboarded) throw new Error('onboarded after register') if (!view.needsGuild) throw new Error('needsGuild after register') await ipc(platform, { type: 'create-guild', name: 'Pearcord HQ' }) view = await platform.view() if (view.guild?.name !== 'Pearcord HQ') throw new Error('guild name') if (view.needsGuild) throw new Error('needsGuild cleared') if (view.mode !== 'guild') throw new Error('guild mode') if (!view.activeChannelId) throw new Error('active channel') const guildId = view.guild.id const generalId = view.activeChannelId await ipc(platform, { type: 'update-profile', displayName: 'UI Flow Tester' }) await ipc(platform, { type: 'update-user-prefs', prefs: { theme: 'amoled', density: 'compact', showTimestamps: true } }) view = await platform.view() if (view.user?.displayName !== 'UI Flow Tester') throw new Error('profile display name') if (view.userPrefs?.theme !== 'amoled') throw new Error('user prefs theme') await ipc(platform, { type: 'update-guild-settings', name: 'Pearcord HQ Renamed' }) await ipc(platform, { type: 'set-discovery-tags', guildId, tags: ['gaming', 'p2p'] }) await ipc(platform, { type: 'set-automod-config', config: { enabled: true, maxMentions: 4, blockInvites: true } }) await ipc(platform, { type: 'add-guild-boost', amount: 1 }) await ipc(platform, { type: 'set-presence', status: 'idle', customStatus: 'smoke testing' }) view = await platform.view() if (view.guild?.name !== 'Pearcord HQ Renamed') throw new Error('guild renamed') if (!view.automodConfig?.enabled) throw new Error('automod enabled') if ((view.guildBoost?.boostCount || 0) < 1) throw new Error('guild boost') if (view.presence?.self?.customStatus !== 'smoke testing') throw new Error('custom status') await ipc(platform, { type: 'send-message', content: 'UI flow smoke message' }) await ipc(platform, { type: 'select-home' }) view = await platform.view() if (view.mode !== 'home') throw new Error('home mode after select-home') const peerId = 'a'.repeat(32) await ipc(platform, { type: 'open-dm', peerUserId: peerId, peerDisplayName: 'Peer' }) view = await platform.view() if (view.mode !== 'dm') throw new Error('dm mode') await ipc(platform, { type: 'send-message', content: 'DM from UI flow' }) await ipc(platform, { type: 'select-guild', guildId }) view = await platform.view() if (view.mode !== 'guild') throw new Error('back to guild') await ipc(platform, { type: 'select-channel', channelId: generalId }) if (view.activeChannelId !== generalId) { view = await platform.view() if (view.activeChannelId !== generalId) throw new Error('channel reselected') } await ipc(platform, { type: 'update-notification-prefs', prefs: { desktopEnabled: true, notifyOnAllMessages: true } }) view = await platform.view() if (!view.notificationPrefs?.desktopEnabled) throw new Error('notification prefs') if (!view.notificationPrefs?.notifyOnAllMessages) throw new Error('notification prefs patch') const invite = await platform.createInvite() return { guildId, generalId, inviteCode: invite.shareCode || invite.code, view } } /** * Host onboarding: register → create guild → first message (UI IPC path). */ async function runOnboardingHostScenario (platform, opts = {}) { const name = opts.guildName || 'Onboarding Mesh Guild' const username = opts.username || 'onboard_host' await ipc(platform, { type: 'register', username, displayName: opts.displayName || 'Onboard Host' }) let view = await platform.view() if (!view.onboarded) throw new Error('host not onboarded after register') if (!view.needsGuild) throw new Error('host should need guild after register') await ipc(platform, { type: 'create-guild', name }) view = await platform.view() if (!view.guild) throw new Error('host guild missing') if (view.needsGuild) throw new Error('host still needsGuild after create') if (view.mode !== 'guild') throw new Error('host not in guild mode') const marker = opts.marker || `onboard-mesh-${Date.now()}` await ipc(platform, { type: 'send-message', content: `onboarding pre-mesh ${marker}` }) view = await platform.view() if (!view.messages?.some((m) => String(m.content).includes(marker))) { throw new Error('host pre-mesh message missing') } const invite = await platform.createInvite() return { guildId: view.guild.id, channelId: view.activeChannelId, marker, inviteCode: invite.shareCode || invite.code, view } } /** * Guest onboarding: register → join-invite (mirrors UI join modal after needsGuild). */ async function runOnboardingGuestJoinScenario (platform, inviteCode, opts = {}) { await ipc(platform, { type: 'register', username: opts.username || 'onboard_guest', displayName: opts.displayName || 'Onboard Guest' }) let view = await platform.view() if (!view.needsGuild) throw new Error('guest should need guild before join') if (view.guild) throw new Error('guest should not have guild before join') await ipc(platform, { type: 'join-invite', code: inviteCode }) view = await platform.view() if (view.needsGuild) throw new Error('guest still needsGuild after join') if (!view.guild) throw new Error('guest guild missing after join') if (view.mode !== 'guild') throw new Error('guest not in guild mode') if (!view.channels?.length) throw new Error('guest missing channels after join') return { view } } /** * Onboarding host with signed audit CSV export for badge/dashboard smokes (v0.8.83). */ async function runOnboardingHostAuditCsvScenario (platform, opts = {}) { const base = await runOnboardingHostScenario(platform, opts) const guildId = base.guildId await platform._audit('automation.onboarding', { guildId, detail: 'ui ipc csv' }) await ipc(platform, { type: 'export-audit-log', format: 'csv', filter: 'automation', limit: 50, gossip: false, sign: true, recordMesh: false }) await ipc(platform, { type: 'sync-audit-export-csv', filter: 'automation', limit: 50 }) const view = await platform.view() if (!view.auditExportCsvMeta?.signature) { throw new Error('host auditExportCsvMeta missing after IPC export') } if (view.auditExportCsvMeta.signatureValid !== true) { throw new Error('host audit csv signature invalid') } const dash = view.automationHealthDashboard || view.automationScheduleDashboard if (!dash?.auditCsvMesh?.hasSignature) { throw new Error('health dashboard missing auditCsvMesh') } return { ...base, auditExportCsvMeta: view.auditExportCsvMeta, dashboard: dash } } /** * Audit modal filter survives two UI refresh cycles (simulates close/reopen) (v0.8.85). */ async function runAuditModalFilterDoubleRefreshScenario (platform, opts = {}) { const base = await runOnboardingHostScenario(platform, opts) await ipc(platform, { type: 'set-audit-log-export-filter', filter: AUDIT_FILTER.AUTOMATION }) await ipc(platform, { type: 'refresh' }) let view = await platform.view() if (view.auditLogExportFilter !== AUDIT_FILTER.AUTOMATION) { throw new Error('filter lost after first refresh') } await ipc(platform, { type: 'refresh' }) view = await platform.view() if (view.auditLogExportFilter !== AUDIT_FILTER.AUTOMATION) { throw new Error('filter lost after second refresh') } if (view.auditExportScheduleFilter !== AUDIT_FILTER.AUTOMATION) { throw new Error('schedule filter missing after double refresh') } return { ...base, filter: AUDIT_FILTER.AUTOMATION } } module.exports = { runFullUiFlowScenario, runOnboardingHostScenario, runOnboardingGuestJoinScenario, runOnboardingHostAuditCsvScenario, runAuditModalFilterDoubleRefreshScenario, ipc }