Installation
Prerequisites
- Node.js 20+ (as specified in
.node-version) - npm 10+ (package manager)
Clone & Install
git clone https://github.com/plcunha/plfy.git
cd plfy
npm install
Quick Start
# Build the main process first (required before dev)
npm run build:main
# Start development server (opens the app automatically)
npm run dev
Building for Production
# Build for current platform
npm run package
# Build for specific platforms
npm run package:win # Windows (.exe)
npm run package:mac # macOS (.dmg)
npm run package:linux # Linux (.AppImage, .deb)
Architecture Overview
Plfy is built on Electron 40 with a clear separation between main process, preload bridge, and renderer.
src/
├── app/
│ ├── backlinks/ → Bidirectional link tracking
│ ├── commands/ → Command palette & registration
│ ├── daily/ → Daily notes automation
│ ├── editor/ → CodeMirror 6 markdown editor
│ ├── explorer/ → File tree with CRUD operations
│ ├── export/ → PDF, HTML, Markdown export
│ ├── indexer/ → Full-text search & indexing
│ ├── outline/ → Document structure navigation
│ ├── plugins/
│ │ ├── api/ → Plugin API interfaces
│ │ ├── loader/ → Dynamic plugin loading
│ │ └── runtime/ → Plugin execution sandbox
│ ├── search/ → Advanced search (regex, operators)
│ ├── settings/ → User preferences & persistence
│ ├── settings-panel/ → Settings UI panel
│ ├── switcher/ → Quick file switcher
│ ├── sync/ → Cloud sync (Supabase + Yjs)
│ │ └── encryption/ → AES-256-GCM + PBKDF2
│ ├── tabs/ → Workspace tab management
│ ├── tags/ → Tag management & browsing
│ ├── templates/ → Note templates with variables
│ ├── ui/ → 13 extracted UI components
│ └── workspace/ → State management & coordination
├── shared/
│ ├── Config.ts → App configuration
│ ├── FileSystem.ts → File operations with caching
│ ├── IPCFileSystem.ts → IPC-based file system bridge
│ ├── Logger.ts → Structured logging
│ └── Validation.ts → Input validation utilities
├── styles/ → 8 external CSS files
│ ├── index.css → Entry point (@import chain)
│ ├── base.css → Reset, scrollbar, focus states
│ ├── themes.css → CSS variables (light/dark)
│ ├── layout.css → App shell, titlebar, grid
│ ├── sidebar.css → Sidebar tabs, file tree
│ ├── editor.css → Tabs bar, editor area, graph
│ ├── components.css → Buttons, modals, toasts
│ └── animations.css → 17 @keyframes
├── types/
│ └── index.ts → TypeScript type definitions
├── main.ts → Electron main process
├── preload.ts → Secure IPC bridge
└── renderer.ts → Renderer entry point
UI Components
The UI layer was refactored from a monolithic 5,500-line file into 13 focused components using a dependency injection pattern (callbacks interfaces):
| Component | Responsibility | Lines |
|---|---|---|
ContextMenu |
Right-click context menus, escapeHtml() utility |
~634 |
SupabaseSyncUI |
Sync settings, vault linking, conflict resolution | ~580 |
FileTreeUI |
File explorer tree rendering & interaction | ~310 |
RightSidebar |
Backlinks, outline, graph tabs | ~327 |
SearchUI |
Search panel with filters & results | ~212 |
SettingsModal |
Settings modal dialog | ~175 |
NoteHeaderUI |
Note title & breadcrumb | ~137 |
CommandPaletteUI |
Fuzzy command search & execution | ~130 |
StatusBarUI |
Bottom status bar (word count, sync) | ~105 |
ThemeManager |
Theme detection & switching | ~98 |
GraphView |
D3 force-directed canvas graph | ~505 |
SidebarTabsUI |
Sidebar tab switcher | ~48 |
ResizersUI |
Panel resize handles | ~48 |
ToastUI |
Toast notifications | ~42 |
CSS Architecture
All styles live in src/styles/ and are imported via
index.css in the renderer entry point. No inline CSS injection at runtime.
| File | Purpose |
|---|---|
base.css |
Reset, scrollbar, selection, focus states |
themes.css |
CSS variables for light/dark, prefers-color-scheme |
layout.css |
App shell, titlebar (mac/win), main grid, sidebar, resizer, status bar |
sidebar.css |
Sidebar tabs, file tree, folders, chevrons, search panel |
editor.css |
Tabs bar, editor area, note header, breadcrumb, backlinks, outline, graph panel |
components.css |
Buttons, inputs, modals, command palette, context menu, toast, settings |
animations.css |
17 deduplicated @keyframes |
Markdown Editor
Built on CodeMirror 6 with full syntax highlighting, bracket matching, auto-indentation, and Markdown-specific extensions. Supports:
- GFM (GitHub Flavored Markdown) syntax
- Wiki-style
[[links]]with auto-completion - Live preview mode
- Auto-save with configurable interval
- Find & replace (
Ctrl+F,Ctrl+H)
Knowledge Graph
The GraphView component renders note connections as an interactive
force-directed graph on HTML5 <canvas>:
- D3-force simulation with charge, link, and center forces
- Mouse hover shows note name tooltip
- Click to open the note in the editor
- Scroll to zoom, drag to pan
- Automatic resize via
ResizeObserver - Accessible in the right sidebar under the "Graph" tab
Advanced Search
Full-text search powered by the Indexer module with support for:
- Boolean operators:
AND,OR,NOT - Regex pattern matching
- Case sensitivity toggle
- Whole word matching
- Filter by file name, content, or tags
Cloud Sync
Local-first sync architecture built on Supabase + Yjs CRDTs:
- Offline-first with local IndexedDB persistence
- Conflict-free merging via Yjs CRDT documents
- End-to-end encryption (AES-256-GCM key derived via PBKDF2)
- Vault linking & device management
- Sync status indicator in the status bar
Export
Export notes from the command palette or context menu:
- PDF — Print-quality output with proper formatting
- HTML — Standalone HTML file with embedded styles
- Markdown — Raw Markdown with front-matter preserved
Creating a Plugin
Plfy's plugin system lets you extend every aspect of the application.
1. Create plugin directory
mkdir plugins/my-plugin
2. Add manifest.json
{
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"minAppVersion": "1.0.0",
"description": "Does amazing things",
"author": "Your Name",
"permissions": ["notes:read", "commands:register"]
}
3. Implement main.ts
import { Plugin } from 'plfy/plugins';
export default class MyPlugin extends Plugin {
async onLoad() {
this.app.commands.register({
id: 'my-plugin:greet',
name: 'Say Hello',
callback: () => console.log('Hello from my plugin!'),
});
this.app.events.on('note:open', (note) => {
console.log(`Opened: ${note.path}`);
});
}
async onUnload() {
// Cleanup resources
}
}
Plugin API
| API | Description |
|---|---|
app.workspace |
Open/close notes, manage layout |
app.notes |
CRUD operations on notes |
app.commands |
Register and execute commands |
app.events |
Subscribe to application events |
app.storage |
Persist plugin data |
Permissions
Plugins must declare required permissions in their manifest:
notes:read/notes:write/notes:deletegraph:read/graph:modifycommands:registerworkspace:read/workspace:modifystorage:read/storage:writeui:ribbon/ui:statusbar/ui:sidebarnetwork:fetchfilesystem:vault
Keyboard Shortcuts
| Shortcut | Action |
|---|---|
Ctrl+P |
Open command palette |
Ctrl+N |
Create new note |
Ctrl+S |
Save current note |
Ctrl+O |
Open vault |
Ctrl+\ |
Toggle left sidebar |
Ctrl+Shift+\ |
Toggle right sidebar |
Ctrl+G |
Open graph view |
Ctrl+F |
Find in note |
Ctrl+H |
Find and replace |
Configuration
Editor Settings
{
editor: {
fontSize: 16,
fontFamily: "'JetBrains Mono', monospace",
lineNumbers: true,
lineWrapping: true,
tabSize: 2,
autoSave: true,
autoSaveInterval: 1000,
spellcheck: false,
}
}
Appearance
{
appearance: {
theme: 'system', // 'light' | 'dark' | 'system'
accentColor: '#3b82f6',
sidebarWidth: 280,
}
}
Development Commands
| Command | Description |
|---|---|
npm run dev |
Start development with hot reload |
npm run build |
Build for production (3 stages) |
npm run typecheck |
Run TypeScript type checking |
npm run lint |
Run ESLint |
npm run lint:fix |
Auto-fix ESLint issues |
npm run format |
Format with Prettier |
npm run test |
Run tests (2,607 tests, 60 files) |
npm run test:coverage |
Run tests with coverage report |
npm run package |
Package for current platform |