Beautiful, private note-taking โ on the web and as an Android app
Write beautifully, anywhere. Notes live locally on your device and sync privately to your own Google Drive.
Install from GitHub Releases. The app also includes an in-app updater from Settings โ Check for updates.
Inkwell uses the v27 dark/orange icon style to match the app UI.
| Asset | Purpose |
|---|---|
public/icon.svg |
Scalable icon for browser, README, and app branding |
public/logo.svg |
Wide README/header logo |
public/logo.png |
PNG logo fallback |
public/icon-512.png |
Master PNG used for Android/PWA icon generation |
public/icon-*.png |
PWA and launcher sizes |
public/favicon-* |
Browser tab icons |
public/apple-touch-icon.png |
iOS home-screen icon |
docs/app-icon-preview.png |
README preview image |
GitHub Actions regenerates Android launcher/adaptive icons from public/icon-512.png during every APK build.
See docs/ANDROID_ICON.md.
|
|
|
|
|
|
๐ฆ Capacitor plugins used
| Plugin | Purpose |
|---|---|
@capacitor/app |
โฌ ๏ธ Back button + app resume event |
@capacitor/filesystem |
๐พ Native file save for exports |
@capacitor/haptics |
๐ณ Vibration feedback |
@capacitor/share |
๐ค Share sheet after export |
@capacitor/browser |
๐ Open URLs in system browser |
@codetrix-studio/capacitor-google-auth |
๐ Native Google Sign-In (Android) |
๐ฆ inkwell/
โโโ ๐ public/
โ โโโ ๐ผ๏ธ icon.svg # Main scalable app icon
โ โโโ ๐ผ๏ธ logo.svg / logo.png # README/header brand logo
โ โโโ ๐ฑ icon-*.png # PWA + launcher icon sizes
โ โโโ ๐ favicon-16.png / favicon-32.png
โ โโโ ๐ apple-touch-icon.png
โ โโโ ๐ manifest.json # PWA web app manifest
โ โโโ โ๏ธ sw.js # Service Worker
โ
โโโ ๐ docs/
โ โโโ ๐ฒ APP_UPDATE_INSTALL.md # In-app APK updater/install flow
โ โโโ ๐จ ANDROID_ICON.md # Android adaptive icon sizing
โ โโโ โ
RELEASE_CHECK.md # Release safety checklist
โ โโโ ๐ CHANGELOG.md # App update history
โ โโโ ๐ WELCOME_NOTE.md # Welcome note toolbar examples
โ โโโ ๐ THEME.md # Theme persistence notes
โ โโโ โ๏ธ EDITOR.md # Editor stability checklist
โ โโโ ๐ผ๏ธ app-icon-preview.png # Icon preview for README
โ
โโโ ๐งฐ scripts/
โ โโโ โ
release-check.mjs # Local release validation script
โ
โโโ โ๏ธ src/
โ โโโ ๐ main.jsx # Entry: HashRouter + providers
โ โโโ ๐บ๏ธ App.jsx # Routes + back button + offline banner
โ โ
โ โโโ ๐ auth/
โ โ โโโ ๐ AuthContext.jsx # Google OAuth web/native
โ โ โโโ โ๏ธ googleDrive.js # Google Drive backup/restore
โ โ
โ โโโ ๐๏ธ store/
โ โ โโโ ๐ AppContext.jsx # Global notes/notebooks state
โ โ โโโ ๐พ storage.js # localStorage CRUD helpers
โ โ
โ โโโ ๐ pages/
โ โ โโโ ๐ Login.jsx # Google sign-in
โ โ โโโ ๐ Home.jsx # Dashboard + pinned/recent notes
โ โ โโโ โ๏ธ Editor.jsx # Rich editor + markdown + reading mode
โ โ โโโ ๐ Notebooks.jsx # Notebook list / CRUD
โ โ โโโ ๐ NotebookDetail.jsx # One notebook's notes
โ โ โโโ ๐ Search.jsx # Full-text search
โ โ โโโ ๐ท๏ธ Tags.jsx # Tag list + tag notes
โ โ โโโ ๐ค Profile.jsx # User stats
โ โ โโโ โ๏ธ Settings.jsx # Theme, sync, update, import/export
โ โ
โ โโโ ๐งฉ components/
โ โ โโโ ๐ TopBar.jsx
โ โ โโโ ๐ป BottomNav.jsx
โ โ โโโ โ Fab.jsx
โ โ โโโ ๐ NoteCard.jsx
โ โ โโโ ๐ญ EmptyState.jsx
โ โ โโโ ๐ Toast.jsx
โ โ โโโ ๐ฒ UpdateChecker.jsx # GitHub Release APK updater
โ โ
โ โโโ ๐ช hooks/
โ โ โโโ ๐ useTheme.js # Persistent dark/light theme
โ โ โโโ ๐ useGreeting.js
โ โ
โ โโโ ๐ ๏ธ utils/
โ โ โโโ ๐ค exportNote.js # TXT / MD / XLS / PDF export
โ โ โโโ ๐ณ haptics.js
โ โ โโโ ๐งฎ helpers.js
โ โ โโโ ๐ฑ seed.js
โ โ
โ โโโ ๐จ styles/
โ โโโ ๐จ styles.css # Full app styling
โ
โโโ ๐ค .github/workflows/
โ โโโ ๐ฆ build-apk.yml # Build signed APK + GitHub Release
โ
โโโ ๐ .env.example
โโโ ๐ซ .gitignore
โโโ ๐ฑ capacitor.config.json
โโโ ๐ index.html
โโโ ๐ฆ package.json
โโโ ๐ package-lock.json
โโโ ๐ push.sh
โโโ โฒ vercel.json
โโโ โก vite.config.js
node_modules/
dist/
android/
.env
*.jks
keystore.txt
# 1๏ธโฃ Clone the repo
git clone https://github.com/tharindu899/Inkwell.git
cd Inkwell
# 2๏ธโฃ Set up environment
cp .env.example .env
# โ Edit .env and set VITE_GOOGLE_CLIENT_ID
# 3๏ธโฃ Install & run
npm install
npm run dev๐ Opens at http://localhost:5173
1. Push your repo to GitHub
2. vercel.com/new โ import repo
3. Framework: Vite | Build: npm run build | Output: dist
4. Add env var: VITE_GOOGLE_CLIENT_ID = <your client id>
5. Deploy ๐
vercel.json handles SPA rewrites + correct cache headers automatically.
๐ก No PC needed โ works fully from Termux on your phone.
pkg install openjdk-17
keytool -genkeypair -v \
-keystore inkwell-release.jks \
-alias inkwell \
-keyalg RSA -keysize 2048 -validity 10000 \
-storepass YOUR_STORE_PASSWORD \
-keypass YOUR_KEY_PASSWORD \
-dname "CN=Inkwell, OU=App, O=Personal, L=City, S=State, C=US"
base64 inkwell-release.jks > keystore.txt
cat keystore.txt # โ copy ALL of this output
โ ๏ธ Keepinkwell-release.jkssafe. Losing it means you can't update the app later.
pkg install git
git config --global user.name "Your Name"
git config --global user.email "you@email.com"
git init && git add .
git commit -m "Initial commit"
git branch -M main
git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPO.git
git push -u origin mainYour repo โ Settings โ Secrets and variables โ Actions โ New repository secret
| Secret | Value | Required |
|---|---|---|
KEYSTORE_BASE64 |
Full base64 from cat keystore.txt |
โ |
KEY_ALIAS |
inkwell (your chosen alias) |
โ |
KEY_PASSWORD |
Your keypass value | โ |
STORE_PASSWORD |
Your storepass value | โ |
VITE_GOOGLE_CLIENT_ID |
Google OAuth 2.0 Client ID | โ |
VITE_GITHUB_REPO |
Optional local override; workflow uses github.repository |
โ |
VITE_GITHUB_TOKEN |
Not recommended for public APK builds | โ |
In Google Cloud Console โ Credentials โ your OAuth Client โ Authorised JavaScript origins, add:
https://localhost
Required because Capacitor serves the app from
https://localhostinside Android WebView.
Also create a separate Android OAuth credential (type: Android) with:
- Package name:
com.inkwell.notes - SHA-1: found in the GitHub Actions job summary after your first build
GitHub โ Actions โ your workflow run โ wait ~5 min โ Artifacts โ download APK
Or grab it from the Releases tab โ the CI creates one automatically. โ
To install: open APK on phone โ Install (Enable: Settings โ Security โ Install unknown apps)
Every git push to main auto-creates a GitHub Release. The CI derives the version from the run number:
Run #45 โ v1.4.5 Run #106 โ v2.0.6
MAJOR = floor(run / 100) + 1
MINOR = floor((run % 100) / 10)
PATCH = run % 10
The in-app update checker compares this version against the latest GitHub Release and prompts users to download if newer.
| Variable | Required | Description |
|---|---|---|
VITE_GOOGLE_CLIENT_ID |
โ | Google OAuth 2.0 Client ID โ get one here |
VITE_GITHUB_REPO |
Optional local override; workflow uses github.repository |
โ |
VITE_GITHUB_TOKEN |
Not recommended for public APK builds | โ |
๐ How to get a Google Client ID
- Google Cloud Console โ create/select a project
- APIs & Services โ Library โ enable Google Drive API
- APIs & Services โ OAuth consent screen โ External โ add scope
.../auth/drive.appdata - Credentials โ Create โ OAuth 2.0 Client ID โ Web application
- Authorised JavaScript origins:
http://localhost:5173(local dev)https://your-app.vercel.app(production)https://localhost(Android WebView โ required)
- Copy the Client ID
๐ค git push main
โ
โผ
โ ๐ Checkout + Node 20 + npm install
โก ๐ฉน Patch GoogleAuth plugin (add Drive scope to native token)
โข ๐ข Derive version from run number โ TAG, VERSION, APK_NAME
โฃ ๐ Write version into package.json (so VITE_APP_VERSION matches tag)
โค ๐๏ธ npm run build โ dist/ (Vite)
โฅ ๐ฑ npx cap add android โ android/ (Capacitor project)
โฆ ๐จ Generate Android launcher/adaptive icons from icon-512.png
โง โ Set up JDK 21 + Android SDK (API 35)
โจ ๐ Patch native APK installer + npx cap sync android
โฉ ๐ Decode KEYSTORE_BASE64 โ inkwell-release.jks
โช ๐ Print SHA-1 to job summary (for Google Cloud Console setup)
โซ ๐จ ./gradlew assembleRelease โ signed APK
โฌ ๐ฆ Rename APK โ Inkwell-v{VERSION}.apk
โญ โฌ๏ธ Upload as workflow artifact (retained 30 days)
โฎ ๐ Create GitHub Release with APK attached
โฏ ๐ฒ Native updater downloads APK inside app from Releases
๐ Backup file lives in
appDataFolderโ never visible in your My Drive.
Backup (inkwell-sync.json):
| Step | What happens |
|---|---|
| โ๏ธ You edit a note | 1.2 s debounce timer starts |
| โฑ๏ธ Timer fires | Cached token used (no popup) |
| ๐ค Drive call | PATCH if file exists ยท POST multipart if new |
| โ Done | iw_last_auto_backup updated in localStorage |
Restore on login:
| Condition | Behaviour |
|---|---|
| ๐ Fresh install / only welcome note | Cloud data replaces local entirely |
| ๐ฑ Existing notes present | Merge by ID โ newer updatedAt wins |
| โ After merge | Merged result pushed back to Drive |
Manual backup / restore also available in Settings.
๐ localStorage keys
| Key | Content |
|---|---|
iw_notes |
JSON array of note objects |
iw_notebooks |
JSON array of notebook objects |
iw_profile |
{ name, email, joinDate } |
iw_theme |
"dark" or "light" |
iw_fontSize |
"small" ยท "medium" ยท "large" |
iw_sort |
"modified" ยท "created" ยท "title" ยท "tags" |
iw_searches |
Up to 8 recent search strings |
iw_gauth |
Cached Google user + access token + expiry |
iw_prefs |
Miscellaneous UI preferences |
iw_last_auto_backup |
ISO timestamp of last Drive backup |
iw_last_cloud_sync |
Drive file modifiedTime |
๐ Note object shape
{
"id": "lp3abc12x",
"title": "My Note",
"content": "<p>HTML from editor</p>",
"tags": ["work", "ideas"],
"notebookId": "lp1xyz99a",
"pinned": false,
"wordCount": 42,
"createdAt": "2025-01-01T10:00:00.000Z",
"updatedAt": "2025-01-15T14:30:00.000Z"
}๐ Notebook object shape
{
"id": "lp1xyz99a",
"name": "Work",
"color": "#6090e0",
"icon": "fa-briefcase",
"createdAt": "2025-01-01T09:00:00.000Z"
}โ
DO commit โ NEVER commit
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
src/ dist/ โ Vite output
public/ android/ โ Capacitor output
docs/
index.html node_modules/
package.json inkwell-release.jks โ signing key!
capacitor.config.json keystore.txt โ base64 key!
vite.config.js .env โ has your secrets
vercel.json
.env.example
.gitignore
.github/workflows/build-apk.yml
Before publishing or creating a new release, run the release check from the project root:
npm ci
npm run release:checkThe full checklist is in docs/RELEASE_CHECK.md. It covers required GitHub secrets, files that must not be committed, and the expected clean build checks.
| ๐จ Symptom | ๐ก Cause | โ Fix |
|---|---|---|
| Sign-in button stuck "loading" | VITE_GOOGLE_CLIENT_ID missing |
Add to .env or Vercel env vars |
Sign-in failed on Android |
Missing Android OAuth credential | Add Android credential in GCloud Console (SHA-1 + package com.inkwell.notes) |
| Drive backup/restore does nothing | Token expired or Drive scope missing | Sign out โ sign in again; re-run CI |
APK build: SDK location not found |
setup-android step failed |
Re-run the workflow |
APK build: KEYSTORE_BASE64 error |
Secret missing or bad base64 | Re-encode keystore, update secret |
| Update checker: "Repo not found" | VITE_GITHUB_REPO secret missing or wrong |
Set it to your-username/your-repo in GitHub Secrets โ Actions |
| Update checker: "rate limited" | Too many API calls (60/hr limit) | Set VITE_GITHUB_TOKEN with contents:read |
| Notes not syncing across tabs | Browser blocking storage events | Check browser privacy/storage settings |
| White screen on Android | Absolute URL paths failing in WebView | Confirm base: './' in vite.config.js + HashRouter is used |
๐ Why HashRouter instead of BrowserRouter?
Android WebView serves assets from the filesystem โ there's no server to fall back to /index.html for deep links. HashRouter keeps navigation in the URL hash (https://localhost/#/editor), which works perfectly on-device.
๐ Why `base: './'` in vite.config.js?
Capacitor copies the built dist/ folder into the Android project. Absolute asset paths (/assets/...) fail when loading from the filesystem. Relative paths (./assets/...) work correctly in both WebView and browser.
๐ฉน Why does the CI patch the GoogleAuth plugin?
@codetrix-studio/capacitor-google-auth only requests profile email by default. The CI patches the plugin's Java source to add https://www.googleapis.com/auth/drive.appdata before Capacitor generates the Android project, because native Google Sign-In must request all scopes upfront โ they can't be added silently later.
๐ Why is copy/select blocked outside the editor?
Android WebView lets users long-press and copy UI labels. CopySelectGuard intercepts copy, cut, and selectstart events globally, allowing them only when the event target (or selection nodes) are inside .editor-body, .editor-title, or [data-inkwell-copy-ok="1"].
Code blocks now use a readable light surface in light theme, with dark text and visible copy/action buttons.
The docs/ folder keeps release/setup notes separate from the main README.
| Doc | Purpose |
|---|---|
docs/APP_UPDATE_INSTALL.md |
In-app APK update, installer, and cleanup flow |
docs/ANDROID_ICON.md |
Android adaptive icon size and safe-zone notes |
docs/THEME.md |
Light/dark theme persistence details |
docs/EDITOR.md |
Editor save, markdown, paste, tag chips, and mobile stability notes |
docs/RELEASE_CHECK.md |
Final checks before publishing a public release |
docs/CHANGELOG.md |
Full app update history and release notes template |
docs/WELCOME_NOTE.md |
App welcome note and editor toolbar examples |
Recommended reading order:
RELEASE_CHECK.mdANDROID_ICON.mdAPP_UPDATE_INSTALL.mdTHEME.mdEDITOR.md
Full update history is available in docs/CHANGELOG.md.
For the in-app update popup, write the newest changes in the GitHub Release description/body. Inkwell reads that release body and shows the short changelog in the update sheet.
Recommended release body format:
## Inkwell v1.0.9
### Fixed
- Added selected delete confirmation
- Added 5-second Undo after delete
- Fixed Tags page selected/unselected colors
### Install
Tap Install in the app update popup or download the APK below.Inkwell checks GitHub Releases for a newer Android APK. The app uses the GitHub Release description/body as changelog text for the update popup.
| State | Result |
|---|---|
| Current APK is older | New APK update available + Install |
| Current APK matches latest release | You are already updated |
| Release has no APK asset | Opens the GitHub Release page |
| In-app install is blocked | Falls back to the release page |
- Open Settings โ Check for updates.
- Inkwell checks the latest GitHub Release for
tharindu899/Inkwell. - If a newer APK exists, the popup shows:
- latest version
- installed version
- APK size
- download count
- repo name
- MB / percent progress bar
- Tap Install.
- The APK downloads inside the app.
- Android Package Installer opens directly.
- Tap Install / Update manually.
- The temporary downloaded APK is deleted from app cache after a short delay.
Android does not allow silent APK updates for normal apps, so the final Install/Update tap is required.
See docs/APP_UPDATE_INSTALL.md.
The APK build uses correct Android launcher/adaptive icon sizes and safe-zone padding so MIUI/Android launchers do not crop the icon badly.
See docs/ANDROID_ICON.md.
Theme selection is saved in iw_theme and applied before React renders, so light mode stays active after closing and reopening the Android app.
See docs/THEME.md.
Personal project โ see the repository for any licence details.
Made with ๐๏ธ by tharindu899
โญ Star the repo if you find it useful!
-
Tag selector updated: the editor footer now stays compact, and full tag management opens in a modal.
-
Manage Tags modal is now compact, smaller, and uses a 2-column action layout.
-
Manage Tags modal now uses horizontal scrolling compact tag chips and removes duplicate Add controls.
-
Editor tag selector now matches the notebook selector style, and the Tags page uses compact note-like tag pills.
-
Tags page uses independent note-size tag pills, and notebook badges use the matching notebook icon.
-
Added selected delete confirmation modal with 5-second Undo toast.
-
App welcome note demonstrates all main editor toolbar options for first-time users.
-
App welcome note refreshes only the built-in
welcome-note; it does not overwrite user notes. -
Added full toolbar examples inside the actual app welcome note.
-
Added GitHub theme as the third app appearance option.
-
Welcome note no longer shows a fixed version tag; it can refresh through internal seed updates.