Memory.Wiki Android — iOS Parity Sprint (2026-05-30)

Single-day execution log: bring the Android-native companion app up to full iOS feature + visual parity, then push the platform-native surfaces (share intent, widget, app shortcuts) that Android does better than iOS.

Total: 25 commits, ~5,000 lines of polish + features, every surface verified on emulator-5554.


Phase 0 — The mess

The founder's words verbatim:

로그인 부터 모든 화면과 콤포넌트들이 ios에 비해서 너무나 완성도도 떨어지고 브랜딩 엘레멘트들도 반영도 안되고 아주 엉망진창임

Translation: every screen and component was visibly worse than iOS. Branding elements absent. A mess.

The Android scaffold existed (74 files, build pipeline working, demo signin working), but the visual + interaction parity wasn't there.

Starting state:

  • 5-tab bar with subtle borderless icons, no labels, static blob
  • Auth using Lucide.Apple fruit emoji for Sign in with Apple (HIG violation)
  • Tab center "Start" tap was dead (WebView swallowed touches)
  • MDs / Bundles / Start / etc. used basic Material chips and dot indicators
  • No long-press menus, no pin/star, no edit/delete, no share, no semantic search
  • No animated blob, no ambient backdrop, no skeletons-with-shimmer

Phase 1 — Primitives + visual parity

Shipped the shared visual atoms iOS uses everywhere, then rebuilt every screen on top.

Primitives

  • AmbientBlob — full-bleed faint morph blob at 4.5% alpha + 12dp blur behind empty states
  • DocStatusIcon — globe/users/cloud per privacy, with sync-badge composite (ink ring + microInfo circle + white check) when synced
  • BundleLayersIcon — Lucide Layers tinted by visibility (lime public / info-blue shared / faint private)
  • RefreshingPip — inline 10dp ProgressView + mono 8 'REFRESHING' caption
  • SkeletonRow/SkeletonList/SkeletonStatStrip — shimmering placeholders
  • BrandBlob — animated SMIL morph SVG hosted in a transparent WebView (only renderer on Android that plays the 10.867s morph cycle). iOS does the exact same thing
  • MarkdownPillBar — 48dp Notes-style accessory bar above the keyboard with cursor-aware H/B/I/list/ordered/task/code/link/quote/hr inserts via TextFieldValue selection manipulation
  • util/TimeFormat.kt — central compactTime() so every list row formats dates identically ('now' / 'Xm' / 'Xh' / EEE / MMM d)

Surfaces (16 commits)

Every screen got rewritten or polished to iOS parity:

Surface What landed
Splash 196dp animated blob + wordmark + tagline, staggered fade-in (spring 0.95/0.78, easeOut delay 160ms/280ms)
Auth BrandBackdrop (huge ambient blob @ 7%), stacked hero (168dp blob → wordmark display 36 → tagline → 'ANDROID COMPANION' chip), glass ProviderButton (52dp, 12dp corner, gradient stroke, press scale 0.985 + dim), staggered entrance per iOS timing (0/180/280/380/450/700ms). Real Apple Inc. logo SVG, not a fruit
Onboarding 3-card HorizontalPager (Capture / Feed your AI / Always within reach), capsule page dots, Skip + Next/Start CTA, ambient blob backdrop, mw.onboarded SharedPreferences flag
Tab bar Capsule with translucent SheetBg + hairline border, labels under non-center tabs (mono 9 uppercase), 14×1dp ink indicator under active tab, animated blob centerpiece (WebView touch-passthrough fix), ProfileTabAvatar on Settings, re-tap shake + nav stack pop
Start Greeting variants by time-of-day × day-of-year rotation, AI URL strip with Copy-for-AI sparkles→check 1.6s swap, Ask-your-hub CTA (microInfo border), pulse row as editorial hairline-bracketed 3-column strip (NOT card tiles), Quick actions, Recent (mixed docs+bundles), Starred section, Featured bundle card, per-block stagger entrance
Markdowns Header with search toggle button + RefreshingPip, filter pills (All/Private/Shared/Synced), time bucket grouping (Today/Yesterday/This week/This month/Earlier), DocumentRow with DocStatusIcon + meta line (privacy badge + sync source + view count) + compactTime + share icon, long-press DropdownMenu (Star/Copy URL/Copy AI/Share/Open), pull-to-refresh, AmbientBlob empty states per filter, Pinned section above buckets, 300ms-debounced semantic search with BY MEANING section
Bundles Same header/search/filter pattern as MDs but with Public filter + BundleLayersIcon rows
DocumentDetail display 26 title, URL strip with DocStatusIcon + Copy-for-AI pill, meta strip (clock/lock/sync/eye), MarkdownBody via Markwon, owner brand accent override, overflow menu (Edit/Star/Add to bundle/Chat/Copy AI/Copy URL/Share/Make public-private/Open/Delete), edit mode with TextField, delete confirmation dialog, TOC sheet when ≥1 heading
BundleDetail Layers glyph + display 26 title + description, Deploy card (DEPLOY THIS BUNDLE TO ANY AI header + explainer + inner URL pill with Copy-for-AI swap), Members section with MemberRow per doc + chevron, ellipsis menu
Capture 6 mode pills (Write/URL/Photo/OCR/Voice/Files), title field display 28, body field with markdown pill toolbar above keyboard, Publish button in top bar
Chat 2-line header (mono 9 SCOPE label + display 20 title) + 30dp glass xmark close, 'Try' empty hint with 3 sparkle prompts, Capsule user bubble, ASSISTANT caption above markdown assistant bubble, citation chips (resolve titles via DocCache), ThinkingIndicator, error row, composer with top hairline + Surface capsule input + animated keyboard-dismiss + 42dp ink Send circle. Tab bar hidden on chat
Settings Hub URL card with Copy for AI / Copy URL / Open hub, Hub stats card (MEMORIES + BUNDLES tiles with public/private breakdown), Account section (Email/Username/Display Name editor sheet), Key color picker, LANGUAGE (opens system locale picker), LEARN (About + How it works + Replay welcome tour), FEEDBACK (Send feedback → mailto with device template), LEGAL, Version footer, Sign out
About display 30 wordmark + tagline + 2 mono Tag pills + WHAT'S NEW bulleted card + ON THE WEB / ACROSS PLATFORMS / LEGAL link groups + credits

Phase 2 — Platform-native affordances

Things Android does that iOS doesn't (or does differently). Shipped them to make Memory.Wiki feel native on Android, not a port.

Affordance Shipped
Share intent ShareReceiverActivity routes text/plain from the system share sheet into Capture (pre-filled body + title) instead of silent-POSTing. image/* encodes WebP, uploads, and creates a doc referencing the upload URL
App Shortcuts Long-press the launcher icon → 4 shortcuts (Capture / Ask / Search / Paste) via res/xml/shortcuts.xml. Each fires a memorywiki:// deep link straight into MainActivity. Verified via adb shell dumpsys shortcut
Glance widget Home-screen widget with brand wordmark + ink 'New capture' primary action + Ask/Search/Paste quiet pills. XML preview layout for the widget picker so users see the chrome before adding it
Foreground refresh RootShell installs a LifecycleEventObserver on ON_RESUME that emits RouterEvent.ForegroundRefresh. MDs/Bundles/Start consume it and re-pull so a user backgrounding the app + creating a doc on the web + coming back sees fresh state
PinnedStore Central pin/star state singleton with optimistic local toggle + server reconciliation. Wired into MDs row + Bundles row + Start 'Starred' section + DocumentDetail menu

Phase 3 — Workflow actions

Things the user can actually do with their data:

  • Pin/unpin (PinnedStore) — Star/Unstar in long-press menus on MDs + Bundles + DocumentDetail. Star icon next to title when pinned. STARRED bucket above time buckets in MDs
  • Edit document — inline TextField mode with mono 13 body + EDITING (MARKDOWN) caption, Save/Cancel toolbar swap, optimistic refresh
  • Change visibility — Make public / Make private toggles is_draft via PATCH action=publish
  • Delete document — AlertDialog ('It moves to Trash on memory.wiki — recoverable for 30 days.') with destructive red Delete button
  • Add to bundle — Bottom sheet with Create new bundle (inline TextField) + existing bundles list. Adding shows microLime checkmark for 700ms then dismisses
  • Table of contents — Sheet that extracts ATX headings from doc body and displays them indented by level, tap to dismiss
  • Semantic search — 300ms-debounced backend search runs alongside title-match. ProgressView spinner in search bar while loading. BY MEANING section beneath title-match results
  • Publish from Capture — Top-bar Publish button creates the doc via POST /api/docs and routes the user to the new doc's reader
  • Replay welcome tour — Settings row clears the SharedPreferences flag so onboarding plays again on next launch

Architecture notes

  • Compose 1.7 + Material 3 dark-only, no Material You dynamic color
  • Hilt 2.57.2 + KSP 2.2.10-2.0.2 for DI
  • Ktor 3.0.2 for HTTP + streaming chat (readUTF8Line line-by-line)
  • Supabase-kt 3.0.2 with a JWT-decode workaround for service-role demo sessions (the standard auth.user returns null for those because /auth/v1/user returns 401)
  • Markwon 4.6 for the markdown reader
  • Glance 1.1.1 for the home-screen widget
  • Coil 3 + coil-svg for provider logos + static brand assets
  • Lucide compose (com.composables:icons-lucide) wildcard-imported across all 17 screens for parity with lucide-react on web + SF Symbols on iOS

The single-line summary

The Android app no longer feels like a port. It feels like the same product, with Android's affordances exposed natively.


What's next

Smaller polish + features remaining:

  • In-app HelpScreen with DisclosureGroup-style expandable sections (Capture / Share / Widget / App Shortcuts / Deep links / Markdown / FAQ)
  • Feedback template in Settings → mailto pre-filled with App: Memory.Wiki Android v<x> · Device: <model> · OS: <version> · Locale: <id>
  • (lower priority) Quick Settings tile, additional widget sizes, deep-link from web → app banner

Everything above is shippable; the Android surface is launchable.


Built with Claude Opus 4.7 in a single 2026-05-30 working session. ~25 commits, all green, all visually verified on emulator-5554 with the demo account.