Skip to content

robsonblcksmth/FrankMD

 
 

Repository files navigation

FrankMD icon

FrankMD

FrankMD (Frank Markdown) is a simple, feature-rich, self-hosted markdown note-taking app built with Ruby on Rails 8.
The name honors Frank Rosenblatt, inventor of the Perceptron - the pioneering neural network that laid the foundation for modern AI.
fed (frank editor) is the command-line alias.

GitHub

Why FrankMD?

  • No database - Notes are plain markdown files on your filesystem
  • Self-hosted - Your data stays on your machine or server
  • Docker-ready - One command to start writing
  • Blog-friendly - Perfect for drafting posts with live preview

FrankMD running as desktop app
FrankMD running as a desktop app with fed . command

Features

Editor

  • Clean, distraction-free writing interface
  • Syntax highlighting for markdown
  • Auto-save with visual feedback
  • Typewriter mode for focused writing (cursor stays centered)
  • Customizable fonts and sizes
  • Multiple color themes (light/dark variants)

Data Safety

  • Offline detection: When the server becomes unreachable, FrankMD disables the editor and shows a warning banner to prevent edits that can't be saved. A "Retry" button lets you manually re-check. The editor re-enables automatically once the connection is restored.
  • Content loss protection: If you accidentally delete a large portion of your note (more than 20% and 50+ characters), a warning banner appears with "Undo" and "Save Anyway" buttons, giving you a chance to recover before the deletion is saved.
  • Offline backup: While you're editing, FrankMD periodically saves your work to the browser's local storage as a safety net.
  • Recovery dialog: If the app detects that a local backup differs from the saved version (e.g., after a crash or lost connection), it shows a side-by-side diff so you can choose to keep the server version or restore the backup.

Organization

  • Nested folder structure with context menu (right-click to create new notes or folders)
  • Drag and drop files and folders
  • Quick file finder (Ctrl+P) sorted by recency
  • Full-text search with regex support (Ctrl+Shift+F)
  • Find and replace with regex support (Ctrl+H)
  • Hugo blog post support - Create posts with proper directory structure

File finder
Quick file finder with fuzzy search (Ctrl+P)

Content search
Full-text search with regex support (Ctrl+Shift+F)

Find and replace
Find and replace with regex support (Ctrl+H)

Preview

  • Live markdown preview panel
  • Synchronized scrolling (including typewriter mode)
  • Zoom controls
  • GitHub-flavored markdown support

Preview panel
Live preview with synchronized scrolling

Media

  • Images: Browse local images, search web (DuckDuckGo), Google Images, Pinterest, or generate with AI
  • Videos: Embed YouTube videos with search, or local video files
  • Tables: Visual table editor with drag-and-drop rows/columns
  • Code blocks: Language selection with autocomplete
  • Emoji & Emoticons: Quick picker with search

Local image picker
Browse local images from your filesystem

Web image search
Search images from the web (DuckDuckGo, Google, Pinterest)

AI image generation
Generate images with AI (requires configured AI provider)

AI generated image example
Example AI-generated image: "nano banana"

YouTube search
Search and embed YouTube videos

Table editor
Visual markdown table editor

Emoji picker
Emoji picker with search

Emoticon picker
Emoticon picker

AI Features

  • Grammar Check: AI-powered grammar, spelling, and typo correction
  • Side-by-side diff view with original and corrected text
  • Editable corrections before accepting changes
  • Supports Ollama (local), OpenAI, Anthropic, Gemini, and OpenRouter

AI grammar checker
AI grammar checker with side-by-side diff view

Internationalization

  • 7 languages: English, Português (Brasil), Português (Portugal), Español, עברית (Hebrew), 日本語 (Japanese), 한국어 (Korean)
  • Language picker in the header
  • Persistent preference saved to configuration

Japanese interface
Full interface localization (Japanese example)

Integrations

  • AWS S3 for image hosting (optional)
  • YouTube API for video search (optional)
  • Google Custom Search for image search (optional)
  • AI/LLM providers for grammar checking (optional)

Quick Start

1. Install

curl -sL https://raw.githubusercontent.com/akitaonrails/FrankMD/master/install.sh | bash

Then add to your ~/.bashrc or ~/.zshrc:

source ~/.config/frankmd/fed.sh

To update, run the curl command again.

2. Run

fed ~/my-notes    # open a specific directory
fed .             # open current directory
fed               # open current directory (same as above)

Available commands:

  • fed [path] - Open FrankMD with notes directory
  • fed-update - Check for and download updates
  • fed-stop - Stop the container

3. Configure API Keys (Optional)

For AI features, image hosting, etc., create an env file:

cp ~/.config/frankmd/env.example ~/.config/frankmd/env
# Edit ~/.config/frankmd/env with your API keys

# Then add to ~/.bashrc:
export FRANKMD_ENV=~/.config/frankmd/env

4. Browser (Optional)

FrankMD auto-detects your browser in this order: Chromium → Firefox → Brave → Chrome → Edge. The first one found is used.

To override, set FRANKMD_BROWSER in your ~/.bashrc or ~/.zshrc:

export FRANKMD_BROWSER=brave           # or chromium, google-chrome, microsoft-edge, firefox

For Firefox, enable SSB mode first: about:configbrowser.ssb.enabled = true

Running in Background

To run as a persistent service:

# Create notes directory on the host
mkdir -p ~/notes

# Start in background
docker run -d --name frankmd -p 7591:80 \
  -v ~/notes:/rails/notes \
  --restart unless-stopped \
  akitaonrails/frankmd:latest

# Stop
docker stop frankmd

# Start again
docker start frankmd

# Remove
docker rm -f frankmd

Tip: If you hit permission errors, run the container as your user (--user "$(id -u):$(id -g)") or rebuild the image with matching UID/GID.

Using Docker Compose

For a more permanent setup, use the docker-compose.yml in this repo:

Quick reference (full file in docker-compose.yml):

services:
  frankmd:
    image: akitaonrails/frankmd:latest
    container_name: frankmd
    restart: unless-stopped
    ports:
      - "7591:80"
    volumes:
      - ./notes:/rails/notes
    environment:
      - SECRET_KEY_BASE=${SECRET_KEY_BASE}
# Copy defaults and set required values
cp .env.example .env
# Set in .env:
# SECRET_KEY_BASE=$(openssl rand -hex 64)
# UID=$(id -u)
# GID=$(id -g)

# Ensure notes directory exists (or create your NOTES_PATH target)
mkdir -p notes

# Start
docker compose up -d

Note: The host directory in NOTES_PATH must exist and be writable by the UID/GID in .env. Avoid sudo docker, which creates root-owned bind mounts; if that happens, fix ownership with chown -R UID:GID <path>.

Configuration

FrankMD uses a .fed configuration file in your notes directory. This file is automatically created on first run with all options commented out as documentation.

The .fed File

When you open a notes directory for the first time, FrankMD creates a .fed configuration file with all available options commented out. You can uncomment and modify any setting:

# UI Settings
theme = gruvbox
locale = en
editor_font = fira-code
editor_font_size = 16
preview_zoom = 100
sidebar_visible = true
typewriter_mode = false

# Local images path
images_path = /home/user/Pictures

# AWS S3 (overrides environment variables)
aws_access_key_id = your-key
aws_secret_access_key = your-secret
aws_s3_bucket = your-bucket
aws_region = us-east-1

# API Keys
youtube_api_key = your-youtube-key
google_api_key = your-google-key
google_cse_id = your-cse-id

# AI/LLM (configure one or more providers)
# ai_provider = auto
# ollama_api_base = http://localhost:11434/v1
# ollama_model = llama3.2:latest
# openrouter_api_key = sk-or-...
# openrouter_model = openai/gpt-4o-mini
# anthropic_api_key = sk-ant-...
# anthropic_model = claude-sonnet-4-20250514
# gemini_api_key = ...
# gemini_model = gemini-2.0-flash
# openai_api_key = sk-...
# openai_model = gpt-4o-mini

Priority order: File settings override environment variables, which override defaults.

This means you can:

  • Set global defaults via environment variables
  • Override per-folder using .fed (e.g., different AWS bucket for different projects)
  • UI changes (theme, font) are automatically saved to the file

Note: AI credentials have special behavior - if ANY AI key is set in .fed, ALL AI environment variables are ignored. See Per-Folder AI Configuration for details.

Editing .fed in the App

The .fed file appears in the explorer panel with a gear icon. You can click it to edit directly in FrankMD:

  • The toolbar and preview panel are hidden when editing config files (they only appear for markdown files)
  • Changes are auto-saved like any other file
  • Live reload: When you save .fed, the UI immediately applies your changes (theme, font, etc.)

Available Settings

Setting Type Default Description
theme string (system) Color theme: light, dark, gruvbox, tokyo-night, etc.
locale string en Language: en, pt-BR, pt-PT, es, he, ja, ko
editor_font string cascadia-code Editor font family
editor_font_size integer 14 Font size in pixels (8-32)
preview_zoom integer 100 Preview zoom percentage (50-200)
sidebar_visible boolean true Show explorer panel on startup
typewriter_mode boolean false Enable typewriter mode on startup
images_path string - Local images directory path
aws_access_key_id string - AWS access key for S3
aws_secret_access_key string - AWS secret key for S3
aws_s3_bucket string - S3 bucket name
aws_region string - AWS region
youtube_api_key string - YouTube Data API key
google_api_key string - Google API key
google_cse_id string - Google Custom Search Engine ID
ai_provider string auto AI provider: auto, ollama, openrouter, anthropic, gemini, openai
ai_model string (per provider) Override model for any provider
ollama_api_base string - Ollama API base URL (e.g., http://localhost:11434/v1)
ollama_model string llama3.2:latest Ollama model
openrouter_api_key string - OpenRouter API key
openrouter_model string openai/gpt-4o-mini OpenRouter model
anthropic_api_key string - Anthropic API key
anthropic_model string claude-sonnet-4-20250514 Anthropic model
gemini_api_key string - Google Gemini API key
gemini_model string gemini-2.0-flash Gemini model
openai_api_key string - OpenAI API key
openai_model string gpt-4o-mini OpenAI model

Environment Variables

Environment variables serve as global defaults. They're useful for Docker deployments or when you want the same configuration across all notes directories.

Variable Description Default
NOTES_PATH Directory where notes are stored (must be writable by UID/GID when using Docker) ./notes
IMAGES_PATH Directory for local images (disabled)
FRANKMD_LOCALE Default language (en, pt-BR, pt-PT, es, he, ja, ko) en
SECRET_KEY_BASE Rails secret key (required in production) -

Optional: Image Hosting (AWS S3)

To upload images to S3 instead of using local paths:

Variable Description
AWS_ACCESS_KEY_ID Your AWS access key
AWS_SECRET_ACCESS_KEY Your AWS secret key
AWS_S3_BUCKET S3 bucket name
AWS_REGION AWS region (e.g., us-east-1)

Optional: YouTube Search

To enable YouTube video search in the video dialog:

  1. Go to Google Cloud Console
  2. Create a project and enable "YouTube Data API v3"
  3. Create an API key under Credentials
Variable Description
YOUTUBE_API_KEY Your YouTube Data API key

In-app setup: You can also configure this directly in the .fed file:

youtube_api_key = your-youtube-api-key

When not configured, the YouTube Search tab shows setup instructions with a link to this documentation.

Optional: Google Image Search

To enable Google Images tab (in addition to the free web search):

  1. Go to Google Cloud Console
  2. Create a project and enable "Custom Search API"
  3. Create an API key under Credentials
  4. Go to Programmable Search Engine
  5. Create a search engine with "Search the entire web" enabled
  6. Enable "Image search" in settings
  7. Copy the Search Engine ID (cx value)
Variable Description
GOOGLE_API_KEY Your Google API key
GOOGLE_CSE_ID Your Custom Search Engine ID

In-app setup: You can also configure this directly in the .fed file:

google_api_key = your-google-api-key
google_cse_id = your-custom-search-engine-id

When not configured, the Google Images tab shows setup instructions with a link to this documentation.

Note: Google Custom Search has a free tier of 100 queries/day.

Optional: AI Grammar Checking

FrankMD includes an AI-powered grammar and spelling checker. Click the "AI" button in the editor toolbar to check your text. The AI will fix grammar errors, spelling mistakes, typos, and punctuation while preserving your writing style and markdown formatting.

Supported Providers (priority order in auto mode):

  1. OpenAI - GPT models
  2. Anthropic - Claude models
  3. Google Gemini - Gemini models
  4. OpenRouter - Multiple providers, pay-per-use
  5. Ollama - Local, free, private

When multiple providers are configured, FrankMD automatically uses the first available one in the priority order above. You can override this with ai_provider = <provider>.

Option 1: Ollama (Local, Free, Recommended)

Run AI models locally on your machine with no API costs:

  1. Install Ollama from ollama.com
  2. Pull a model: ollama pull llama3.2:latest
  3. Configure in .fed:
ollama_api_base = http://localhost:11434/v1
ollama_model = llama3.2:latest

Note for Docker users: Use host.docker.internal instead of localhost:

ollama_api_base = http://host.docker.internal:11434/v1

Option 2: OpenRouter

Access multiple AI providers through one API:

  1. Get an API key from openrouter.ai
  2. Configure in .fed:
openrouter_api_key = sk-or-...
openrouter_model = openai/gpt-4o-mini

Option 3: Anthropic (Claude)

Use Anthropic's Claude models:

  1. Get an API key from console.anthropic.com
  2. Configure in .fed:
anthropic_api_key = sk-ant-...
anthropic_model = claude-sonnet-4-20250514

Option 4: Google Gemini

Use Google's Gemini models:

  1. Get an API key from aistudio.google.com
  2. Configure in .fed:
gemini_api_key = ...
gemini_model = gemini-2.0-flash

Option 5: OpenAI

Use OpenAI's GPT models:

  1. Get an API key from platform.openai.com
  2. Configure in .fed:
openai_api_key = sk-...
openai_model = gpt-4o-mini

Provider Selection

By default, FrankMD uses the first configured provider in priority order (OpenAI → Anthropic → Gemini → OpenRouter → Ollama). To force a specific provider:

ai_provider = anthropic

To override the model for any provider:

ai_model = claude-3-opus-20240229

Per-Folder AI Configuration

Important: If you set ANY AI credential in .fed, ALL AI-related environment variables are ignored for that folder. This allows per-folder AI configuration that completely overrides your global ENV settings.

For example, if you have OPENAI_API_KEY and OPENROUTER_API_KEY set as environment variables, but add this to .fed:

anthropic_api_key = sk-ant-your-key

FrankMD will:

  • Use only Anthropic (ignoring OpenAI and OpenRouter from ENV)
  • Pick up changes immediately when you save .fed from the editor

This is useful for:

  • Using different AI providers for different projects
  • Testing new providers without changing your global config
  • Overriding ENV vars set in Docker/shell profiles

Default Models

Provider Default Model
Ollama llama3.2:latest
OpenRouter openai/gpt-4o-mini
Anthropic claude-sonnet-4-20250514
Gemini gemini-2.0-flash
OpenAI gpt-4o-mini

Usage:

  • Click the "AI" button in the toolbar while editing a note
  • Review the side-by-side diff showing original and corrected text
  • Edit the corrected text if needed
  • Click "Accept Changes" to apply corrections

Keyboard Shortcuts

File Operations

Shortcut Action
Ctrl+N New note
Ctrl+S Save now
Ctrl+P Find file by path
Ctrl+Shift+F Search in file contents
Ctrl+F Find in file
Ctrl+H Find and replace
Ctrl+G Go to line

Editor

Shortcut Action
Ctrl+E Toggle sidebar
Ctrl+Shift+V Toggle preview panel
Ctrl+\ Toggle typewriter mode
Ctrl+L Toggle line numbers
Ctrl+Shift++ Increase editor width
Ctrl+Shift+- Decrease editor width
Tab Indent line/block
Shift+Tab Unindent block

Text Formatting

Shortcut Action
Ctrl+B Bold
Ctrl+I Italic
Ctrl+M Open text format menu
Ctrl+Shift+E Emoji picker

Help

Shortcut Action
F1 Open help dialog
Escape Close dialogs

Typewriter Mode

Typewriter mode (Ctrl+\) is designed for focused, distraction-free writing:

Normal mode (default):

  • Explorer panel visible on the left
  • Preview panel available
  • Editor uses normal scrolling

Typewriter mode:

  • Explorer panel hidden
  • Preview panel closed for maximum focus
  • Editor centered horizontally on the screen
  • Cursor stays centered in the middle of the editor (50% viewport height)
  • As you type, the text scrolls to keep your writing position fixed
  • Adjust editor width with Ctrl+Shift++ and Ctrl+Shift+-

Typewriter mode
Typewriter mode: distraction-free writing with centered cursor

This mimics the experience of a typewriter where your typing position stays constant on the page, reducing eye movement and helping maintain focus during long writing sessions.

Hugo Blog Post Support

FrankMD includes built-in support for creating Hugo-compatible blog posts. When you click the "New Note" button (or press Ctrl+N), you can choose between:

  • Empty Document - A plain markdown file
  • Hugo Blog Post - A properly structured Hugo post

New note dialog
New note dialog with Hugo blog post option

Hugo Post Structure

When you create a Hugo blog post with a title like "My Amazing Post Title", FrankMD will:

  1. Create the directory structure: YYYY/MM/DD/my-amazing-post-title/
  2. Create index.md inside with Hugo frontmatter:
---
title: "My Amazing Post Title"
slug: "my-amazing-post-title"
date: 2026-01-30T14:30:00-0300
draft: true
tags:
-
---

Hugo post with frontmatter
Hugo blog post with automatic frontmatter generation

Slug Generation

The slug is automatically generated from the title:

  • Converts to lowercase
  • Replaces accented characters (a→a, e→e, c→c, n→n, etc.)
  • Removes special characters
  • Replaces spaces with hyphens

Examples:

  • "Conexao a Internet" → conexao-a-internet
  • "What's New in 2026?" → whats-new-in-2026
  • "Codigo & Programacao" → codigo-programacao

Hugo YouTube Shortcode

When embedding YouTube videos, FrankMD can insert a Hugo shortcode ({{< youtube >}}) instead of raw HTML. Check the "Use Hugo shortcode" checkbox in the video dialog to enable this.

The inserted shortcode looks like:

{{< youtube id="dQw4w9WgXcQ" title="Video Title" >}}

To use this in your Hugo blog, create the shortcode file at layouts/shortcodes/youtube.html in your Hugo project:

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/{{ .Get "id" }}"
    title="{{ with .Get "title" }}{{ . }}{{ else }}YouTube video player{{ end }}"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

Then add the responsive CSS to your stylesheet (e.g. assets/css/custom.css or your theme's styles):

.embed-container {
  position: relative;
  padding-bottom: 56.25%; /* 16:9 aspect ratio */
  height: 0;
  overflow: hidden;
  max-width: 100%;
}

.embed-container iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

Note: Hugo ships with a built-in youtube shortcode, but it uses a different syntax ({{< youtube dQw4w9WgXcQ >}} with a positional parameter). The custom shortcode above matches the named-parameter format (id="...", title="...") that FrankMD generates, and gives you full control over the markup and styling.

Themes

FrankMD includes 18 built-in color themes, plus automatic Omarchy theme sync:

Theme picker
Theme picker dropdown

Theme Description
Light Clean light theme
Dark Standard dark theme
Catppuccin Soothing pastel dark theme
Catppuccin Latte Soothing pastel light theme
Ethereal Dreamy, soft colors
Everforest Warm green nature theme
Flexoki Light Inky light theme
Gruvbox Retro groove color scheme
Hackerman Matrix-style green on black
Kanagawa Inspired by Katsushika Hokusai's art
Matte Black Pure dark minimal theme
Nord Arctic, north-bluish palette
Osaka Jade Japanese-inspired jade colors
Ristretto Deep coffee tones
Rose Pine All natural pine, faux fur and mystery
Solarized Dark Classic dark color scheme
Solarized Light Classic light color scheme
Tokyo Night Vibrant night theme

Omarchy auto-sync: If you run the Omarchy desktop environment, FrankMD detects your terminal theme and adds it as a selectable "Omarchy" option. Switching your terminal theme updates FrankMD in real time.

Change themes from the dropdown in the top-right corner. Your preference is saved to the .fed file.

Remote Access with Cloudflare Tunnel

For secure remote access without opening ports:

  1. Install cloudflared: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/

  2. Authenticate:

    cloudflared tunnel login
  3. Create a tunnel:

    cloudflared tunnel create frankmd
  4. Add to your docker-compose.yml:

    services:
      frankmd:
        # ... existing config ...
    
      cloudflared:
        image: cloudflare/cloudflared:latest
        container_name: cloudflared
        restart: unless-stopped
        command: tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}
        environment:
          - CLOUDFLARE_TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
        depends_on:
          - frankmd
  5. Configure the tunnel in Cloudflare Zero Trust dashboard to point to http://frankmd:80

  6. Add your tunnel token to .env:

    CLOUDFLARE_TUNNEL_TOKEN=your-tunnel-token
  7. Access via your configured domain (e.g., notes.yourdomain.com)

Security Note: Consider adding Cloudflare Access policies to restrict who can access your notes.

Development

Requirements

  • Ruby 3.4+
  • Node.js 20+ (for Tailwind CSS)
  • Bundler

Setup

# Clone the repository
git clone https://github.com/akitaonrails/FrankMD.git
cd FrankMD

# Install Ruby dependencies
bundle install

# Start development server (includes Tailwind watcher)
bin/dev

Visit http://localhost:3000

Running Tests

# Run ALL checks (lint + security + tests) — same as CI
bin/ci

# Run Ruby tests only
bin/rails test

# Run JavaScript tests only
npx vitest run

# Run specific test file
bin/rails test test/controllers/notes_controller_test.rb

# Run with verbose output
bin/rails test -v

Always run bin/ci before pushing to ensure CI will pass.

Project Structure

app/
├── controllers/
│   ├── notes_controller.rb        # Note CRUD operations
│   ├── folders_controller.rb      # Folder management
│   ├── images_controller.rb       # Image browsing & S3 upload
│   ├── youtube_controller.rb      # YouTube search API
│   ├── ai_controller.rb           # AI grammar checking API
│   ├── config_controller.rb       # .fed configuration
│   └── translations_controller.rb # i18n API for JavaScript
├── models/
│   ├── note.rb                # Note ActiveModel
│   ├── folder.rb              # Folder ActiveModel
│   └── config.rb              # Configuration management
├── services/
│   ├── notes_service.rb           # File system operations
│   ├── images_service.rb          # Image handling & S3
│   ├── ai_service.rb              # AI/LLM integration
│   └── omarchy_theme_service.rb   # Omarchy desktop theme sync
├── javascript/
│   └── controllers/
│       ├── app_controller.js          # Main Stimulus controller
│       ├── theme_controller.js        # Theme management
│       ├── locale_controller.js       # Language/i18n management
│       └── table_editor_controller.js # Table editing
└── views/
    └── notes/
        ├── index.html.erb     # Single-page app
        ├── _header.html.erb   # Top bar with GitHub link
        ├── _sidebar.html.erb  # File explorer
        ├── _editor_panel.html.erb
        ├── _preview_panel.html.erb
        └── dialogs/           # Modal dialogs

Building Docker Image

# Build locally
docker build -t frankmd .

# Run locally
mkdir -p notes
docker run -p 7591:80 -v $(pwd)/notes:/rails/notes frankmd

License

MIT License - see LICENSE for details.

Contributing

Getting Started

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes following the guidelines below
  4. Run bin/ci to verify everything passes
  5. Commit (git commit -m 'Add amazing feature')
  6. Push (git push origin feature/amazing-feature)
  7. Open a Pull Request

PR Requirements

Before submitting a PR, ensure:

  • Rebase from master — always rebase on the latest master before opening a PR. Merge commits are not accepted.
  • bin/ci passes — run it locally. This checks rubocop style, brakeman security scan, bundler-audit, importmap audit, Ruby tests, and JavaScript tests. PRs that fail CI will not be reviewed.
  • Tests included — every new feature or bug fix must include tests. Ruby tests go in test/, JavaScript tests go in test/javascript/. Untested PRs will be sent back.
  • Focused scope — one PR should do one thing. Don't mix unrelated changes (e.g., a new feature + linter fixes + refactoring). If you spot something unrelated to fix, open a separate PR.

Architecture Guidelines

This is a Rails 8 app. Follow Rails 8 idioms and conventions:

  • Turbo Streams for server-rendered updates — file tree updates, CRUD operations on files/folders, and any server-driven DOM update must use Turbo Stream responses. Do not build HTML in JavaScript from JSON API responses.
  • @rails/request.js for fetch calls — use get, post, patch, destroy from @rails/request.js instead of raw fetch(). It handles CSRF tokens and Turbo Stream content negotiation automatically.
  • Stimulus Outlets for controller communication — use Stimulus Outlets (static outlets = [...]) instead of manual querySelector + getControllerForElementAndIdentifier lookups.
  • Config via Config.get() — never read ENV["KEY"] directly in controllers or services. Use Config.new.get("key_name") which respects the .fed file > ENV > default priority chain. See app/models/config.rb for the schema.
  • No sessions — this app is sessionless. Do not use session[] for state. All persistent state goes through the .fed config file.

Code Style

  • Ruby follows the project's .rubocop.yml. Run bin/rubocop -a to auto-fix most issues.
  • JavaScript has no linter configured, but follow the existing patterns: ES module imports, Stimulus controller conventions, no semicolons.
  • Keep changes minimal. Don't add extra error handling, comments, or abstractions beyond what's needed for the task.

Stats

Memory Footprint

Component Memory
Rails container (Puma + Thruster) ~115 MiB
Browser tab (Brave/Chrome) ~340 MB
Total ~455 MB

Codebase (from bin/rails stats)

Type Lines LOC
JavaScript 13,793 10,081
Views (ERB) 2,953 2,633
Models 904 706
Controllers 850 652
Total source ~18,500 ~14,100

Test Coverage

Type Tests
JavaScript (Vitest) 1,379
Ruby (Minitest) 425
Total 1,804

About

Self-hosted, Simple Markdown Web Editor and Notes Organizer

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • JavaScript 61.4%
  • Ruby 20.7%
  • HTML 14.0%
  • CSS 3.0%
  • Other 0.9%