Show HN: Simple org-mode web adapter

Published: (February 16, 2026 at 11:19 AM EST)
4 min read

Source: Hacker News

Org Web Adapter

A lightweight local web app for browsing and editing Org files.

The app is implemented as a single Python server (main.py) plus one HTML template (templates/index.html) and one stylesheet (static/style.css). It scans a notes directory for .org files and renders a three‑pane UI.

⚠️ There is no authentication or encryption; only run this service on trusted networks. ⚠️

Screenshots

Desktop

Desktop view

Mobile

Mobile view

Instructions

  • Symlink your notes directory to notes.
  • Edit the bind address and port in config.yaml if desired.
  • Run the server:
python3 main.py

How it works

  • main.py starts an HTTP server.
  • On each page request (GET /), it rescans the notes directory for .org files.
  • It resolves links/backlinks and builds HTML fragments.
  • The fragments are injected into templates/index.html placeholders:
    • {{NAV_ITEMS}}
    • {{MAIN_CONTENT}}
    • {{BACKLINKS}}
  • Browser JavaScript in templates/index.html handles client‑side interactions (search, shuffle, sorting, jump‑to‑current, theme toggle).

Server‑side components (main.py)

File discovery and parsing

  • scan_org_files(...) recursively finds .org files.
  • Extracts title from #+TITLE: and ID from :ID:.
  • resolve_link_target(...) supports file:... and id:... links.
  • find_backlinks(...) computes notes linking to the selected note.
  • build_backlink_counts(...) computes backlink totals for sorting.

Rendering

  • render_org_to_html(...) converts headings (*, **, …) and paragraphs to simple HTML.
  • render_line_with_links(...) converts Org links in text to clickable app links where resolvable.
  • truncate_label(...) caps sidebar labels to 32 characters with .

Editing

  • POST /edit updates a selected .org file and redirects back with status flags.

Static files

  • serve_static(...) serves files under static/ with path‑traversal protection.

Frontend components

templates/index.html

  • Base layout markup.
  • Sidebar controls.
  • Small JavaScript controller for filtering, sorting, and shuffling navigation links.
  • MathJax initialization for inline $...$ rendering.

static/style.css

  • Three‑column desktop grid and stacked mobile layout.
  • Independent scroll regions for note list and backlinks.
  • Mobile note‑list cap (about five notes visible before scrolling).

Configuration

Startup configuration is read from config.yaml by default.

  • bind_addr: host/IP to bind.
  • bind_port: TCP port (1..65535).

If config.yaml is missing, defaults are 127.0.0.1:8000.

CLI flags --host and --port override config values, and --config /path/to/config.yaml lets you specify an alternate file.

Useful run commands

python3 main.py --dir notes
python3 main.py --host 127.0.0.1 --port 9000
python3 main.py --config ./config.yaml
python3 main.py --no-browser

Features

Note browsing

  • Recursive .org file discovery.
  • Sidebar note list with active‑note highlighting.
  • Title/path search filter.
  • Shuffle notes.
  • Sort by backlink count (descending).
  • Sort by created date (ascending). Notes without timestamps are treated as older than notes with timestamps.
  • Jump to current note button.
  • Right sidebar lists notes linking to the current note.
  • Supports both file: and id: link resolution.

Editing

  • Toggle Preview/Edit for the selected note.
  • Save changes from the browser to disk.
  • Inline status messages for save success or errors.

Math rendering

  • Inline math rendering via MathJax using $...$ delimiters.

UI behavior

  • Light/dark theme toggle (persisted in localStorage).
  • Desktop: independent scrollable sidebars.
  • Mobile: notes list capped with its own scroll area.
  • Sidebar text truncation with ellipsis and tooltip for full text.

Project layout

  • main.py: server, parsing, rendering, routing.
  • templates/index.html: page template + UI behavior JavaScript.
  • static/style.css: styling and responsive layout.
  • config.yaml: bind configuration.
  • notes/: notes directory (can be a symlink).
  • old_notes/: alternate local notes snapshot.

Limitations

  • Not a full Org parser; rendering is intentionally simple.
  • Notes are rescanned on each request (simple and fresh, but not optimized for huge note sets).
  • Math rendering depends on loading MathJax from a CDN.
0 views
Back to Blog

Related posts

Read more »

Mastodon is down. now what?

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as we...