Skip to main content

Architecture Overview

Ceres has three main parts. Each one does a specific job.

The three parts

1. Main Renderer

This is the brain. It lives in src/main/ and does three things:

  • Reads the URL to figure out which template to load and where to get the data
  • Loads the template's JavaScript and CSS files
  • Fetches the data from the API and passes it to the template

The entry point is src/main/index.ts. When the page loads, it calls renderDocument(), which kicks off everything.

2. Templates

Templates live in src/templates/. Each template is a folder with its own HTML (Handlebars), CSS, and a small TypeScript file that wires things together.

For example, basic-invoice-example looks like this:

src/templates/basic-invoice-example/
index.ts # Imports the HBS template, CSS, and widgets
template.hbs # The actual HTML layout
styles.css # Styling
version.json # {"version": "1.0.101"}
samples.json # Test API URLs
thumbnail.png # Preview image

When a template is built, it gets compiled into a JavaScript bundle. That bundle registers a function called window.CeresTemplate that the main renderer calls with the API data.

3. Widgets

Widgets live in src/widgets/. They are small, reusable pieces that any template can use. Think of them as building blocks.

Each widget registers itself as a Handlebars partial (a reusable chunk of HTML). Some also register helpers (functions you can call from your template).

Currently there are four widgets:

WidgetWhat it doesHow you use it
InvoiceStatusShows a colored status tag (Paid, Overdue, etc.){{> InvoiceStatus}}
DemoBadgeShows a "Demo" watermark{{> DemoBadge}}
DateTimeFormats dates with timezone support{{formateShortDateWithOffset date offset}}
MarkdownViewerRenders markdown text as HTML{{> MarkdownViewer (prepareMarkdownViewerData text)}}

How they connect

Here is what happens when someone opens a Ceres document:

1. Browser loads index.html
|
2. index.html fetches main-manifest.json
|
3. Loads the main renderer JS bundle
|
4. Main renderer reads ?template=xxx&apiUrl=yyy from the URL
|
5. Fetches the template's manifest.json
|
6. Loads the template's JS and CSS bundles
|
7. Template JS registers window.CeresTemplate
|
8. Main renderer fetches data from the API URL
|
9. Calls window.CeresTemplate(data) to get HTML
|
10. Puts the HTML into the page

Key files at a glance

FileWhat it does
index.htmlThe entry point. Loads main-manifest.json and bootstraps the renderer
src/main/index.tsThe main renderer. Orchestrates loading and rendering
src/main/commonUtils.tsUtility functions (URL decoding, CSS loading, font loading, style application)
src/main/lydiaBridge.tsCommunication layer with Lydia (height reporting, print handling)
src/main/dibellaBridge.tsCommunication layer with Dibella (currently a placeholder)
src/global.d.tsTypeScript type declarations for HBS, CSS, and global window types
webpack.config.jsBuild configuration. Discovers templates and widgets, handles versioning

System templates vs custom templates

This is an important distinction:

System templates are React components that live in the Lydia codebase (for example, lydia/src/components/template/quotation/default.js). They have access to all of Lydia's React infrastructure and they handle most users.

Custom templates are Handlebars/CSS templates that live in the Ceres codebase. They run inside an iframe. They are for users who need a custom look that the system templates do not support.

Lydia decides which one to use. If the user has a custom layout template applied, Lydia creates an iframe pointing to Ceres. If not, Lydia renders the document with its own React components.