# Spine — initial scaffold A minimal working Emacs web server that serves templated HTML. This is the first concrete slice of [spine-spec.md](../spine-spec.md), resolving the open "glue language + web framework" decision: Emacs owns the HTTP socket and serves the UI directly. ## Architecture decision **Emacs end-to-end.** No external web process, no `emacsclient` IPC for reads. The app is a single Emacs instance running `simple-httpd`. This keeps the build simple (one language, two packages) and is adequate for Spine's complexity level. ## Packages | Package | Version | Source | Purpose | |---------|---------|--------|---------| | `simple-httpd` | MELPA latest | MELPA | HTTP server, `defservlet` routing | | `mustache` | MELPA latest | MELPA | Mustache template rendering | | `ht` | MELPA latest | MELPA | Hash table for template context | ## Project structure ``` spine/ ├── scripts/ │ ├── run # start the server as a daemon │ └── stop # stop the daemon ├── spine.el # entry point: bootstrap, server start, handlers ├── templates/ │ └── hello.mustache # smoke-test template ├── spine-spec.md # design brief (existing) ├── spine-mockup-a-agenda.html ├── spine-mockup-b-journal.html └── README.md ``` - `spine.el` is the sole Elisp file for now. Split later when a seam earns it. - Templates live in `templates/` as `.mustache` files. - Mockups stay as reference artifacts; they are not served. ## Package bootstrap `spine.el` uses `package.el` (built-in). On first run it installs missing packages from MELPA; subsequent starts skip the install step. ``` (require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (package-initialize) (dolist (pkg '(simple-httpd mustache ht)) (unless (package-installed-p pkg) (package-refresh-contents) (package-install pkg))) ``` `package-refresh-contents` runs only when a package is missing — not on every start. ## Server startup ``` (defvar spine-port 8080 "Port for the Spine HTTP server.") (defvar spine-host nil "Host for the Spine HTTP server. nil means localhost only. Set to a string like \"0.0.0.0\" to listen on all interfaces.") (defvar spine-template-dir (expand-file-name "templates/" (file-name-directory load-file-name)) "Directory containing .mustache templates.") (setq httpd-host spine-host) (setq httpd-port spine-port) (httpd-start) (message "Spine listening on http://%s:%d" (or spine-host "localhost") spine-port) ``` - Port defaults to 8080, overridable via `spine-port`. - Host defaults to nil (localhost-only), overridable via `spine-host`. - `spine-template-dir` resolves relative to `spine.el`'s directory so the app works regardless of `default-directory`. ## Template rendering A single helper wraps Mustache rendering: ``` (defun spine-render (name context) "Render templates/NAME.mustache with CONTEXT (an ht hash table). Returns the rendered string." (mustache-render (with-temp-buffer (insert-file-contents (expand-file-name name spine-template-dir)) (buffer-string)) context)) ``` - Reads the template file from disk on every request. No caching — correct and simple; add caching if profiling shows a need. - `context` is an `ht` hash table whose keys map to `{{key}}` placeholders. ## Hello World handler ``` (defservlet hello text/html () (insert (spine-render "hello.mustache" (ht ("title" "Spine") ("message" "Hello from Emacs."))))) ``` `templates/hello.mustache`: ```html {{title}}

{{message}}

``` - Accessible at `http://localhost:8080/hello`. - The root `/` is unbound and will show `simple-httpd`'s default directory listing — acceptable for a smoke test. ## Invocation Production use — Emacs daemon with a named socket, no GUI frame: ```bash scripts/run [HOST:]PORT ``` - `scripts/run 9090` → `http://localhost:9090/hello` Stop: `scripts/stop` The socket name `spine` is separate from the default Emacs daemon socket, so your personal daemon is unaffected. Development use — interactive Emacs in the terminal: ```bash emacs --quick --load spine.el ``` `--quick` (aliases: `-Q`) skips init files for a clean, repeatable start. ## Acceptance - [ ] `emacs --quick --load spine.el` starts without errors. - [ ] Browsing `http://localhost:8080/hello` shows "Hello from Emacs." - [ ] Templates load relative to `spine.el`'s directory, not `default-directory`. - [ ] Second invocation skips package install (packages already present). ## Out of scope - Book data, Org file integration, any handler beyond `/hello`. - Static file serving (CSS, JS, icons). - Tests — the scaffold is small enough to smoke-test manually.