Skip to content
jennifer-languagePublic

About

Jennifer's Website

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Jennifer

The Jennifer website

The public entrance to the Jennifer programming language - the site served at www.jennifer-lang.dev, pointing on to the documentation, the deck registry, Grimoire and the source.

It is written in Jennifer, all the way down:

  • httpd is the server - a pull loop with a pool of spawned workers and a drained shutdown.
  • tengine renders the pages from a base layout, a set of partials and one page template each.
  • src/highlight.j colors every code sample - the Jennifer language, highlighted by a Jennifer program.

No JavaScript framework, no CDN, no build step, and nothing fetched at run time. The two fonts are self-hosted; the JavaScript is one nine-line script that applies a stored theme before the first paint, plus progressive enhancement the site works fine without.

Run it

./bin/serve                     # http://127.0.0.1:8080/
./bin/serve --addr :80          # every interface, port 80
./bin/serve --dev               # re-read content and templates on every request
./bin/serve --help
Flag Default What it does
--addr, -a 127.0.0.1:8080 host:port, :port, or unix:/path/to.sock
--workers, -w 4 accept loops to spawn; each pulls from the same queue
--dev, -d off re-render content and templates per request
--root, -r the directory above bin/ project root, if you start it from elsewhere

Pages are rendered once at start-up and answered from memory; public/ is handed to the engine's static file server. SIGINT / SIGTERM are trapped, so the server drains in-flight requests before the process exits.

Layout

bin/
  serve             the server (executable, shebang)
  healthcheck       container health probe, also Jennifer
src/
  server.j          routes, headers, worker pool, shutdown
  content.j         content/ -> one json.Value tree
  redirects.j       the manual's old URLs -> the docs host (301)
  downloads.j       /download/<alias> -> the newest release's asset (302)
  render.j          tengine set assembly
  highlight.j       Jennifer syntax highlighting (vendored, see below)
content/
  site.json         every word on the site, and every link
  backdrop.j        the program in the tilted editor behind every page
  examples/*.j      the code samples - real programs
  examples/*.out    the output each one actually produced
.github/workflows/
  build.yml         check with the shipped interpreter, then build and push
docker-compose.yml  the whole VPS deployment
templates/
  base.html         the document skeleton
  partials.html     header, footer, code card
  home.html  install.html  showcase.html  projects.html  notfound.html
public/
  css/site.css      the whole design, both themes
  js/theme.js       applies a stored theme before the first paint (blocking)
  js/site.js        theme switch, copy buttons, scroll reveals (deferred)
  fonts/  img/  favicon.*  robots.txt  site.webmanifest
  .well-known/security.txt

Routes

Every page lives under a language base. English is the only language so far.

Path What
/ negotiates Accept-Language and 302s into a language (/en/)
/en/ the landing page
/en/install install instructions per platform
/en/showcase all ten code samples
/en/projects featured projects written in Jennifer
/install, /showcase the flat URLs the site was born with - 301 to /en/...
/index.html this domain's root document - 301 to /, which then negotiates
/libraries/*.html, /modules/*.html, /user-guide/*.html, /technical/*.html, /design-stances.html, ... the manual, which used to live here - 301 to docs.jennifer-lang.dev
/download, /download/<alias> 302 to the newest release, or to that release's asset
/healthz ok, for a load balancer or the container health check
/sitemap.xml generated from the route table, one entry per language and page
anything under public/ served by httpd.serveDir
anything else the 404 page

The manual moved off this domain. It was served from here before docs.jennifer-lang.dev existed, so every path it published - 157 of them, plus the directory URLs - is listed in src/redirects.j and answered with a 301. The one exception is /index.html: it was the manual's front page, but it is also this domain's root document, so it stays here and goes to /. That is the status that carries a page's search standing to its new address instead of leaving the link dead. The map is old path -> path on the docs host, so a page that was later renamed can be re-pointed by editing its line, and the four documentation directories are also matched by prefix, so a page the manual grows after this snapshot moves too. Regenerate the list from the manual's own tree (docs/**/*.md -> .html) when it changes; jennifer test src/redirects_test.j checks the shapes and that no target points back here.

Caching is content-addressed. Two things carry a build's identity. The stylesheet and the scripts are linked with a digest of themselves (/css/site.css?v=46badca40e), so a deploy that changes the design changes the URL, and a reader holding an hour-old copy stops holding it - a container restart alone cannot do that, because max-age freshness means the browser never asks. And every answer carries an ETag over its own bytes. A page is hashed when it is rendered at start-up and handed to httpd.etag, which sets the validator and answers a matching If-None-Match with an empty 304; static files go out through httpd.serveDirEtag, which hashes the file itself and caches the digest by mtime and size. Because a tag is the content, two containers built from the same commit agree, so a reader who lands on another replica revalidates instead of downloading again - which the Last-Modified those files also carry cannot do. --dev sends no-store instead, so an edit shows up on reload.

Downloads follow the releases. Every release asset carries its version in the filename, so GitHub's own /releases/latest/download/<name> shortcut cannot be used - the name is not stable. src/downloads.j supplies the missing indirection: /download/windows-setup, /download/linux-amd64, /download/deb-arm64, /download/openwrt-aarch64 and seven more resolve the newest tag from the GitHub API, cache it for half an hour in a kv store the server opens and every worker shares, and 302 to that release's asset. Bare /download - and every failure, from no network to a rate limit - lands on the releases page instead, so a download link here is never dead, only sometimes one click longer. src/downloads_test.j pins the asset names against what release.yml actually uploads.

Adding a language is a LANGUAGES entry in src/server.j and a translated copy of content/site.json; nothing else has to move. Routing, hreflang, the sitemap and Accept-Language negotiation all read that list. Internal links are written language-free in the content (/install), and content.localize rewrites them onto the active base (/en/install) at render time, so neither the templates nor the content ever spell a language out.

Editing the site

Words and links live in content/site.json - nothing is hardcoded in a template. Change a heading, a card, a nav entry or a URL there and restart (or run with --dev).

Code samples are real .j files in content/examples/. They are linted and run before they are published, and the .out file beside each one is the output it actually produced:

jennifer lint content/examples/*.j
jennifer run content/examples/01-hello.j > content/examples/01-hello.out

The SPDX header and module docblock every file carries are stripped on the way to the page, so a sample shows only its code.

The background is content/backdrop.j - a real, lint-clean Jennifer program - run through the same highlighter as the samples and set into an editor window that is tilted away in 3D (.backdrop in the stylesheet). It is aria-hidden, inert to the pointer, and sits under a scrim that keeps the text column calm, so it reads in the margins and the gaps and nowhere else. Swap the file and the whole background changes.

Themes. The system preference is the default: with no choice stored, the stylesheet follows prefers-color-scheme on its own. The switch in the header is a plain light/dark toggle, and the choice is written to a jennifer-theme cookie (a year, SameSite=Lax), so a reader who picked one gets it back on the next visit. theme.js re-applies it before the first paint, so there is no flash of the wrong theme; with JavaScript off the switch is hidden and the system preference stands. Both palettes are token sets on :root - the light one is declared twice on purpose, once for the media query and once for the explicit [data-theme="light"], because CSS cannot merge the two into one rule.

Design is one stylesheet, public/css/site.css. The palette is two accents on a near-neutral dark ground with a faint green cast: ember orange (--ember) carries structure and every interaction - section indices, headline accents, links, buttons, keywords - and moss green (--moss) carries the metadata layer: stat labels, hostnames, shell prompts, program output, strings. Everything else is hairline rules and two self-hosted variable fonts (SIL OFL 1.1): Archivo - an engineered grotesque whose width axis separates the display role (expanded) from the text role (normal) - and Spline Sans Mono for code, labels and navigation. Change the two accent tokens in :root and the whole site follows; change --font-display / --font-body for another technical sans and nothing else has to move.

Container

docker build -t jennifer-website .
docker run --rm -p 8080:8080 jennifer-website

The base image is ghcr.io/jennifer-language/jennifer:0.25, so the image is the interpreter plus this repository - no other runtime. The build lints every .j file with the interpreter that will run them, and the HEALTHCHECK is itself a Jennifer program (bin/healthcheck), so the image needs no curl or shell to probe itself.

Deploy

.github/workflows/build.yml checks the site with the interpreter it ships with - formatting, lint, and a diff proving every published transcript is still what the sample prints - then builds and pushes a multi-arch image to ghcr.io/<owner>/<repo> and smoke-tests it. latest follows main; every push also gets an immutable sha-xxxxxxx tag, and a version tag publishes X.Y.Z and X.Y.

On the server, docker-compose.yml is the whole deployment - copy that one file across and:

docker compose pull
docker compose up -d

It publishes on 127.0.0.1:8080 only, so the host's reverse proxy owns TLS and the public port (nginx and Caddy snippets are in the file). The container runs with a read-only root filesystem, every capability dropped, no-new-privileges, a 256 MB cap and rotated logs. Updating is docker compose pull && docker compose up -d; pin an exact build by putting TAG=sha-abc1234 in a .env beside the compose file.

Behind nginx

upstream jennifer_site { server unix:/run/jennifer/site.sock; }

server {
    listen 443 ssl;
    server_name www.jennifer-lang.dev;
    location / {
        proxy_pass http://jennifer_site;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
    }
}
./bin/serve --addr unix:/run/jennifer/site.sock

Notes

  • Domains. Every external URL is in content/site.json under links. The documentation is linked as docs.jennifer-lang.dev; if the docs keep a different hostname, that one file is the only place to change it.
  • src/highlight.j is vendored from Grimoire (src/highlight.j there), byte-for-byte, so a fix upstream is a straight copy back. It emits highlight.js class names (hljs-keyword, hljs-string, ...), which public/css/site.css themes.
  • JENNIFER.md is the language reference for AI assistants working on the site. It is optional - the image copies *.md with a glob precisely so a checkout without it still builds - but committing it is worth it: an assistant pointed at it writes correct .j from the first line. Keep it in sync with the installed interpreter; the marker on its last line records which version it matches.
  • Response headers are all set in one place, commonHeaders in src/server.j - the bare-engine equivalent of a web.before middleware, since every answer passes through it. It stamps the security headers (CSP, nosniff, referrer policy; the CSP is 'self'-only, which is why nothing here loads from a CDN) and advertises the stack: Server: Jennifer/<version> and X-Powered-By: Jennifer httpd + tengine. The httpd engine sends no Server header of its own by design - this site opts in, because it is the language's own front door.

License

LGPL-3.0-only, matching the language. The logo files under public/img/ are the project's own marks; the fonts under public/fonts/ are SIL OFL 1.1.

About

Jennifer's Website

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors