A modern, ultra-minimalist Markdown writing app built with TanStack Start, Milkdown, and Tailwind CSS.
A quick walkthrough of creating a page, writing Markdown, and using slash commands in M34-Write.
| Empty workspace | Writing with Markdown | Slash commands |
|---|---|---|
![]() |
![]() |
![]() |
| New page | Sidebar pages |
|---|---|
![]() |
![]() |
- Clean, centered workspace — A distraction-free canvas with generous whitespace and monochrome styling.
- Collapsible sidebar — Navigate pages, search, create new pages, and delete pages quickly.
- Inline Markdown rendering — Write
#headers,-lists,**bold**, code, blockquotes, and more; they render as you type. - Slash commands — Type
/to insert headings, lists, todos, quotes, code blocks, dividers, and images. - Image uploads — Drag, drop, paste, or pick images; they are inlined as Base64 data URLs so everything stays local.
- Autosave with status — Debounced persistence to
localStoragewith a live "Saving… / Saved" indicator. - Version history — Each page keeps up to 20 autosaved snapshots; view, diff, and revert to previous states.
- Export — Download the current page as a clean PDF or raw Markdown (
.md). - Local-first — No backend required; all data is stored in the browser's
localStorage.
M34-Write can be run as a standalone desktop app on Linux, macOS, and Windows. The desktop build wraps the app in Electron and runs a local server internally — your notes still live in the app's own localStorage. Every desktop build includes the custom M34-Write "M" icon.
Download the latest v0.1 desktop archives from /0.1v/bin/:
| Platform | Archive | SHA256 |
|---|---|---|
| Linux (x64) | M34-Write-linux-x64.tar.gz |
2b29238290b91336fc6e84565d4603f5e24feff38ec77931f069b2fe52e353dc |
| macOS (Apple Silicon) | M34-Write-darwin-arm64.zip |
9173d9897a502589c597ba45ee563509fe20a164a57050dec8bccfc00b05f0c8 |
| Windows (x64) | M34-Write-win32-x64.zip |
82788ab235d00af3acef4d3cec42200bd8988fc836a078c8002a785831c0496e |
Extract the archive for your platform and launch the M34-Write executable (M34-Write.app on macOS, M34-Write.exe on Windows).
All three checksums are also listed in /0.1v/bin/SHA256SUMS.
# macOS / Linux
shasum -a 256 M34-Write-linux-x64.tar.gz
# or, from inside /0.1v/bin/
sha256sum -c SHA256SUMS# Windows (PowerShell)
Get-FileHash M34-Write-win32-x64.zip -Algorithm SHA256Once the repo is connected to GitHub (Lovable → Plus menu → GitHub → Connect project), pushing a v* tag (e.g. git tag v0.1.0 && git push --tags) triggers .github/workflows/release.yml, which builds all three archives in CI and attaches them — plus a regenerated SHA256SUMS — to a GitHub Release at https://github.com/<owner>/<repo>/releases/latest.
bun install
bun run electron:package # produces electron-release/M34-Write-<platform>-<arch>/Cross-compile from Linux by passing --platform=darwin or --platform=win32 and --arch=x64|arm64:
bun run build
npx @electron/packager . M34-Write --platform=darwin --arch=arm64 --overwrite --out=electron-release \
--ignore='^/src' --ignore='^/public' --ignore='^/docs' --ignore='^/electron-release' \
--ignore='^/node_modules' --ignore='^/dist'bun run electron:dev- Archives are not code-signed — macOS and Windows will show an unsigned-app warning on first launch (right-click → Open on macOS; "More info → Run anyway" on Windows).
- The app spawns a local HTTP server on
127.0.0.1at a random-ish port; no data leaves your machine.
M34-Write also ships as a native Android app (Capacitor shell around the same offline bundle — notes stay in the app's local storage, nothing is uploaded).
bun run mobile:sync # build the static web bundle + copy it into android/
bun run android:apk # android/app/build/outputs/apk/release/*.apk
bun run android:aab # android/app/build/outputs/bundle/release/*.aab (Play Store upload)Requires JDK 21 and the Android SDK (ANDROID_HOME). Open the project in Android Studio with bunx cap open android for emulator runs.
Release signing is read from the environment, so nothing secret lives in the repo:
| Variable | Purpose |
|---|---|
MONO_VERSION_NAME |
Version name, e.g. 0.1.0 |
MONO_VERSION_CODE |
Integer build number, must increase per Play upload |
MONO_KEYSTORE_PATH |
Path to the upload keystore (unsigned build if unset) |
MONO_KEYSTORE_PASSWORD / MONO_KEY_ALIAS / MONO_KEY_PASSWORD |
Keystore credentials |
Signing setup (one time): run bash scripts/make-keystore.sh, then add MONO_KEYSTORE_BASE64, MONO_KEYSTORE_PASSWORD, MONO_KEY_ALIAS and MONO_KEY_PASSWORD as GitHub repository secrets (Settings -> Secrets and variables -> Actions). Back the keystore up outside the repo — store updates must use the same key forever.
Pushing a v* tag then builds signed M34-Write-android.apk / M34-Write-android.aab in CI (downloadable from the GitHub Releases page, and from the run's Artifacts -> M34-Write-android). If the secrets are missing the tagged run fails instead of publishing an unsigned build; manual runs still produce M34-Write-android-UNSIGNED.apk for local testing only. The Android versionCode is derived from the tag (MAJOR*10000 + MINOR*100 + PATCH, so v0.1.0 -> 100), so reruns are reproducible and newer tags always sort higher.
Before submitting to a store:
PRIVACY.mdmust be pushed tomain— store reviewers openhttps://github.com/RU22-S-LABS/Mono-Write/blob/main/PRIVACY.md, and that URL 404s until the file is on the remote branch.
M34-Write can be published to Google Play and the Indus Appstore (India) from the same signed Android build:
| Store | Upload file | Guide |
|---|---|---|
| Google Play | M34-Write-android.aab (release notes call it M34-Write-google-play.aab) |
docs/play-store.md |
| Indus Appstore | M34-Write-android.apk (release notes call it M34-Write-indus-appstore.apk) |
docs/indus-appstore.md |
- Framework: TanStack Start (React 19, Vite 8)
- Editor: Milkdown (Crepe preset)
- State: Zustand with custom debounced persistence
- Styling: Tailwind CSS 4 + shadcn/ui components
- Typography: Geist Sans
- Icons: Lucide React
# Install dependencies
bun install
# Start the dev server
bun devOpen http://localhost:8080 to start writing.
| Script | Description |
|---|---|
bun dev |
Start the Vite dev server |
bun build |
Build for production |
bun build:dev |
Build in development mode |
bun preview |
Preview the production build |
bun lint |
Run ESLint |
bun format |
Format code with Prettier |
src/
├── components/
│ ├── app-sidebar.tsx # Collapsible sidebar navigation
│ ├── diff-view.tsx # Diff dialog for history snapshots
│ ├── markdown-editor.tsx # Milkdown editor wrapper
│ └── workspace.tsx # Main writing canvas + export actions
├── hooks/
│ ├── use-mobile.tsx
│ └── use-notes-store.ts # Zustand store + localStorage persistence
├── routes/
│ ├── __root.tsx # Root layout + head metadata
│ ├── index.tsx # Home page with sidebar + workspace
│ └── README.md # TanStack Start routing conventions
├── styles.css # Monochrome tokens + editor overrides
└── lib/ # Utilities and error handling
All notes and their version history are saved to localStorage under the key md-notes:v1. The app is designed to work fully offline in the browser with no backend. If you later want cloud sync or file storage, the editor and components can remain unchanged while swapping the storage layer.
MIT





