From 2f2a9a6542d2194e50ffba6f382e4fdffd481838 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sat, 10 Oct 2026 14:21:46 +0200 Subject: feat(android): one backup destination, a group the account owns alone Chosen once at the top of Android Sync for every kind; photos and contacts go into /-photos and -contacts. Owner and sole member are checked at set-up and before every run. Co-Authored-By: Claude Opus 5.5 --- .../meshbay-hub/tests/harness/photo_sync_probe.py | 40 ++++++++++++++++------ 1 file changed, 29 insertions(+), 11 deletions(-) (limited to 'packages/meshbay-hub/tests/harness') diff --git a/packages/meshbay-hub/tests/harness/photo_sync_probe.py b/packages/meshbay-hub/tests/harness/photo_sync_probe.py index 08c21b9..c1c4599 100644 --- a/packages/meshbay-hub/tests/harness/photo_sync_probe.py +++ b/packages/meshbay-hub/tests/harness/photo_sync_probe.py @@ -8,8 +8,9 @@ Only the Android application has this section, and its engine is Chromium, so one engine is the whole of it. The bridge is a stand-in that answers the way the phone does; the group connection (`connection-pool.js`) is replaced in the copy by one that hands out a node with long, space-free folder names, because -those are what widen a page (CLAUDE.md). Three states: setting up, set up with -a refusal showing, and setting up with only selected photos allowed. +those are what widen a page (CLAUDE.md). Four states: choosing where backups +go, setting up photos, set up with a refusal showing, and setting up with only +selected photos allowed. photo_sync_probe.py prints one JSON object, keyed """ @@ -61,16 +62,20 @@ window.addEventListener('error', (e) => note(e.message)); window.addEventListener('unhandledrejection', (e) => note((e.reason && e.reason.message) || e.reason)); const MODE = location.hash.slice(1); -const cfg = { - account: 'bob', groupId: 'g1', groupName: '__GROUP__', owner: '__OWNER__', - folder: '__FOLDER__', albums: ['cam'], includeExisting: true, since: 0, -}; +const dest = MODE === 'dest' ? null + : { account: 'bob', groupId: 'g1', groupName: '__GROUP__', folder: '__FOLDER__' }; +const cfg = { albums: ['cam'], includeExisting: true, since: 0 }; window.meshbay = { hubBase: () => 'https://hub.invalid', + phoneSync: { + destination: async () => dest, + setDestination: async (d) => d, + }, photoSync: { status: async () => ({ permission: MODE === 'partial' ? 'partial' : 'granted', unmetered: true, sent: 1240, config: MODE === 'configured' ? cfg : null, lastCompleted: Date.now(), + destination: dest, dir: dest && '__FOLDER__/bob-photos', failure: MODE === 'configured' ? 'disk_full' : null, }), albums: async () => [ @@ -83,6 +88,15 @@ window.meshbay = { sent: async () => true, completed: async () => ({}), failed: async () => true, keepAlive: async () => true, }, + contactSync: { + status: async () => ({ + permission: 'granted', on: MODE === 'configured', destination: dest, + dir: dest && '__FOLDER__/bob-contacts', sent: 3, lastCompleted: Date.now(), + lastSent: dest && '__FOLDER__/bob-contacts/contacts-2026-10-10-0900.vcf', failure: null, + }), + permit: async () => ({}), configure: async () => ({}), plan: async () => ({ item: null }), + sent: async () => true, completed: async () => ({}), failed: async () => true, + }, };
@@ -92,14 +106,18 @@ import { initLocale } from './i18n.js'; import { startPhotoSync } from './photo-sync.js'; import { AndroidSyncPage } from './android-sync-page.js'; await initLocale(); -startPhotoSync({ hub: '', getUser: () => ({ username: 'bob', token: 't' }), - getBundleKey: async () => '' }); +startPhotoSync({ + hub: '', getUser: () => ({ username: 'bob', userId: 'u-bob', token: 't' }), + hubFetch: async () => ({ admin_id: 'u-bob', members: [{ user_id: 'u-bob', username: 'bob' }] }), + getBundleKey: async () => '' }); const groups = [{ id: 'g1', name: '__GROUP__', owner_username: '__OWNER__' }]; -render(html`<${AndroidSyncPage} user=${{ username: 'bob', token: 't' }} groups=${groups} />`, +render(html`<${AndroidSyncPage} user=${{ username: 'bob', userId: 'u-bob', token: 't' }} + groups=${groups} />`, document.getElementById('root')); setTimeout(() => { if (MODE === 'configured') return; [...document.querySelectorAll('button')].find((b) => b.textContent.includes('Set up')).click(); + if (MODE !== 'dest') return; setTimeout(() => { const sel = document.querySelector('select'); sel.value = 'g1'; @@ -112,7 +130,7 @@ setTimeout(() => { OUTER = """