Khazna (خزنة, "vault") is a private, offline pocket vault for links, notes and checklists — one input that works out which of the three you pasted, a capture flow measured in about a second, app lock behind your fingerprint, and no server, no account and no sync behind any of it. Built with Expo SDK 57, React Native 0.86 and TypeScript, Arabic-first with a full right-to-left layout.
Khazna (خزنة) is Arabic for vault — the small kind, the one in the room rather than the bank. Somewhere to put the link you found at midnight and the four things to pack, that is quick to reach and yours alone.
Download
Coming soon to Google Play. Android-first, Arabic and English, light and dark, free, no ads.
The signed bundle builds locally through pnpm release:aab, which bumps the version code, prebuilds clean so the native project matches app.json, forces all four ABIs, and then verifies the signer and the merged manifest's permissions before it says where the file is.
Screens
Fourteen device captures — every surface the app has, in dark, in light, and in Arabic.
| Capture | The vault |
|---|---|
![]() |
![]() |
| Search | A link |
![]() |
![]() |
| A checklist | Reminders |
![]() |
![]() |
| Tags | Multi-select |
![]() |
![]() |
| Your own order | Settings |
![]() |
![]() |
| Light | A link, light |
![]() |
![]() |
| Arabic | Arabic settings |
![]() |
![]() |
Why Khazna
The product claim is one number: opening the app and getting a paste into the vault stays near a second. Every feature is measured against it, and most of the ones that would have added a step were cut for exactly that reason.
Two decisions buy the number:
- Capture is a sheet, not a screen. The floating button, a share from the browser, the launcher long-press and the empty state all open the same sheet over whatever you were looking at. An earlier full-screen composer asked what kind of thing you were saving before it let you type — that question, and the screen transition in front of it, were the whole reason capture felt slow.
- One input takes anything. Paste a URL, a paragraph or a list of lines, and the app works out which it is: an explicit checkbox beats everything, a leading URL takes the rest of the line as its annotation, and anything else is a note. The guess is a pill you can tap to change, and converting an item folds its content across kinds without losing any of it.
Storage is one persisted store over MMKV, read synchronously at boot, so the feed is on the first frame rather than after a spinner.
Features
Capture
- One sheet, opened from the FAB, a share from another app, a launcher long-press, or the empty state
- Detection into Link / Note / List as you type, always overridable, and never destructive to convert
- A paste banner that offers the clipboard rather than reading it behind your back
- Android share doors — a share from any app, a Direct Share shortcut, and
PROCESS_TEXTon selected text - A shared link that is nothing but a URL saves with no sheet at all, with an undo
- Duplicate detection: it says when you are about to save something already in the vault
- Tags at capture time, plus a domain table that tags common links by itself
Links
- Page titles fetched on demand, cached, cancellable, and only ever for a link you saved
- Tracking parameters (
utm_*,fbclid,gclidand the rest) stripped as the link is normalised - Every deliberate departure goes through one seam, so opening a saved link does not come back to a lock prompt
Notes and checklists
- Plain text, committed as you type — no save button, no formatting menus
- Paste a list and every line becomes an entry; tick, drag by the grip, clear the finished ones
- A progress ring that fills as a list closes out
Finding things again
- Search as you type across titles, bodies, URLs and checklist entries
- Filter by kind and by tag, pin to the top, archive, read / unread
- Sort by saved date, title or kind — or pick "My order" and drag the vault into the order you want
- Swipe a row to pin or delete, with undo; multi-select to act on many at once
- Reminders scheduled locally, with a launch-time reconcile so nothing reads the clock during render
Privacy
- App lock behind fingerprint, face or device PIN, with a grace window and an escape hatch when biometrics become unavailable
- Screen privacy (
FLAG_SECURE) — out of screenshots and out of the app switcher, only when you turn it on - Backup to a file you pick, restore on a new device, erase everything from one screen
- Anonymous usage analytics, opt-out, that the type system will not let carry anything you typed
Craft
- Arabic-first with native RTL — the tree comes down and reloads around the direction flip, because a mounted navigator cannot absorb one
- Dark and light drawn separately, from one palette held in two syntaxes, with a test that fails the build when they drift
- Tajawal throughout, and no named line-heights anywhere: Android trades the font's own padding band for one and clips Arabic marks
- Western digits in both languages, except inside Arabic running text, where two named helpers opt into Arabic-Indic
- A deep-import icon barrel, enforced by a test, because the lucide index evaluates ~1,600 icons otherwise
- 15 test suites, 113 tests, and a route-screenshot harness that deep-links every screen so a layout defect is found rather than reported
Tech stack
| Layer | Choice |
|---|---|
| Framework | Expo SDK 57 (managed) + Expo Router (file-based routing) |
| Runtime | React Native 0.86 / React 19.2, React Compiler enabled |
| Language | TypeScript, strict mode |
| UI | NativeWind 4 (Tailwind class names on native) |
| Animation | react-native-reanimated 4 + react-native-gesture-handler |
| Vector | react-native-svg (the mark, the progress rings) |
| State | Zustand, persisted to react-native-mmkv v4 (Nitro module) |
| Notifications | expo-notifications — local reminders only, no push server, no token |
| Security | expo-local-authentication (app lock) · expo-screen-capture (FLAG_SECURE) |
| Sharing | expo-share-intent · expo-quick-actions · a local Expo module for the Direct Share target |
| Files | expo-document-picker + expo-file-system (backup and restore) |
| Updates | expo-updates (OTA) |
| Analytics | PostHog, opt-out, restricted by an allowlist the tests enforce |
| Fonts | Tajawal — one family for Arabic and Latin |
| Icons | lucide-react-native, deep-imported through a generated barrel |
Network: two calls, both from the device straight to a third party, both switchable off — a GET for a saved link's <title>, and anonymous analytics. The Expo update check is a third connection that no feature initiates, disclosed rather than hidden behind the number two. There is no backend: no server, no account, no sync, no API key.
Non-negotiables
These are load-bearing — every change must respect them:
- Vault content never leaves the device. Not titles, bodies, URLs, checklist labels, tag names or search queries. Not truncated, not hashed, not "just the domain". The analytics payload types can only express counts, enums and booleans, and a test fails the build if a free string ever appears in one.
- No screen may claim the app tracks nothing. Analytics ship, so the README, the onboarding cards and the settings copy all say the true thing.
- Capture stays near a second. A feature that adds a step to that path has to justify itself against the number.
- Every screen scrolls. Content that fits still scrolls when the font scale is large or the keyboard is up.
- Reuse is not negotiable. A redesign changes how a shared primitive looks; it does not license a second hand-rolled copy of it.
- Kind colour is semantic. Slate blue is a link, sand is a note, moss is a list, and none of the three appears on anything that is not that kind.















