[feature] android: in-app account deletion, and a web deletion route for both platforms - #235
Merged
Merged
Conversation
…for both platforms Google Play requires any app offering account creation to provide both an in-app way to delete the account and a publicly reachable web URL where deletion can be requested without installing the app. Parley Android had neither: `DELETE /me` has existed on the backend and shipped on iOS since launch, but the Android CloudClient never implemented it, and the privacy page told everyone to delete "from Settings in the iOS app" — a dead end for an Android-only user. This is a listing blocker. - CloudClient gains `deleteAccount()`. The backend's 409 refusal for an account that still owns a shared organization gets its own typed flag, `CloudException.ownsOrganizations`, rather than collapsing into a generic failure: it is the one deletion error the user can act on, and telling them to "try again" would be a lie. - The account sheet gets a destructive "Delete account" action, mirroring iOS Settings → Account: the button only arms a confirmation dialog that spells out what is destroyed (account, every synced recording, its audio, permanently), and a second separately-labelled destructive tap commits. On success the pending-upload queue is wiped and the token cleared — the server already destroyed the session, so there is nothing to revoke — and ParleyRoot returns the app to the sign-in wall on its own. - New strings in both values/ and values-zh-rTW/. - New page at /account-deletion/, zh-Hant with an English section like the rest of the site: what deletion removes, the in-app route for iOS and Android, the shared-organization exception, and the contact route for someone who cannot open the app. Linked from the landing footer, privacy and support; privacy's iOS-only deletion claim is corrected.
|
This was referenced Aug 16, 2026
YJack0000
added a commit
that referenced
this pull request
Aug 18, 2026
…eature claims (#256) Five of the six desktop screenshots predated the v0.22.14–v0.22.16 removals. The worst was the hero, which is both the largest image on parley.tw and the first image in the README: its right-hand column was the intel board, removed in #247. The page around it had already been rewritten in #252 to stop promising that feature; the picture kept promising it anyway. Reshot against current main, seeded through the store with a fictional renewal negotiation (Northwind Logistics / Dana Whitfield — no real customer data): - showcase-hero — the live cockpit as it actually is now: transcript, coach feed, agenda checklist. No intel board, because there is no intel board. - showcase-transcript — the transcript posture with the findings column. - showcase-study — the report: brief with clickable timestamp pills, action items with severity, filed into a customer folder. - showcase-qa — Ask, in the drawer it actually lives in now. The old shot still had the v0.11 "Ask / TODO" tab pair and the old delivery panel. - showcase-eval — Settings → Evaluations, showing the built-in playbooks and an editable prompt, which is what "your checks" means concretely. showcase-voicetyping is kept: the overlay has only had behaviour fixes since it was shot (screen placement, tap-to-toggle, preview cap), no visual redesign. The two iPhone shots are also kept — they predate the #248 restyle, so they are next in line, but they show nothing that no longer exists. The capture shims (a store handle in main.tsx, a thread seeder in AskPanel) are reverted, not committed, per the standing recipe. Alongside that, the store listings still sold "deal intelligence" / 成交情報 — the mini-CRM removed in #245 — in all four locale files, in the paragraph that tells a phone user what they get on the desktop. Replaced with cross-meeting search, which is what actually replaced it. Also settled two stale open items that #235 had already resolved without the docs catching up: Android does ship in-app account deletion, and parley.tw/account-deletion/ is the Play-required web route, so `android/AppStore/data-safety.md` no longer opens with a TODO gate and the listings point at the real URL rather than the privacy policy. The remaining `[TODO: confirm with Jack]` markers there are genuine questions for a human (whether to over-declare usage data, and whether api.parley.tw retains account-linked request logs) and are left alone. Smaller corrections: iOS is 1.2 on the App Store, not 1.1; the iOS listing's rationale notes described a name and a subtitle it does not ship; CLAUDE.md listed "interpreter" among the secondary windows, which went with #242; and docs/design/mini-crm.md now says up front that it documents a subsystem that was built and then removed, rather than reading as current design. Verified: bunx tsc --noEmit and bunx vitest run (221 tests) pass, and all eight media slots load at their intended dimensions on a local serve of website/.
Lanznx
added a commit
that referenced
this pull request
Sep 16, 2026
… comes from the right source (#386) * [docs] Android: the store packet stops contradicting itself Three claims outlived the thing they described. `data-safety.md` still answered Play's deletion question with "the honest answer today is request by other means", and its cross-check called deletion "the one real contradiction between the two stores". In-app account deletion has been there since #235, and per-recording deletion landed with 1.13 — which also clears what the phone kept locally: the audio saved for playback, a queued re-transcription, and the retry ledger entry. Android now goes further than iOS here, which is the opposite of what the file said. `review-notes.md` said the foreground-service demo video "still has to be hosted". `fgs-declaration.md` has recorded it as uploaded since 2026-08-19, with the URL. Two files in the same directory disagreeing about whether a submission blocker exists is worse than either answer. The warning that replaces it is the one that will actually be needed: that video shows meeting recording only, and Play reviews the declaration against the permission, so the release carrying voice typing needs a re-shoot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * [fix] the Play badge comes from the source Google's guidelines name The badge shipped as a PNG from `play.google.com/intl/en_us/badges/static/...`. That URL still serves, but the documented badges page now redirects to the Partner Marketing Hub, and the guidelines say to use the current assets from there. The lockup is visually identical, so nothing looked wrong — but the provenance was a stale copy rather than the source the rules point at, and a small multi-colour mark on a dark background at HiDPI is exactly where a downscaled bitmap shows its seams. The swap is not just a file change, and this is the part worth reading: the official SVG is edge-to-edge, while the legacy PNG carried 41px of transparent padding that was silently supplying 11.8px of the 12.1px of required clear space. Dropping the new file in without re-deriving the sizing would have shipped a badge with essentially no clear space — out of compliance, on a public page, looking fine. Height is now 48px with a 12px margin carrying the full quarter-height itself. Rendered size is unchanged at 162×48. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: YJack0000 <jack@pathors.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Why
This unblocks Google Play submission. Play requires that any app offering account creation provide both:
Parley Android had neither.
DELETE /mehas existed on the backend and shipped on iOS since launch, but the AndroidCloudClientnever implemented it (android/docs/api-cloud.mdlisted it under "not implemented"), andwebsite/privacy/index.htmltold users to delete "from Settings in the iOS app" — a dead end for an Android-only user. Either gap alone gets the listing rejected.What changed
CloudClient.deleteAccount()DELETE /mewith the bearer token, same contract as iOSParleyKit/CloudClient.swift.The backend refuses (409
owned_organizations) while the account still owns a shared workspace — deleting it silently would take its other members' recordings with it. That refusal gets its own typed flag,CloudException.ownsOrganizations, alongside the file's existingisAuthExpired/isForbidden/isQuotaExhaustedconventions, rather than collapsing into a generic failure. It is the one deletion error the user can actually act on, and it is deliberately notisRetryable— telling them to "try again" would be a lie.In-app deletion UX
Mirrors iOS Settings → Account exactly, in Material3:
TextButtonin the account sheet, shown onceme()has confirmed who is signed in (same condition as iOS — no point offering a call that cannot reach the server either).AlertDialog. The dialog body names what is destroyed — the account, every synced recording and its audio, transcripts, summaries, folders, usage history, permanently and irreversibly, plus the not-yet-uploaded queue on the device — instead of asking "are you sure?".confirmationDialog, not a typed confirmation), so this matches rather than inventing a stricter gesture.ParleyRootalready watchesisSignedIn.Queue-then-token ordering is deliberate: the sign-in screen must never appear over a still-populated queue that a re-sign-in would upload into a brand-new account.
Strings
All eight new strings in both
values/strings.xmlandvalues-zh-rTW/strings.xml. Verified programmatically: 96 names, zero asymmetry.Website
New page at
/account-deletion/(website/account-deletion/index.html) — zero-build static like the rest of the site,legal.css, zh-Hant with an English section, same structure asprivacy/andsupport/. It states what deletion removes, the in-app route for each platform (iOS Settings → Account; Android account sheet), the shared-organization exception, and the contact route at contact@pathors.com for someone who cannot open the app. Added tositemap.xmland linked from the landing footer, privacy and support.website/privacy/index.htmlcorrected: the deletion paragraph, the summary callout and the English summary all claimed iOS-only deletion.website/support/index.htmlhad the same claim in its contact list.No invented policy. No retention window, no response-time SLA, no "within N days" — nothing that is not already stated somewhere in the repo. The identity check for emailed requests ("email from the address the account is registered to") is procedure, not a commitment.
Docs
android/docs/api-cloud.md:DELETE /memoved out of "not implemented" into the method table, with a section covering the clear-don't-sign-out ordering and theownsOrganizationsrefusal, plus a row in the error table.Verification
JAVA_HOME=$(/usr/libexec/java_home -v 24) ./gradlew assembleDebug :parleykit:test :app:testDebugUnitTest→ BUILD SUCCESSFUL, 45 tasks.New
CloudClientDeleteAccountTest(5 tests, MockWebServer — addedtestImplementation(libs.okhttp.mockwebserver)to:app, which:parleykitalready used) covers the error mapping: the authenticatedDELETE /meon the wire, 409owned_organizations→ownsOrganizations+ not retryable, an unrelated 409 → not the org refusal, 401 →isAuthExpired+onUnauthorizedfired once, 503 → still retryable.The website is zero-build; HTML well-formedness and every relative link were checked instead.
Not in this PR
android/AppStore/is untouched — #232 (docs/android-store-listing) owns that, and documents this same gap indata-safety.md. That PR's data-safety declaration and this PR's implementation should land together for the submission.