How Ceres Talks to Lydia
Ceres runs inside an iframe in Lydia. They communicate through postMessage. This page explains every message they send to each other, and which files handle what.
The big picture
Lydia (React app, runs in the main browser window)
|
| Creates an iframe pointing to Ceres
|
v
Ceres (runs inside the iframe)
|
| Renders the document template
| Sends height updates back to Lydia
|
v
Lydia resizes the iframe to fit
How Lydia loads Ceres
When a user has a custom template, Lydia builds an iframe URL and drops it on the page. Here is how it works:
The URL builder
In Lydia, the file src/components/utils/iframeUtils.js has a function called buildIframeSrc. It does three things:
- Takes the document ID and share token
- Builds the API URL and base64-encodes it
- Combines everything into a Ceres URL
// Simplified version of what buildIframeSrc does:
const API_URL = `${apiDomain}/invoices/${invoiceId}?_at=${shareId}&populateBusiness=true`;
const iframeSrc = `${ceresDomain}?template=${templateName}&apiUrl=${btoa(API_URL)}&isLydiaMode=1`;
The isLydiaMode=1 parameter tells Ceres to activate the Lydia Bridge, which handles height reporting and print commands.
The iframe component
Lydia renders the iframe using IframeRenderer.jsx:
<StyledIframe
ref={iframeRef}
id="invoiceIframe"
src={iframeSrc}
width="100%"
title="Document Preview"
/>
The height hook
Lydia listens for height messages using the useIframeHeight hook:
// Listens for messages from Ceres
window.addEventListener('message', handleMessage);
// When Ceres says "my content is 1200px tall":
// Lydia sets the iframe height to 1200px + a small buffer
Messages from Lydia to Ceres
Lydia sends three types of messages to Ceres:
1. Print command
When the user clicks "Print" or presses Ctrl+P:
iframeContentWindow.postMessage({
action: 'lydia:print',
reason: 'lydia-print-button'
}, '*');
Ceres receives this, prepares the document for printing (adjusts sizing, forces overflow visible), waits two animation frames, then calls window.print().
2. Height request
When Lydia wants to know the current height:
iframeContentWindow.postMessage({
action: 'lydia:height-request',
reason: 'template-update'
}, '*');
Ceres measures the content and sends back a ceres:content-height message.
3. Template style update
When the user changes template styles (colors, fonts, logo) in the Lydia UI:
iframeContentWindow.postMessage({
source: 'lydia',
type: 'lydia:template-update',
reason: 'color-change',
template: {
primaryColor: { r: 46, g: 133, b: 85 },
fontFamily: 'Inter',
// ... more style options
}
}, '*');
Ceres receives this, applies the styles as CSS custom properties, re-renders, and reports the new height.
Messages from Ceres to Lydia
Ceres sends one type of message:
Content height
After rendering (or when the content size changes):
parent.postMessage({
source: 'ceres',
type: 'ceres:content-height',
height: 1247,
reason: 'render-complete'
}, '*');
This message is debounced (120ms) and only sent when the height actually changes by more than 1px. This prevents flooding Lydia with identical messages.
Key files reference
In Lydia
| File | What it does |
|---|---|
src/components/utils/iframeUtils.js | Builds iframe URLs, sends print commands, posts template updates |
src/components/hooks/useIframeHeight.js | Listens for height messages, manages iframe sizing |
src/components/widgets/IframeRenderer.jsx | The actual iframe React component |
In Ceres
| File | What it does |
|---|---|
src/main/lydiaBridge.ts | All Lydia communication: height reporting, print handling, template updates |
src/main/commonUtils.ts | Style application (applyPreviewStyles), asset loading |
src/main/dibellaBridge.ts | Dibella communication (currently a placeholder) |
When does Lydia use Ceres?
Lydia checks if the user has a custom layout template. If yes, it renders via Ceres. If no, it uses its own React system templates.
The check happens in iframeUtils.js:
export const hasCustomLayout = (customTemplateApplied) => {
return customTemplateApplied?.isLayoutTemplate || false;
};
If hasCustomLayout returns true, the IframeRenderer component creates an iframe pointing to Ceres. If it returns false, Lydia renders the document using its own React components (like template/quotation/default.js).