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:
| Widget | What it does | How you use it |
|---|---|---|
| InvoiceStatus | Shows a colored status tag (Paid, Overdue, etc.) | {{> InvoiceStatus}} |
| DemoBadge | Shows a "Demo" watermark | {{> DemoBadge}} |
| DateTime | Formats dates with timezone support | {{formateShortDateWithOffset date offset}} |
| MarkdownViewer | Renders 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
| File | What it does |
|---|---|
index.html | The entry point. Loads main-manifest.json and bootstraps the renderer |
src/main/index.ts | The main renderer. Orchestrates loading and rendering |
src/main/commonUtils.ts | Utility functions (URL decoding, CSS loading, font loading, style application) |
src/main/lydiaBridge.ts | Communication layer with Lydia (height reporting, print handling) |
src/main/dibellaBridge.ts | Communication layer with Dibella (currently a placeholder) |
src/global.d.ts | TypeScript type declarations for HBS, CSS, and global window types |
webpack.config.js | Build 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.