@bobfrankston/rmfmail 1.2.190 → 1.2.191
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/TODO.md +7 -7
- package/client/android-bootstrap.bundle.js +10 -2
- package/client/android-bootstrap.bundle.js.map +2 -2
- package/client/app.bundle.js +10 -0
- package/client/app.bundle.js.map +2 -2
- package/client/app.js +18 -0
- package/client/app.js.map +1 -1
- package/client/app.ts +15 -0
- package/client/package.json +1 -1
- package/package.json +3 -3
- package/packages/mailx-store-web/android-bootstrap.d.ts.map +1 -1
- package/packages/mailx-store-web/android-bootstrap.js +24 -2
- package/packages/mailx-store-web/android-bootstrap.js.map +1 -1
- package/packages/mailx-store-web/android-bootstrap.ts +25 -2
- package/packages/mailx-store-web/package.json +1 -1
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-12376 → node_modules.npmglobalize-stash-47576}/.package-lock.json +0 -0
package/TODO.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# mailx TODO
|
|
4
4
|
|
|
5
|
-
*Last updated: 2026-07-30 · v1.2.
|
|
5
|
+
*Last updated: 2026-07-30 · v1.2.190 (bug-sweep: paste-link double-insert generalized, task-delete confirms, ⊘ in viewer address menu, C158 Android double-init + OAuth single-flight guards; stale entries reconciled)*
|
|
6
6
|
|
|
7
7
|
> **Policy: completed items live at the end of this file.** Active sections (autonomous queue, priority tables, quick wins, categorized) show OPEN / PARTIAL / PENDING only. When an item ships, strike it through *and move the row* to the [Done section](#done-recent) (or [Done](#done) for older items). The version-tagged completion log is `DONE.md`; this file's Done section is the in-TODO archive of items that used to appear in the active tables.
|
|
8
8
|
|
|
@@ -18,10 +18,10 @@ These items don't need user input — source-only changes, compile-verified befo
|
|
|
18
18
|
| ~~4~~ | ~~**C122 — Recent-unread count overlay on taskbar icon**~~ — **DONE** (shipped ~v1.1.x 2026-05-29; TODO entry was stale). `updateBadge` in `client/app.ts` renders the red count pill on a 32×32 canvas (badge-only, transparent field) and pushes it via `mailxapi.setTaskbarOverlay`; hooked to `folderCountsChanged` + startup via `updateNewMessageCount`. Badge shows NEW-since-last-seen, matching the "recent-unread" spec. S65 (badge while not running) remains open/blocked. | S | |
|
|
19
19
|
| ~~5~~ | ~~**C126 — Cold-start latency (~28s observed)**~~ — **MOSTLY DONE** (2026-05-11). Root cause IDed from boot log: 43 s of cascading ES module imports through msger's custom protocol IPC (one roundtrip per file). Fixes shipped: (a) bundling via esbuild (`bin/build-bundles.mjs`) collapses the cascade to one fetch per entry point — `client/app.bundle.js` 345 kb, `client/compose/compose.bundle.js` 99 kb; (b) boot-snapshot hydration — bundled app saves `folder-tree` + `ml-body` innerHTML to localStorage every 30 s; inline script in `index.html` restores at cold start before the bundle has fetched. Sub-bullet (c) [500 ms placeholder pause] not in current code at cited line — skipped as stale. Expected cold start now 3-5 s (full WebView2 + bundle parse), with near-instant *feel* via hydrated snapshot after the first run. | M | |
|
|
20
20
|
| **6** | **C125 — Unify desktop + Android IMAP code paths** | M | Today's split (`mailx-imap` Node-only + `mailx-store-web` browser-only) duplicates orchestration that's protocol-identical. Differences boil down to (a) transport factory choice (b) whether clients are persistent — both parameters, not architectural divides. Plan: extract `mailx-imap-core` with ImapManager + ops queue + fast-lane + sync orchestration over a `Transport` and a `Storage` interface, no Node-specific imports. `mailx-imap` and `mailx-store-web` become thin shims that wire engine-specific Storage + Transport. `{persistent: true\|false}` flag on ImapManager covers the lifetime split (no fast-lane queue when ephemeral). Pre-req for Android non-Gmail IMAP without forking sync code. |
|
|
21
|
-
|
|
|
22
|
-
|
|
|
21
|
+
| ~~7~~ | ~~**C156 — Android first-boot/setup progress narration**~~ — **DONE v1.2.191** (2026-07-30, needs on-device verify): new `narrate()` in android-bootstrap emits `bootstrapStatus` events at the consent/GDrive/account-registration stages; client renders them in the status bar, the pre-UI startup banner, and the folder tree's `.folder-loading` slot. | M | |
|
|
22
|
+
| ~~8~~ | ~~**C157 — Android fresh install: paint folder list before message sync**~~ — **DONE v1.2.191** (2026-07-30, needs on-device verify): the sync order was already folder-LIST-first — the real hole was that NOTHING kicked a sync when accounts finally registered (boot syncAll ran before OAuth/GDrive produced any provider, then the tree sat empty until the next 60 s tick). GDrive reconcile and `setupAccount` now fire `syncAll()` immediately after registering accounts, so the tree paints seconds after consent. | S/M | |
|
|
23
23
|
| **10** | **C159 — Harper local grammar check** [M] | M | **Desktop SHIPPED v1.2.168** (2026-07-22): daemon-side Harper engine + `grammarLint` IPC, generic squiggle overlay + suggestion popup across ALL THREE editors, Settings toggle default ON, quoted chains + spelling lints skipped. **Remaining: Android** (host LocalLinter in android-bootstrap, harper.js dist in APK assets like sql.js, ~+18 MB) + per-rule config + userdict merge. Details: `docs/harper-integration.md`. |
|
|
24
|
-
| ~~9~~ | ~~**C158 — Android double-init after fold-triggered reload**~~ — **FIXED v1.2.
|
|
24
|
+
| ~~9~~ | ~~**C158 — Android double-init after fold-triggered reload**~~ — **FIXED v1.2.190** (2026-07-30, needs on-device verify): `initAndroid` now single-flights via `window.__rmfInitAndroid` (window-global so it holds across duplicate module instances — bundle + package-path specifiers), and the OAuth token provider single-flights per email via `window.__rmfOAuthInflight` so concurrent startup callers (reconcile + contacts + syncAll) share one consent instead of launching three. What triggers the reload-on-fold itself is still unidentified — the guards make it harmless; watch logit for the "duplicate suppressed (C158)" line to confirm. | M | |
|
|
25
25
|
| **11** | **C160 — Attachment delete: Gmail + Android halves** [M] | M | Desktop IMAP shipped v1.2.173 (right-click chip → Delete one/all: verified local strip + append-confirm-then-delete server replace, .pre-strip.bak backup, UID rebind). Remaining: Gmail REST path (messages.insert + trash of original — changes message id, provider_id rebind) and Android (bridge method + web-service strip using its own parser). Strip helper lives in mailx-service (enumerateMimeLeafParts) — move to mailx-types when Android needs it. |
|
|
26
26
|
| **12** | **C161 — `_rmfmail` IMAP folder for cross-machine state sync** [M] | M | Bob 2026-07-27: use a hidden `_rmfmail` folder on the mail server itself as a per-account sync channel for state that today rides on Google Drive or nowhere — reminder dismissals, per-machine prefs, read-position, contact edits for non-Google accounts. Advantages over Drive files: works for every IMAP account (no Google dependency), naturally per-account, versioned by APPEND, and reuses the existing connection/sync machinery. Shape: one message per state document with a stable `X-Rmfmail-State: <name>` header, save = delete-and-reappend (the draft-dedup pattern), merge semantics per document type (reminders already have per-key max merge in mailx-types). Design Qs: folder visibility (subscribe or not, hide from folder tree), conflict handling when two machines append concurrently, and whether reminders.jsonc migrates or dual-writes. First shipped consumer of the pattern: draft freshness probe (v1.2.180) already proves the search-by-header primitive. |
|
|
27
27
|
| **5** | **C124 — mailto handler on macOS** (Linux ✅ 2026-07-08 v1.2.115) | S | Linux half shipped: `--register-mailto` on linux writes `~/.local/share/applications/rmfmail.desktop` (`MimeType=x-scheme-handler/mailto;`, quoted node+mailx.js Exec, bare `%u`), runs `xdg-mime default` + `update-desktop-database`; `--unregister-mailto` removes it. **Remaining: macOS** — real `.app` bundle (LaunchServices won't register a bare script) with `Info.plist` `CFBundleURLTypes` declaring `mailto` + a tiny launcher binary; `LSSetDefaultHandlerForURLScheme` programmatically sets the default. Rust `rmfmailto-src/` crate already portable — drop the Win32 imports and `cargo build --target x86_64-apple-darwin` produces the macOS launcher. Needs a Mac to build/verify. |
|
|
@@ -271,7 +271,7 @@ Small, self-contained items. Pick them up between higher-priority blocks without
|
|
|
271
271
|
- ~~**Q158 — Android: "Search for…" lands on the empty viewer pane.**~~ **DONE (2026-07-13, v1.2.132).** runSearch flips narrow layout back to the list pane (viewer narrow-active off, list narrow-hidden off) so the populated search bar + results are visible. [S] Bob 2026-07-13 shot: choosing Search-for from the context menu ran the search but narrow mode stayed on the VIEWER pane ("Select a message to read") — zero feedback. Fix: after a programmatic search, (a) switch narrow/medium layout to the LIST pane, (b) reveal the search bar populated with the query so the state is visible, (c) focus stays on results. Applies to every search entry point, not just the context menu (see feedback_summary: search state must be visible).
|
|
272
272
|
|
|
273
273
|
- **Q151 — AI config window (natural-language config edits).** Bob 2026-05-16: an AI window that understands the config files so he can say "don't show outlook_…@outlook.com". Feasible — mailx already has AI plumbing (aiTransform, provider+key in autocomplete settings) so it reuses the existing key. Design constraint: the AI must NOT free-form-rewrite a JSONC file (a malformed contacts.jsonc wipes everything — just saw it). It proposes a structured operation (`denylist add <email>`, `preferred add …`), shows a diff, the user confirms, and mailx applies it via the existing typed methods (`addToDenylist`, `addPreferredContact`, …). Worth it for open-ended requests ("stop showing anything from that marketing company", "merge these contacts") — for a single denylist it's overkill vs. a direct affordance (Q149).
|
|
274
|
-
- **Q149 — Visible prefer/ignore affordances.** (1) ✅ **Done v1.1.55** — compose autocomplete rows now have visible per-row ★ (prefer → `contacts.jsonc#preferred[]`) and ⊘ (ignore → `denylist[]`) buttons, shown on hover; plain click, no dependence on the flaky right-click. (2) ✅ **Done v1.2.
|
|
274
|
+
- **Q149 — Visible prefer/ignore affordances.** (1) ✅ **Done v1.1.55** — compose autocomplete rows now have visible per-row ★ (prefer → `contacts.jsonc#preferred[]`) and ⊘ (ignore → `denylist[]`) buttons, shown on hover; plain click, no dependence on the flaky right-click. (2) ✅ **Done v1.2.190** (2026-07-30) — "⊘ Never use this address" added to the message-viewer From/To/Cc right-click menu (`message-viewer.ts`), routed through `addToDenylist`. (3) Related: route ★ to a Google-Contacts star instead of `preferred[]` once that's decided (see Q151 discussion).
|
|
275
275
|
- **Q148 — "Open in editor" + live file-watch for the JSONC config editor.** Bob 2026-05-16: the Edit-config-file modal's textarea is cramped; he wants a button revealing the source path so he can edit in a full editor (VS Code), with the modal watching the file and reflecting external changes. Local/cloud split: `config.jsonc` is a real local file (`~/.rmfmail/config.jsonc`) — trivial: a "Reveal source" button + `fs.watch` → re-read the textarea on change. The other files (`accounts.jsonc`, `allowlist.jsonc`, `clients.jsonc`, `contacts.jsonc`) live on Google Drive via the Drive API — **no local path exists**. To edit those externally, "check out" a local working copy (e.g. `~/.rmfmail/config-edit/<name>`), reveal that path, `fs.watch` it, and `cloudWrite` back on change. Modal gains: a "Reveal/Open source" button per file + a watcher that updates the textarea (and warns on a dirty-buffer conflict). ImapManager already runs `fs.watch` on the local config files (`watchConfigFiles()` → `configChanged`) — reuse that channel for the local case.
|
|
276
276
|
- **Q147 — Message list stalls mid-scroll during sync churn — incremental updates + virtualization.** Bob 2026-05-16: "scrolling the list of messages, it seems stuck and finally gets loose." Design defect, not a symptom to patch. Root cause: `renderMessages` rebuilds the whole list (`new MessageRow` per row, ~10 DOM nodes each) and there is **no virtualization** — infinite scroll appends pages so a large folder holds thousands of live nodes. Sync events (and the every-30s calendar/task refresh storm makes them constant) trigger synchronous list re-render and/or `scrollIntoView` re-anchoring on the **main thread the user is scrolling** → scroll input queues until the burst clears. Existing `{scroll:false}` guards (added 2026-05-14 for the same "won't let me scroll" report) patched the symptom; the defect remains. Fix, two parts: (1) **sync events update rows in place** — a changed flag/date/folder mutates that one `MessageRow`'s DOM; never rebuild the list and never touch scroll/focus on a background reconcile; (2) **virtualize** large folders so only viewport-adjacent rows are in the DOM. Continuation of the row-objects-own-behavior direction (related: S56). Touches `message-list.ts` render + the store-subscription/event handlers.
|
|
277
277
|
- **Q146 — All-day events are floating calendar dates, not instants — model fix.** Bob 2026-05-16. Interim fix shipped v1.1.50: `calendarEventToLocal` parses an all-day `start.date` as *local* midnight instead of UTC (`Date.parse` treats date-only as UTC → a June 14 birthday rendered June 13 in US Eastern). That makes it correct *on the current machine in the current timezone* but the underlying model is still wrong: an all-day event — a birthday especially — is a **floating date** ("June 14", true everywhere), not a timestamp. Storing it as `start_ms` epoch ties a date to a timezone; it'll drift again on travel / timezone change / cross-device view. Proper fix: distinguish **timed events** (an instant — `start_ms`, fine) from **date events** (store the `YYYY-MM-DD` string, no timezone). Day-grouping, the day header, and rendering key off the date directly for date-events; only timed events do epoch math. Reminders on all-day events (Google's "1 day before at 9am") still need a timezone anchor — resolve that against the *local* day. Touches `GCalEvent`/`calendarEventToLocal`, `calendar_events` schema (a date column or an `all_day` + date pair), `calendarRowToObject`, the sidebar `CalEvent` + `fetchUpcoming` + `renderEvents` grouping, and the alarm scheduler. Medium model change — the v1.1.50 parse is a stopgap until it lands.
|
|
@@ -281,7 +281,7 @@ Small, self-contained items. Pick them up between higher-priority blocks without
|
|
|
281
281
|
- **Birthdays**: shown automatically with 🎂 — no special checkbox (it's just one of the generated per-calendar checkboxes). Bob has already moved low-priority birthdays to a Contacts label so they don't flood; the two-tier "care about / FYI" split is his to manage upstream in Google for now. `eventType` (`default`/`birthday`/`fromGmail`/…) is NOT currently captured by `calendarEventToLocal` — add it.
|
|
282
282
|
- **Multi-source dedup**: the same event can arrive via two selected calendars. Show the row **once**, carry a *set* of source calendar IDs, render all their icons. Dedup key = `iCalUID` (stable across calendars), not `providerId`. Needs a source-list on the row replacing the single `calendarId`; the upsert passes in `refreshCalendarEvents` union into it instead of last-write-wins.
|
|
283
283
|
- Schema: `event_type` + source-list — no migration, calendar cache rebuilds from Google. Touches `GCalEvent` type, `calendarEventToLocal`, `refreshCalendarEvents`, a new service call exposing the enumerated calendar list (id, name, color, type) to the client, `db` calendar schema, `calendar-sidebar.ts` (dynamic checkbox list + icon rendering). Removes `HOLIDAY_SOURCES`, `showHolidays`, `showJewishHolidays`.
|
|
284
|
-
- **Q144 — Overdue tasks pinned to the top of the calendar list.** ✅ **Implemented v1.1.47** (pending runtime verify) — overdue block prepended in `renderEvents`, done-checkbox marks the task completed in Google (non-destructive). Bob 2026-05-16, design settled. NOT full Google-Calendar parity (which interleaves every due-dated task onto its due date). Instead: **only overdue tasks** — past due date AND `status: needsAction` — pin to the **top of the calendar event list** as a persistent "behind on these" block. Each row carries a done control (checkbox / ✓) which **marks the task completed in Google** (`updateTask` with `completedMs` → `status: "completed"`) — NOT a delete. Completing it drops it off the calendar top (no longer overdue) while the task survives in Google's Completed section. Tasks that are undated or due in the future stay only in the existing Task section — they don't appear on the calendar. Implementation: `renderEvents` in `calendar-sidebar.ts` prepends an overdue-task block built from `getTasks()` filtered to `dueMs < startOfToday && !completedMs`; reuse the existing `updateTask(uuid, {completedMs})` path the task-list checkbox already uses. Depends on the Q143 sidebar rework. Note: ~~the existing task-list **× button is a hard `tasks.delete`** (destructive, no confirm)~~ — **fixed v1.2.
|
|
284
|
+
- **Q144 — Overdue tasks pinned to the top of the calendar list.** ✅ **Implemented v1.1.47** (pending runtime verify) — overdue block prepended in `renderEvents`, done-checkbox marks the task completed in Google (non-destructive). Bob 2026-05-16, design settled. NOT full Google-Calendar parity (which interleaves every due-dated task onto its due date). Instead: **only overdue tasks** — past due date AND `status: needsAction` — pin to the **top of the calendar event list** as a persistent "behind on these" block. Each row carries a done control (checkbox / ✓) which **marks the task completed in Google** (`updateTask` with `completedMs` → `status: "completed"`) — NOT a delete. Completing it drops it off the calendar top (no longer overdue) while the task survives in Google's Completed section. Tasks that are undated or due in the future stay only in the existing Task section — they don't appear on the calendar. Implementation: `renderEvents` in `calendar-sidebar.ts` prepends an overdue-task block built from `getTasks()` filtered to `dueMs < startOfToday && !completedMs`; reuse the existing `updateTask(uuid, {completedMs})` path the task-list checkbox already uses. Depends on the Q143 sidebar rework. Note: ~~the existing task-list **× button is a hard `tasks.delete`** (destructive, no confirm)~~ — **fixed v1.2.190** (2026-07-30): all three sidebar delete paths (per-row ×, batch Delete-key, context menu) now confirm, with wording that points at the checkbox for mark-done. The full tasks pane already confirmed.
|
|
285
285
|
- **Q142 — Editor help should be HTML compiled into the app, not `editor.md`.** 2026-05-16: search help was converted from a deployed `docs/search.md` (shown raw, then rendered) to HTML compiled into the client (`client/help/search-help.ts`, dynamically imported, rendered as styled HTML in the panel). Bob's principle: feature help is application content and must display as HTML — the `docs/*.md` files are only a stand-in for proper settings UI and should hold *config-file* help only. `editor.md` is the remaining feature-help `.md` still in the deploy whitelist (`mailx-settings/index.ts` `USER_DOC_WHITELIST`). Convert it the same way: author `client/help/editor-help.ts` (or wherever the compose window's help is surfaced), point the editor's help affordance at it, drop `editor.md` from the whitelist, remove the file. Also: `docs/search.md` is now orphaned (no longer whitelisted, no longer read) — delete it; the `readConfigHelp` "search" mapping in `mailx-service` can go too.
|
|
286
286
|
- **Q141 — Shavuot shows in calendar sidebar with the ✡ Jewish-holidays checkbox OFF — DIAGNOSED, not a bug; design decision needed.** Bob 2026-05-16 (screenshot `screenshots/Screenshot 2026-05-16 105854.png`). Root cause confirmed from `rmfmail-2026-05-16.log`: `[calendar] enumerated 4 calendars, 3 selected` → `[calendar] pulled 10 events from "Jewish Holidays"`. There are **two independent Jewish-holiday paths** and only one is the checkbox: (1) mailx's ✡ checkbox → `showJewishHolidays` → pulls the `en.judaism#holiday` holiday-source feed; the log shows it is OFF (no "pulled N Jewish holiday events" line — only "5 US-official holiday events"). (2) `refreshCalendarEvents` enumerates *every* calendar the user marked `selected` in Google Calendar itself (`listCalendars().filter(c => c.selected)`) and pulls each as regular events. The user has Google's **"Jewish Holidays"** calendar subscribed + selected in their own Google account, so mailx faithfully pulls its 10 events — Shavuot among them — as ordinary calendar rows. The ✡ checkbox has no bearing on path (2). So mailx is behaving correctly: it shows a calendar the user selected in Google. Bob's own read ("artifact of Google Calendar") was right. **Resolutions** (pick one — needs Bob): (a) zero-code — deselect "Jewish Holidays" in Google Calendar's web UI and it stops appearing in mailx; (b) feature — add per-Google-calendar visibility toggles in the sidebar so a `selected` Google calendar can be hidden in mailx without changing Google (Q140-adjacent — the sidebar would list each enumerated calendar with a checkbox; gate the `calendarsToFetch` loop on it); (c) leave as-is. No code change made — this is a design call, not a defect.
|
|
287
287
|
- **Q139 — General app-message channel from daemon to msger popups.** Bob 2026-05-11: "when an exit handler kills the popups by sending them a message — and can there be a general app message?" Today reminder popups are one-shot `showMessageBoxEx` (we have the handle for cross-platform reap). For graceful close-via-message AND other patterns (theme changes, in-flight content updates), spawn popups in `service: true` mode and use the bidirectional pipe — daemon calls `handle.send({type: "close"})` and the popup's JS listens for `_msgapiServiceEvent` events and reacts (`window.close()`, refresh content, etc.). Reuses the same wire mailx's main WebView already uses. Adapter shape: `showServicePopup(opts)` in `mailx-host` returns a `ServiceHandle` + a `result: Promise`; popup HTML's `<script>` registers `window._msgapiServiceEvent = (msg) => { if (msg.type === "close") window.close(); /* future: themeChanged, eventUpdated, … */ }`. Then `gracefulShutdown` in `bin/mailx.ts` iterates open popup handles and sends `{type:"close"}` instead of relying on the OS reaping. Pre-req: confirm msger's service mode supports the `rawHtml` content shape we use for reminders (may need a small msger PR).
|
|
@@ -386,7 +386,7 @@ Design-time terms used throughout this doc. Kept here (rather than README) becau
|
|
|
386
386
|
Dropped for now. The Ctrl+Z undo window for delete and the Save/Discard/Cancel prompt for compose close cover the majority of "wait I didn't mean that" recovery cases. Revisit if a specific loss pattern surfaces. Historical description preserved in earlier versions of this file.
|
|
387
387
|
|
|
388
388
|
<a id="ext10"></a>
|
|
389
|
-
- [x] ~~**Paste-link-with-text double-inserts**~~ — **FIXED v1.2.
|
|
389
|
+
- [x] ~~**Paste-link-with-text double-inserts**~~ — **FIXED v1.2.190** (2026-07-30). The capture-phase paste handler in `client/compose/editor.ts` (which stops Quill's parallel listener via `stopImmediatePropagation`) already covered browser copies, but only detected a *top-level lone* `<a>` — Word/Docs wrap the anchor in `<p>`/`<span>` layers, so those clipboards fell through to Quill's own matcher. Detection generalized: one `a[href]` carrying all the meaningful text at ANY wrapper depth is treated as a single-anchor clipboard and consumed.
|
|
390
390
|
|
|
391
391
|
<a id="ext7"></a>
|
|
392
392
|
|
|
@@ -10168,6 +10168,10 @@ function vlog(msg) {
|
|
|
10168
10168
|
} catch {
|
|
10169
10169
|
}
|
|
10170
10170
|
}
|
|
10171
|
+
function narrate(msg) {
|
|
10172
|
+
console.log(`[android] ${msg}`);
|
|
10173
|
+
emitEvent({ type: "bootstrapStatus", message: msg });
|
|
10174
|
+
}
|
|
10171
10175
|
function withTimeout(label, ms, fn) {
|
|
10172
10176
|
return new Promise((resolve, reject) => {
|
|
10173
10177
|
let done = false;
|
|
@@ -11028,6 +11032,7 @@ async function fetchTokenForEmail(email) {
|
|
|
11028
11032
|
login_hint: email
|
|
11029
11033
|
}).toString();
|
|
11030
11034
|
console.log(`[oauth] Starting browser consent for ${email}`);
|
|
11035
|
+
narrate(`Signing into ${email}\u2026`);
|
|
11031
11036
|
const code = await bridge2.app.startOAuth(authUrl);
|
|
11032
11037
|
const tokens = await exchangeCodeForTokens(code);
|
|
11033
11038
|
const token = {
|
|
@@ -11302,7 +11307,7 @@ async function initAndroidOnce() {
|
|
|
11302
11307
|
reconcileTry++;
|
|
11303
11308
|
setGDriveTokenProvider(tp);
|
|
11304
11309
|
try {
|
|
11305
|
-
|
|
11310
|
+
narrate(`Fetching settings from Google Drive\u2026 (try ${reconcileTry})`);
|
|
11306
11311
|
vlog(`gdrive-reconcile try ${reconcileTry}: token fetch`);
|
|
11307
11312
|
await Promise.race([
|
|
11308
11313
|
tp(),
|
|
@@ -11336,7 +11341,7 @@ async function initAndroidOnce() {
|
|
|
11336
11341
|
} catch (e) {
|
|
11337
11342
|
console.warn(`[android] List debug: ${e.message}`);
|
|
11338
11343
|
}
|
|
11339
|
-
|
|
11344
|
+
narrate("Loading accounts from Google Drive\u2026");
|
|
11340
11345
|
const gdriveAccounts = await loadAccountsFromCloud();
|
|
11341
11346
|
console.log(`[android] GDrive returned ${gdriveAccounts.length} accounts: ${gdriveAccounts.map((a) => a.id).join(",")}`);
|
|
11342
11347
|
if (gdriveAccounts.length > 0) {
|
|
@@ -11354,6 +11359,8 @@ async function initAndroidOnce() {
|
|
|
11354
11359
|
await syncManager.addAccount(account);
|
|
11355
11360
|
}
|
|
11356
11361
|
console.log(`[android] Loaded ${accounts.length} accounts from GDrive`);
|
|
11362
|
+
narrate(`Syncing ${accounts.length} account(s)\u2026`);
|
|
11363
|
+
syncManager.syncAll().catch((e) => console.error(`[android] post-reconcile sync: ${e.message}`));
|
|
11357
11364
|
}
|
|
11358
11365
|
await registerDeviceInGDrive(tp, folderId, accounts.map((a) => a.id));
|
|
11359
11366
|
}
|
|
@@ -11632,6 +11639,7 @@ function installBridge() {
|
|
|
11632
11639
|
await syncManager.addAccount(account);
|
|
11633
11640
|
db.upsertAccount(account.id, account.name, account.email, JSON.stringify(account));
|
|
11634
11641
|
console.log(`[android] Account added: ${email}`);
|
|
11642
|
+
syncManager.syncAll().catch((e) => console.error(`[android] post-setup sync: ${e.message}`));
|
|
11635
11643
|
return { ok: true, message: `Added ${email}. Syncing...` };
|
|
11636
11644
|
} catch (e) {
|
|
11637
11645
|
return { ok: false, error: e.message };
|