Welcome to Chrono-Org, an elegant, pure-text workflow that bridges the gap between Emacs Org-mode's unparalleled time-tracking capabilities and modern, interactive web dashboards.
Built with the K.I.S.S. (Keep It Simple, Stupid) and Suckless philosophies in mind, this project treats Org-mode as the absolute source of truth. It extracts your logged hours using Emacs Lisp, generates static JSON data, renders beautiful Chart.js dashboards in the browser, and publishes everything securely via org-publish and Apache .htaccess rules.
The ecosystem relies on a closed, private, and lightweight architecture divided into four main engines:
-
Source of Truth (Org-mode as Database): The base is purely textual, segmented by context (e.g.,
personal.org,client1.org,client2.org). Each file manages its own lifecycle (TODO, INICIADO, DONE), properties, and precise time tracking viaLOGBOOKandCLOCK. -
Extraction Engine (The Lisp ETL): A silent Emacs Lisp routine acts as an Extract, Transform, Load (ETL) pipeline. It recursively scans the
.orgfiles, parses the task trees, sums the minutes in theLOGBOOKs, filters by status/tags, and outputs consolidated.jsonfiles into anassets/data/directory. -
Visualization Engine (Frontend JS): Inside each
.orgfile, literal HTML blocks (#+BEGIN_EXPORT html) inject<canvas>structures. Local JavaScript engines (dashboard-local.js,dashboard-master.js) run entirely client-side on the browser, consuming the generated JSONs to render interactive metrics (Timelines, Donuts, Top Tasks) based ondata-contextanddata-monthattributes. -
Publishing & Security Engine (org-publish + Apache): The
org-publishprocess orchestrates the final assembly. It transpiles the raw Org text to HTML preserving the directory tree, and pushes static assets to the production server. Directory-level access control is enforced via.htaccessfiles, dynamically injected into the publish configuration to keep client names private in public repositories.
www/
├── assets/
│ ├── css/
│ │ └── style.css
│ ├── data/ <-- Generated by the Lisp Extraction Engine
│ │ ├── global-dashboard-2026-03.json
│ │ └── index-2026-03-summary.json
│ └── js/
│ ├── chart.js
│ ├── dashboard-local.js
│ └── dashboard-master.js
├── index.org
├── .htaccess <-- Protects index but allows assets/
├── personal/
│ ├── personal.org
│ ├── .htaccess
│ ├── client3/
│ │ ├── client3.org
│ │ └── .htaccess
│ └── client4/
│ ├── client4.org
│ └── .htaccess
├── client1/
│ ├── client1.org
│ └── .htaccess <-- Distinct htpasswd basic auth
└── client2/
├── client2.org
└── .htaccess
To orchestrate the workflow, you need to set up your agenda files, load the extraction engine, and configure the publishing parameters. Here is the canonical configuration:
;; 1. Map Org-mode Agenda Files (Recursive search)
(setq org-agenda-files
(directory-files-recursively "~/Trabalho/Agenda/" "\\.org$"))
;;------------------------------------------
;; chrono-org :: Online Agenda Publisher
;;------------------------------------------
;; 2. Load the Extraction Engine (Lisp to JSON)
(load "~/src/chrono-org/publish/extraction-engine.el")
;; Define where the engine should look for data and the main context
(setq clock2json/agenda-files
(directory-files-recursively "~/Trabalho/Agenda/" "\\.org$"))
(setq clock2json/agenda-main-context "personal")
;; 3. Load the Publishing Engine (org-publish wrapper)
(load "~/src/chrono-org/publish/chrono-org-publish.el")
;; 4. Define Security Files (.htaccess)
;; We use a variable so we don't hardcode client names in the org-publish-project-alist.
;; This list will be evaluated during the publish process using a backquote (`) injection.
(setq cop/agenda-security-files
'(".htaccess"
"assets/.htaccess"
"personal/.htaccess"
"client1/.htaccess"
"client2/.htaccess"))
;;-----------------------------------------(Note: Ensure your org-publish-project-alist within chrono-org-publish.el uses backquotes ` and unquotes , to evaluate the cop/agenda-security-files into the :include property of the static components. This bypasses the default org-publish behavior of ignoring hidden files).
Because you are likely sharing specific directories with specific clients (e.g., client1 sees only client1.org's output), we rely on Apache's htpasswd and .htaccess.
-
Create outside-of-webroot password files:
sudo htpasswd -c /etc/httpd/.passwd-agenda-personal my_user sudo htpasswd -c /etc/httpd/.passwd-agenda-client1 client1_user
-
Configure Directory
.htaccess: Place an.htaccessinside each specific directory. Example forclient1/.htaccess:AuthType Basic AuthName "Client Portal - Client 1" AuthUserFile /etc/httpd/.passwd-agenda-client1 Require valid-user
-
Public Assets Exception: To ensure charts render correctly, your
assets/.htaccessmust override parent locks:Require all granted Satisfy Any
-
Clock In / Clock Out: Start your day. Navigate to your tasks in your
.orgfiles and use standardC-c C-x C-i(clock-in) andC-c C-x C-o(clock-out). -
Execute the Extraction Engine: Run the loaded Elisp extraction function (e.g.,
M-x clock2json/extract-all). This will quietly parse allLOGBOOKentries and generate the contextual JSON files insideassets/data/. -
Publish to Web: Run the org-publish command (
C-c C-e P p). Emacs will transpile the.orgfiles to.html, copy the.htaccessfiles (maintaining security boundaries), and push the updated JSONs to your server. -
Review: Navigate to your Apache-hosted URL. The embedded JavaScript will fetch the new JSONs and instantly update your client-facing dashboards.
The beauty of this workflow is its modularity.
- Want a new chart type? Just edit
dashboard-local.jsand push. - Got a new client? Add a directory, create an
.htaccess, drop in an.orgfile with an HTML export block, update yourcop/agenda-security-filesvariable, and publish.
No complex Node.js build pipelines, no heavy databases. Just pure text, robust Lisp, and standard web technologies.
This project was built to showcase the power of Emacs as a complete lifecycle management tool. If you adapt this workflow, feel free to share your dashboards and Lisp tweaks on the r/emacs subreddit!
