-
Notifications
You must be signed in to change notification settings - Fork 0
Support modules for composer apps #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| node_modules | ||
| dist/ | ||
| module/ | ||
| logs/ | ||
| .DS_Store |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,8 @@ | |
|
|
||
| This directory contains a template for creating embedded applications that share a consistent design system and user experience. | ||
|
|
||
| Each app can run as a **normal static site** (`npm run build` → `dist/`) and as a **bundle for composer hosts** (`npm run build:module` → `module/`). Hosts fetch `/simulations/{id}/content.html`, `simulation.css`, and `simulation.js`; serve the module tree with `IS_PRODUCTION=true SERVE_DIR=module PORT=<port> node server.js`. See `AGENTS.md` and `BESPOKE-TEMPLATE.md`. | ||
|
|
||
|
Comment on lines
+5
to
+6
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Document These lines correctly show 🤖 Prompt for AI Agents |
||
| ## Components | ||
|
|
||
| ### 1. Design System Integration | ||
|
|
@@ -24,12 +26,8 @@ A base HTML template that includes: | |
| - Help modal integration | ||
| - Proper CSS and JavaScript loading | ||
|
|
||
| ### 4. `client/help-modal.js` | ||
| A dependency-free JavaScript module for the help modal system: | ||
| - Consistent modal behavior across all apps | ||
| - Keyboard navigation (ESC to close) | ||
| - Focus management | ||
| - Custom event system | ||
| ### 4. `client/app.js` and `client/standalone.js` | ||
| Shell behavior (help modal via design system `Modal`, WebSocket) plus standalone `init(context)` wiring for the same contract composer hosts use. | ||
|
|
||
| ### 5. `client/help-content-template.html` | ||
| A template for creating consistent help content: | ||
|
|
@@ -52,7 +50,7 @@ A template for creating consistent help content: | |
| - `<!-- APP_TITLE -->` - Your application title | ||
| - `<!-- APP_NAME -->` - Your application name (appears in header) | ||
| - `<!-- APP_SPECIFIC_HEADER_CONTENT -->` - Any additional header elements | ||
| - `<!-- APP_SPECIFIC_MAIN_CONTENT -->` - Your main content area | ||
| - Default `<main id="standalone-sim-mount" …>` / `client/content.html` — your main UI (keep in sync for composer) | ||
| - `<!-- APP_SPECIFIC_CSS -->` - Links to your app-specific CSS files | ||
| - `<!-- APP_SPECIFIC_SCRIPTS -->` - Links to your app-specific JavaScript files | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| /* Styles for simulation fragment (bundled as module/simulation.css) */ | ||
| .bespoke-sim-intro { | ||
| max-width: 36rem; | ||
| } | ||
|
|
||
| .bespoke-sim-hint code { | ||
| font-size: 0.9em; | ||
| } | ||
|
|
||
| .bespoke-sim-standalone-mount { | ||
| padding: var(--UI-Spacing-spacing-ml, 1rem); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,10 @@ | ||||||
| <!-- Fragment mounted by composer hosts under /simulations/{id}/content.html --> | ||||||
| <main data-bespoke-sim-root> | ||||||
| <section class="box card bespoke-sim-intro"> | ||||||
| <h2 class="heading-small">Your app</h2> | ||||||
| <p class="body-default bespoke-sim-hint"> | ||||||
| Replace this markup and extend <code>simulation-app.js</code>. Keep this file in sync with the standalone block in <code>index.html</code> when you use both modes. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep the fragment text identical to Line 6 does not match the corresponding standalone block copy, so the two files are already out of sync. Suggested sync fix- Replace this markup and extend <code>simulation-app.js</code>. Keep this file in sync with the standalone block in <code>index.html</code> when you use both modes.
+ Replace this block when customizing; keep in sync with <code>client/content.html</code> for composer builds.As per coding guidelines, "Keep 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| </p> | ||||||
| <button type="button" class="button button-primary" id="btn-sim-demo">Send sample event</button> | ||||||
| </section> | ||||||
| </main> | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,22 +24,32 @@ | |
|
|
||
| <!-- Template-specific components (layout, utilities, temporary components) --> | ||
| <link rel="stylesheet" href="./bespoke-template.css" /> | ||
| <link rel="stylesheet" href="./bespoke-simulation.css" /> | ||
| <!-- APP_SPECIFIC_CSS --> | ||
| </head> | ||
| <body class="bespoke"> | ||
| <!-- Navigation Header --> | ||
| <header class="header"> | ||
| <h1 class="heading-small">APP NAME</h1> | ||
| <h1 class="heading-small"><!-- APP_NAME --></h1> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace the Line 33 still contains a template placeholder instead of user-facing title text. As per coding guidelines, "Replace 🤖 Prompt for AI Agents |
||
| <!-- APP_SPECIFIC_HEADER_CONTENT --> | ||
| <div class="spacer"></div> | ||
| <button id="btn-help" class="button button-text">Help</button> | ||
| </header> | ||
|
|
||
| <!-- Main Application Content --> | ||
| <!-- APP_SPECIFIC_MAIN_CONTENT --> | ||
| <!-- Main Application Content: default starter below — replace when customizing; keep id & data-bespoke-sim-root if you use standalone.js + composer --> | ||
| <main id="standalone-sim-mount" class="bespoke-sim-standalone-mount" data-bespoke-sim-root> | ||
| <section class="box card bespoke-sim-intro"> | ||
| <h2 class="heading-small">Your app</h2> | ||
| <p class="body-default bespoke-sim-hint"> | ||
| Replace this block when customizing; keep in sync with <code>client/content.html</code> for composer builds. | ||
| </p> | ||
| <button type="button" class="button button-primary" id="btn-sim-demo">Send sample event</button> | ||
| </section> | ||
| </main> | ||
|
|
||
| <!-- Core Scripts --> | ||
| <script type="module" src="./app.js"></script> | ||
| <script type="module" src="./standalone.js"></script> | ||
| <!-- APP_SPECIFIC_SCRIPTS --> | ||
| </body> | ||
| </html> | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,47 @@ | ||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||
| * Simulation entry for composer hosts: exports init(context). | ||||||||||||||||||||||||||||||
| * Hosts load content.html, simulation.css, then import ./simulation.js | ||||||||||||||||||||||||||||||
| * (see composer template simulation-loader.js). | ||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| function escapeSelector(id) { | ||||||||||||||||||||||||||||||
| if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') { | ||||||||||||||||||||||||||||||
| return CSS.escape(id); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| return id.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| function resolveRoot(context) { | ||||||||||||||||||||||||||||||
| const simId = context.config?.id; | ||||||||||||||||||||||||||||||
| if (simId) { | ||||||||||||||||||||||||||||||
| const slot = document.querySelector( | ||||||||||||||||||||||||||||||
| `.sim-slot[data-sim-id="${escapeSelector(simId)}"]` | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| const inner = slot?.querySelector('[data-bespoke-sim-root]'); | ||||||||||||||||||||||||||||||
| if (inner) return inner; | ||||||||||||||||||||||||||||||
| if (slot) return slot; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| return document.getElementById('standalone-sim-mount'); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| function bindDemo(root, emit) { | ||||||||||||||||||||||||||||||
| const btn = root.querySelector('#btn-sim-demo'); | ||||||||||||||||||||||||||||||
| if (!btn) return; | ||||||||||||||||||||||||||||||
| btn.addEventListener('click', () => { | ||||||||||||||||||||||||||||||
| emit('demo:click', { source: 'template' }); | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
Comment on lines
+27
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make event binding idempotent to prevent duplicate emits on re-init. If Suggested fix function bindDemo(root, emit) {
const btn = root.querySelector('#btn-sim-demo');
- if (!btn) return;
+ if (!btn || btn.dataset.simDemoBound === 'true') return;
+ btn.dataset.simDemoBound = 'true';
btn.addEventListener('click', () => {
emit('demo:click', { source: 'template' });
});
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| export function init(context = {}) { | ||||||||||||||||||||||||||||||
| const emit = | ||||||||||||||||||||||||||||||
| typeof context.emit === 'function' | ||||||||||||||||||||||||||||||
| ? context.emit | ||||||||||||||||||||||||||||||
| : () => {}; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const root = resolveRoot(context); | ||||||||||||||||||||||||||||||
| if (!root) { | ||||||||||||||||||||||||||||||
| console.warn('simulation-app: no mount root found'); | ||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| bindDemo(root, emit); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| /** | ||
| * Standalone page: same init(context) contract as composer hosts. | ||
| * Set SIM_ID to the id used in the host's simulations.json. | ||
| */ | ||
| import { init } from './simulation-app.js'; | ||
|
|
||
| const SIM_ID = 'bespoke-app'; | ||
|
|
||
| const logBuffer = []; | ||
| let flushTimer = null; | ||
|
|
||
| function flushLogs() { | ||
| const entries = logBuffer.splice(0); | ||
| flushTimer = null; | ||
| if (entries.length === 0) return; | ||
| fetch('/api/log', { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ entries }) | ||
| }).catch(err => console.error('Failed to flush logs:', err)); | ||
|
Comment on lines
+12
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Requeue failed log batches instead of dropping them.
As per coding guidelines, "Implement retry logic for network operations". 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| function pushLog(entry) { | ||
| logBuffer.push({ ...entry, ts: new Date().toISOString() }); | ||
| if (!flushTimer) flushTimer = setTimeout(flushLogs, 1000); | ||
| } | ||
|
|
||
| const context = { | ||
| config: { id: SIM_ID, basePath: '' }, | ||
| emit: (eventType, payload = {}) => { | ||
| pushLog({ simId: SIM_ID, dir: 'event', type: eventType, payload }); | ||
| } | ||
| }; | ||
|
|
||
| function boot() { | ||
| init(context); | ||
|
Comment on lines
+35
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Catch A thrown or rejected As per coding guidelines, "Provide meaningful error messages to users" and "Log errors to console for debugging". 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| if (document.readyState === 'loading') { | ||
| document.addEventListener('DOMContentLoaded', boot); | ||
| } else { | ||
| boot(); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update the required file list to match the new module workflow.
The new composer section introduces
client/standalone.js,client/simulation-app.js,client/content.html, andclient/bespoke-simulation.css, but "Required Files Structure" still only listsapp.jsandserver.js. That leaves the document internally inconsistent for new template adopters.Based on learnings, "Maintain consistency with the template's file structure and patterns when adding new files".
Also applies to: 55-69
🤖 Prompt for AI Agents