A Linux desktop notes app built with PyQt6, with optional cloud sync via Supabase.
- Notebooks & Sections — hierarchical note organization (notebook → section → note)
- Tags — label notes and filter by tag
- WYSIWYG Editor — powered by Toast UI Editor (Markdown under the hood)
- 8 Themes — 4 dark (Dark, Deep Sea, Midnight, Night Forest) + 4 light (Classic, Ocean, Forest, Rose)
- Auto-save — saves automatically 800ms after you stop typing
- Full-text search — search by title, content, and tags
- Pin & Priority — pin important notes, mark priority (Low / Medium / High)
- Export PDF — export any note to a PDF file
- Cloud Sync — auto-sync across devices via Supabase (optional)
- Offline-first — all features work without an internet connection
Not sure which option to choose? Go with Shared Server — it takes 2 minutes, requires no configuration, and everything is ready to use immediately.
Connect to the developer's pre-configured server. No Supabase account, no database setup, no credentials to manage — just install, create an account, and start taking notes.
Requirements: Ubuntu / Debian, Python 3.10+, sudo access.
cd lemanotes-apps
chmod +x install.sh
./install.shWhen the installer asks "Which database server do you want to use?", press Enter to accept the default:
▶ 1) Shared Server (recommended) ← default
2) Self Hosted (my own Supabase)
The installer writes the server credentials automatically. No manual input required.
lemanotes
# or open it from the app launcher / desktop iconGo to Account → Sign in… and register a new account. Your notes will sync automatically across any machine using the same account.
Use your own Supabase project. Choose this if you want full control over the backend and data storage.
Requirements: Ubuntu / Debian, Python 3.10+, sudo access, a Supabase account.
Before installing, complete the Supabase setup steps below to create the database table and get your credentials.
cd lemanotes-apps
chmod +x install.sh
./install.shWhen asked "Which database server?", select Self Hosted, then choose a sync mode:
| Mode | Description |
|---|---|
| Static | Enter your Supabase URL and Anon Key during install. Credentials are written to .env. Users only need to log in — no in-app setup. |
| Dynamic | Skip credentials for now. Users configure them later inside the app via Account → Setup Supabase… |
lemanotesThe installer finds a suitable Python environment automatically, in this order:
- Active conda environment (if running inside one)
- Any conda environment that already has PyQt6
- System Python that already has PyQt6
- Auto venv — creates a self-contained environment at
~/.local/lib/lemanotes/venv/and installs all dependencies there
No conda or pre-installed packages required — it just works.
chmod +x uninstall.sh
./uninstall.shRemoves app files (~/.local/lib/lemanotes/), desktop shortcut, and the lemanotes CLI launcher.
Your notes (~/LemaNotes/) and settings (~/.config/lemanotes/) are not deleted.
To also remove notes and settings:
rm -rf ~/LemaNotes ~/.config/lemanotes./uninstall.sh && ./install.shsudo apt install -y libxcb-cursor0 libxcb-xinerama0 libxcb-icccm4 \
libxcb-image0 libxcb-keysyms1 libxcb-render-util0
# WebEngine (if the pip version does not bundle it)
sudo apt install -y python3-pyqt6.qtwebenginepip install -r requirements.txtrequirements.txt:
PyQt6>=6.6.0
PyQt6-WebEngine>=6.6.0
supabase>=2.0.0
python-dotenv>=1.0.0
cd lemanotes-apps
python run.py| Error | Fix |
|---|---|
Could not load the Qt platform plugin "xcb" |
sudo apt install -y libxcb-cursor0 |
PyQt6-WebEngine not found |
sudo apt install -y python3-pyqt6.qtwebengine |
| Editor area is blank | Make sure assets/tui/ contains the Toast UI Editor files |
ModuleNotFoundError: supabase |
pip install supabase |
ModuleNotFoundError: dotenv |
pip install python-dotenv |
Sync is optional. The app runs fully offline without this setup.
Sign up for free at supabase.com and create a new project.
Open the SQL Editor in the Supabase Dashboard and run:
CREATE TABLE notes (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
notebook TEXT NOT NULL,
section TEXT NOT NULL DEFAULT '',
slug TEXT NOT NULL,
title TEXT NOT NULL DEFAULT '',
content TEXT NOT NULL DEFAULT '',
tags TEXT[] NOT NULL DEFAULT '{}',
pinned BOOLEAN NOT NULL DEFAULT FALSE,
priority INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
UNIQUE(user_id, notebook, section, slug)
);
ALTER TABLE notes ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users manage own notes"
ON notes FOR ALL
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);In the Supabase Dashboard → Authentication → URL Configuration, add the following to Redirect URLs:
http://localhost:54321/callback
- Go to console.cloud.google.com and create or select a project.
- Navigate to APIs & Services → Credentials → Create Credentials → OAuth client ID.
- Set Application type to Web application.
- Under Authorized redirect URIs, add your Supabase callback URL:
(Copy the exact URL from Supabase Dashboard → Authentication → Providers → Google → Callback URL)
https://<your-project-ref>.supabase.co/auth/v1/callback - Click Create. Google will give you a Client ID and Client Secret.
- Client ID format:
123456789012-xxxxxxxxxxxxxxxx.apps.googleusercontent.com
- Client ID format:
- In Supabase Dashboard → Authentication → Providers → Google, paste the Client ID and Client Secret, then click Save.
- Go to github.com → Settings → Developer settings → OAuth Apps → New OAuth App.
- Fill in the form:
Application name : LemaNotes Homepage URL : https://<your-project-ref>.supabase.co Authorization callback URL : https://<your-project-ref>.supabase.co/auth/v1/callback - Click Register application, then click Generate a new client secret.
- Copy the Client ID and Client Secret.
- In Supabase Dashboard → Authentication → Providers → GitHub, paste both values and click Save.
Go to Project Settings → API and copy:
- Project URL —
https://xxxx.supabase.co - Anon (public) Key —
eyJhbGciOiJIUzI1NiIs...
There are two ways to provide the Supabase URL and Anon Key to the app.
In LemaNotes: Account → Setup Supabase…, enter the URL and Anon Key, then click Save & Connect. Values are saved to ~/.config/lemanotes/settings.json.
Copy .env.example to .env in the app directory and fill in your values:
cp .env.example .envLEMANOTES_SYNC_FROM_ENV=true
LEMANOTES_SUPABASE_URL=https://xxxx.supabase.co
LEMANOTES_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIs...When LEMANOTES_SYNC_FROM_ENV=true:
- The app loads URL and key from
.envon startup - The Account → Setup Supabase… menu item is hidden (config is read-only)
- Users only need to log in — no manual configuration required
- Useful for distributing the app with a pre-configured backend
When LEMANOTES_SYNC_FROM_ENV=false (or .env is absent):
- URL and key are configured through the app UI (Option A)
- Values are stored in
~/.config/lemanotes/settings.json
SYNC_FROM_ENV |
Config source | Setup menu |
|---|---|---|
true |
.env file |
Hidden |
false or absent |
App UI → settings.json |
Visible |
Never commit your
.envfile to version control. Add it to.gitignore.
┌──────────────┬──────────────────┬────────────────────────────┐
│ Sidebar │ Note List │ Editor │
│ │ │ │
│ Notebooks │ [Search] │ [Title input] │
│ Sections │ ───────────── │ [Tag bar] │
│ ───────── │ Note cards │ [Toolbar] │
│ Tags │ (title, tags, │ │
│ │ date) │ [Editor area] │
│ │ │ │
└──────────────┴──────────────────┴────────────────────────────┘
| Action | How |
|---|---|
| New notebook | Ctrl+Shift+N or click + in the sidebar |
| New section | Right-click a notebook → New Section |
| Rename / Delete | Right-click a notebook or section |
| Sort notebooks | Right-click the notebook area → choose sort order |
| Action | How |
|---|---|
| New note | Ctrl+N or the + Note button in the note list |
| Search notes | Type in the search box (searches title, content, and tags) |
| Pin a note | Right-click → Pin, or use the pin button in the editor |
| Set priority | Right-click → Priority → Low / Medium / High |
| Delete a note | Right-click → Delete Note |
| Move a note | Drag & drop onto another notebook or section in the sidebar |
| Shortcut | Action |
|---|---|
Ctrl+B |
Bold |
Ctrl+I |
Italic |
Ctrl+Shift+S |
Strikethrough |
Ctrl+Shift+H |
Highlight |
Ctrl+K |
Insert Link |
Ctrl+Shift+I |
Insert Image |
Ctrl+Shift+C |
Insert Code Block |
Ctrl+F |
Find |
Ctrl+H |
Find & Replace |
Ctrl+Z |
Undo |
Ctrl+Shift+Z |
Redo |
- Click + tag below the title to add a tag
- Click × on a tag to remove it
- Click a tag in the sidebar to filter all notes by that tag
Switch themes via:
Ctrl+Shift+D— cycle through all themes (Dark → Deep Sea → Midnight → Night Forest → Classic → Ocean → Forest → Rose)Ctrl+,→ Appearance tab → pick a theme from the dropdown
Open a note → Ctrl+E or File → Export as PDF…
| Action | How |
|---|---|
| Login (email) | Account → Sign in… → enter email & password |
| Login (OAuth) | Account → Sign in… → click GitHub or Google |
| Register | Account → Sign in… → click Register |
| Manual sync | Account → Sync Now |
| Logout | Account → Sign out |
Once logged in:
- Auto pull — cloud data is pulled to local storage on first login
- Auto push — every time a note is saved, it is pushed to the cloud in the background
- Delete sync — deleting a note locally marks it as deleted in the cloud
- Sync status is shown in the bottom-right corner of the status bar
Notes are always stored locally in
~/LemaNotes/. Cloud sync adds a backup layer and does not replace local storage.
| Shortcut | Action | Category |
|---|---|---|
Ctrl+B |
Bold | Format |
Ctrl+I |
Italic | Format |
Ctrl+Shift+S |
Strikethrough | Format |
Ctrl+Shift+H |
Highlight | Format |
Ctrl+K |
Insert Link | Insert |
Ctrl+Shift+I |
Insert Image | Insert |
Ctrl+Shift+C |
Insert Code Block | Insert |
Ctrl+F |
Find | Find |
Ctrl+H |
Find & Replace | Find |
Ctrl+Z |
Undo | Edit |
Ctrl+Shift+Z |
Redo | Edit |
Ctrl+N |
New Note | App |
Ctrl+Shift+N |
New Notebook | App |
Ctrl+E |
Export PDF | App |
Ctrl+, |
Settings | App |
Ctrl+Shift+D |
Cycle Theme | App |
Ctrl+R |
Refresh Notes | App |
Individual shortcuts can be disabled under Settings → Shortcuts.
~/LemaNotes/
<NotebookName>/
<note-slug>.md
<note-slug>.meta.json
<SectionName>/
<note-slug>.md
<note-slug>.meta.json
~/.config/lemanotes/
settings.json # theme, font size, shortcuts, Supabase config (UI mode)
lemanotes-apps/
├── run.py
├── requirements.txt
├── .env.example # Supabase config template
├── assets/
│ ├── editor.html # Toast UI Editor wrapper
│ └── tui/ # Toast UI Editor static files
└── notes_app/
├── storage.py # File-based storage (CRUD, search, tags)
├── sync.py # Supabase sync manager
├── settings.py # Load/save settings.json
├── themes.py # 8 color themes
├── shortcuts.py # Shortcut definitions
├── widgets.py # TagPill, NoteListWidget, etc.
├── dialogs.py # Settings, Login, Supabase Setup dialogs
├── sidebar.py # Left panel: notebook tree + tag list
├── note_list.py # Center panel: note list + search
├── editor.py # Right panel: editor + tag bar + toolbar
└── main_window.py # Main window, wires all panels together