Maintenance

For developers

A brief appendix for self-builders — Enova's build commands, tech stack, file map, the 62-locale i18n workflow, and the stable data-* hook contract.

This page is the short reference for developers customizing Enova from source. Most site editors won't need anything here — uploading the pre-built enova.zip is enough. If you want to fork the theme, change styles at the source level, or rebuild after edits, read on.

Custom code is overwritten on theme update

If you modify theme files directly and then upload an updated enova.zip through Ghost Admin, your changes are erased. Fork the repo, commit your changes to your fork, and rebuild your own zip — don't edit files in place on a production install. For small CSS or JavaScript tweaks, prefer Ghost's Settings → Code injection, which survives theme updates — see Code injection for the stable selectors Enova exposes for exactly this.


Build requirements

  • Node.js v22.12.0 or later
  • Yarn (any recent v1.x)
  • Ghost v6.0.0 or later for the running site that will host the built theme

Tech stack

Enova is built with Vite (bundling), Tailwind CSS v4 (styling, with the typography plugin for article content), and Alpine.js (interactivity — sidebars, preferences, the mobile menu). Templates are standard Ghost Handlebars.


Build commands

All commands run from the theme root.

CommandWhat it does
yarn installInstall dependencies.
yarn devVite dev server with HMR. Useful when iterating on CSS or JS in a local Ghost install pointed at the theme directory.
yarn buildProduction build to assets/built/.
yarn lintRuns lint:i18n (translation-parity check) then lint:theme (GScan compliance against Ghost's theme-API expectations).
yarn testAlias for yarn lint.
yarn zipRun test + build, then package enova.zip for upload. Excludes node_modules, .git, demo templates, scripts, and other source-only files.

File map

The repo's root templates and key partials at a glance:

Templates (root)

FilePurpose
default.hbsMaster layout — <html> state attributes (data-color-scheme, data-navigation-style), the two sidebar shells, header/footer includes.
index.hbsHomepage: hero (four layouts) + tabbed home feed.
post.hbs / page.hbsArticle and static-page templates.
tag.hbs, author.hbsTag / author archives with cover headers.
newsletter-archive.hbsThe /newsletters/ collection template (requires routes.yaml, below).
custom-full-width.hbs, custom-left-sidebar.hbs, custom-right-sidebar.hbsThe three selectable custom templates.
error.hbs, error-404.hbsError pages.

Key partials

PartialPurpose
partials/header.hbsSticky header: masthead, logo (light/dark), auth links, preferences.
partials/sidebar-left.hbs / partials/sidebar-right.hbsThe two sidebars; the right one hosts the whole widget stack.
partials/sidebar-donate.hbs, partials/sidebar-sponsors.hbs, partials/sidebar-house-ad.hbsThe presence-gated monetization widgets (driven by pages slugged donate / sponsors / house-ad).
partials/hero-*.hbsThe four homepage hero layouts.
partials/home-feed.hbsTabbed homepage feed (tag tabs from the home_feed_tags setting).
partials/post-card.hbsReusable feed card, grid and list variants.
partials/content-post.hbs + partials/post/Post hero dispatch (#video / split / cinematic / default) and post-page pieces.
partials/newsletter-archive/The five archive card layouts, hero, and year rail.
partials/footer.hbsFooter brand, nav columns, newsletter form.

Assets

PathPurpose
assets/css/index.cssTailwind v4 entry, design tokens, typography. Feature styles split into cards.css, sidebar.css, video.css, donate.css, sponsors.css, dropcap.css, code.css.
assets/js/index.jsAlpine.js entry — stores, sidebar state, bookmarks, preferences.
assets/js/navigation.js, header-nav.jsSidebar nav build (dash-prefixed sub-items, # group headers) and top-bar mode.
assets/js/video.jsThe #video hero + docked mini-player (YouTube, Vimeo, uploaded video).
assets/js/social-links.js, nav-icons.jsThe meta-tag-driven social links and nav-icon systems.
vite.config.jsVite build config.

routes.yaml

Enova ships a routes.yaml at the repo root (and inside enova.zip). It defines the two collections — / (everything except the newsletter tag) and /newsletters/ (only the newsletter tag) — plus the standard tag/author taxonomies. If you fork and change routing, keep the docs' copy in mind: the file is published verbatim in the installation guide.


i18n workflow

Enova ships 62 locales in locales/, and the yarn lint:i18n parity check is enforced — never add a {{t "..."}} call to a template without also adding the matching key to locales/en.json.

The mechanics

  1. scripts/check-i18n.mjs walks every *.hbs file in the theme.
  2. It extracts every {{t "..."}} and subexpression (t "...") call.
  3. It diffs the extracted key set against locales/en.json.
  4. It fails the build on any drift — missing key, orphan key, mismatched value.

The discipline

  • Add a translation key first, then reference it in the template.
  • Use Ghost's interpolation syntax ({{t "Members of {site}" [email protected]}}) instead of string concatenation, so word order is correct in non-English locales.
  • Never hardcode user-visible English in a template.

See also: Publication Language for the reader-facing side of the same system, including the full locale list and RTL support.


The data-* hook contract

Enova's templates carry a documented styling API — the data-region / data-component / data-variant / data-part attributes. If you fork the theme, treat these as public contract: site owners build Code-injection CSS against them, and renaming or removing one breaks that CSS. Add new hooks freely (and document them), but keep existing names stable. The full vocabulary: Hook reference.