Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .cursorrules
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Ceres - Custom Document Renderer
# Uses Handlebars for templates, plain CSS for styling.
# Runs inside an iframe in Lydia, communicates via postMessage.

# Structure:
# src/main/ - Renderer (index.ts, commonUtils.ts, lydiaBridge.ts)
# src/templates/ - Custom templates (HBS + CSS + index.ts per folder)
# src/widgets/ - Reusable partials (InvoiceStatus, DemoBadge, DateTime, MarkdownViewer)

# Flow:
# 1. index.html loads main-manifest.json
# 2. Renderer reads ?template=<name>&apiUrl=<base64>
# 3. Loads template JS/CSS, template sets window.CeresTemplate
# 4. Fetches API data, calls CeresTemplate(data), puts HTML in page

# Lydia postMessage protocol:
# Lydia -> Ceres: lydia:print, lydia:height-request, lydia:template-update
# Ceres -> Lydia: ceres:content-height

# For detailed skills, read .agent/skills/*/SKILL.md:
# - scaffold-template: Create new template
# - debug-build: Fix build failures
# - navigate-codebase: Repo map + Lydia integration
# - snapshot-testing: Visual regression tests
# - design-to-template: Figma/screenshot -> HBS template

# Build:
# npm run build - Build everything
# npm run build:template --template=NAME - Build one template
# npm run build:widget --widget=NAME - Build one widget
# npm run typecheck - TypeScript check
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist/
67 changes: 67 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"env": {
"browser": true,
"node": true,
"jest": true
},
"extends": [
"eslint:recommended",
"airbnb-base",
"plugin:eslint-comments/recommended",
"plugin:prettier/recommended",
"plugin:jsdoc/recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:jsdoc/recommended-typescript"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"eslint-comments",
"jest",
"prettier",
"promise",
"jsdoc",
"@typescript-eslint"
],
"settings": {
"import/resolver": {
"node": {
"extensions": [".js", ".jsx", ".ts", ".tsx"]
},
"typescript": {}
},
"import/parsers": {
"@typescript-eslint/parser": [".js", ".ts", ".tsx"]
}
},
"rules": {
"no-plusplus": ["error", { "allowForLoopAfterthoughts": true }],
"no-param-reassign": "off",
"no-underscore-dangle": ["error", { "allow": ["_id"] }],
"import/extensions": [
"error",
"ignorePackages",
{
"js": "never",
"ts": "never",
"tsx": "never",
"jsx": "never",
"mjs": "always"
}
],
"@typescript-eslint/no-explicit-any": "warn"
},
"overrides": [
{
"files": ["*.js", "*.jsx", "*.cjs"],
"rules": {
"@typescript-eslint/no-var-requires": "off"
}
}
],
"ignorePatterns": ["webpack.config.js", "coverage/"]
}
4 changes: 4 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
legacy-peer-deps=true
registry=https://registry.npmjs.org
@refrens:registry=https://npm.pkg.github.com
scripts-prepend-node-path=true
3 changes: 3 additions & 0 deletions .stylelintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["stylelint-config-standard-scss"]
}
52 changes: 52 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Ceres - Custom Document Renderer

Ceres renders custom document templates (invoices, quotations) inside an iframe in Lydia. It uses Handlebars for templating and plain CSS for styling.

## Project Structure

- `src/main/` - Main renderer (loads templates, fetches API data, renders)
- `src/templates/` - Custom templates (each folder is one template)
- `src/widgets/` - Reusable Handlebars partials (InvoiceStatus, DemoBadge, DateTime, MarkdownViewer)
- `webpack.config.js` - Build config with automatic entry discovery and semver versioning
- `index.html` - Entry page that bootstraps the renderer

## How it works

1. Browser loads `index.html` which fetches `main-manifest.json`
2. Renderer reads `?template=<name>&apiUrl=<base64>` from the URL
3. Loads template JS/CSS bundles
4. Template registers `window.CeresTemplate` (compiled Handlebars function)
5. Renderer fetches API data and calls `window.CeresTemplate(data)`
6. Result HTML goes into `<div id="documentOutput">`

## Lydia Integration

Ceres runs inside an iframe in Lydia. Communication via postMessage:
- Lydia sends: `lydia:print`, `lydia:height-request`, `lydia:template-update`
- Ceres sends: `ceres:content-height`

Key Lydia files: `iframeUtils.js` (URL builder), `useIframeHeight.js` (height sync), `IframeRenderer.jsx` (iframe component)

## Skills

For detailed instructions on specific tasks, read these files:

- **Scaffold a template**: `.agent/skills/scaffold-template/SKILL.md`
- **Debug build failures**: `.agent/skills/debug-build/SKILL.md`
- **Navigate the codebase**: `.agent/skills/navigate-codebase/SKILL.md`
- **Snapshot testing**: `.agent/skills/snapshot-testing/SKILL.md`
- **Convert design to template**: `.agent/skills/design-to-template/SKILL.md`

## Build Commands

```bash
npm run build # Build everything
npm run build:template --template=my-template # Build one template
npm run build:widget --widget=date-time # Build one widget
npm run typecheck # TypeScript check
npm test # Run Jest tests
```

## Static Hosting

Built files are uploaded to Azure Blob Storage and served through the lstatic CDN at `lstatic.refrens.com/ceres/`. JS/CSS are cached. JSON/HTML are not cached (so manifest updates take effect immediately).
3 changes: 3 additions & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
extends: ["@commitlint/config-conventional"],
};
12 changes: 6 additions & 6 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@
color: #555;
}
</style>
<script>
if (!window.location.search) {
window.location.replace("?devMode=1");
}
</script>
</head>
<script>
if (!window.location.search) {
window.location.replace("?devMode=1");
}
</script>
<body>
<div id="documentOutput" class="loading-message">
Trying to load document...
Expand Down Expand Up @@ -110,4 +110,4 @@
});
</script>
</body>
</html>
</html>
33 changes: 21 additions & 12 deletions jest.config.cjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
// JEST_CI_RUN=1 is set by test:staged (lint-staged pre-commit). In that mode,
// --findRelatedTests only runs the subset of tests for staged files, so global
// collectCoverageFrom would pull in unrelated source files with 0% coverage and
// blow the 100% threshold. Disable both for staged runs; full enforcement lives
// in test:coverage which runs the entire suite.
const isStagedRun = process.env.JEST_CI_RUN === "1";

module.exports = {
testEnvironment: "node",
transform: {
Expand All @@ -8,17 +15,19 @@ module.exports = {
testPathIgnorePatterns: ["/node_modules/", "<rootDir>/tests/render-invoice.test.ts"],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'json', 'json-summary'],
coverageThreshold: {
global: {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
...(isStagedRun ? {} : {
coverageThreshold: {
global: {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
},
},
collectCoverageFrom: [
'src/**/*.{ts,js}',
'!src/**/*.d.ts',
'!src/**/index.ts', // Exclude pure export files if needed, but per request keep it strict
],
collectCoverageFrom: [
'src/**/*.{ts,js}',
'!src/**/*.d.ts',
'!src/**/index.ts',
],
}),
};
Loading