How folder-based Applications are compiled — the manifest, virtual file system, and what the build produces.
|
|
Source files (in your folder or manifest) ↓ Virtual file system (in-memory path map) ↓ esbuild (TypeScript → JS, SCSS → CSS, resolves imports) ↓ HTML assembly (inject styles, scripts, import maps) ↓ Self-contained HTML document
manifest.json Source document. For folder-based Applications, it lives at the root of your folder. For document-based Applications, it's the first JSON code block in the document.{ "id": "my_app_v1", "name": "My Application", "description": "What this does", "requestedScopes": [ "appState.read", "appState.write" ], "bundle": { "format": "esm", "entry": { "html": "/index.html", "script": "/src/main.tsx", "style": "/src/styles.scss" }, "dependencies": { "importMap": { "react": "https://esm.sh/react@18.2.0", "react-dom/client": "https://esm.sh/react-dom@18.2.0/client" } } } }
id | |
name | |
bundle.entry.html |
requestedScopes | |
bundle.entry.script | |
bundle.entry.style | |
bundle.format | 'esm' for ES modules. Omit for IIFE (global variables). |
bundle.files | |
bundle.dependencies |
charmiq.fetch, MCP, OAuth, Application-to-Application — resolves to a scope (for example appState.read, command.notification.toast.emit, fetch.platform). Folder-based and document-based Applications declare these scopes in the manifest's requestedScopes. Inline Applications declare them in the Application container's requested-scopes attribute as a percent-encoded JSON array."requestedScopes": [] declares "needs nothing" — everything gated is denied.)requested-scopes has an authoritative empty declaration. It loads without prompting, and every gated call is denied until its scope is added to the container.requestedScopes? A manifest-backed Application written before declarations existed retains legacy per-capability prompts. Add requestedScopes to bound what it can ask for and to batch consent.<script src="charmiq://…"> tag or a charmiq.fetch of a CharmIQ URL runs with the user's credentials and requires fetch.platform.navigation.popupEscape lets a popup your Application opens leave the iframe sandbox; that is what a popup-based OAuth flow or an external picker needs. navigation.top lets your Application navigate the whole CharmIQ page away — high sensitivity, so omit it unless replacing the page is the feature. Reach for popupEscape first; top is rarely the answer.denied by the consent gate — scope 'fetch.platform' is not declared in requestedScopes), and the [consent-shadow] rows list every scope the Application exercised with its declared= status.App.tsx in a sub-folder named components becomes /components/App.tsx.files entries: Virtual paths map directly to source queries. The query format is ?nodeId=<id>&format=plain.files entries override or supplement them. Useful when you need to pull a specific file from outside the folder.Folder on disk → Virtual file system ──────────────── ──────────────────── manifest.json (parsed, excluded) index.html → /index.html src/main.tsx → /src/main.tsx src/App.tsx → /src/App.tsx components/Header.tsx → /components/Header.tsx
import { Header } from './components/Header' resolves to /components/Header.tsx..ts) | ||
.tsx) | ||
.jsx) | ||
.js) | ||
.scss) | ||
.sass) | ||
.css) | ||
.html) |
@import 'variables' resolves to _variables.scss in the virtual file system.format: 'esm'):{ "bundle": { "format": "esm", "dependencies": { "importMap": { "react": "https://esm.sh/react@18.2.0", "react-dom": "https://esm.sh/react-dom@18.2.0", "react-dom/client": "https://esm.sh/react-dom@18.2.0/client", "lucide-react": "https://esm.sh/lucide-react@0.554.0?deps=react@18.2.0" } } } }
<script type="importmap"> into the output HTML. Your code uses normal import statements.{ "bundle": { "dependencies": { "cdn": { "react": { "url": "https://unpkg.com/react@18.2.0/umd/react.production.min.js", "global": "React" } } } } }
<script> tags for each CDN URL and generates a require() shim that maps package names to window globals.object-src 'none' and base-uri 'none'. Do not rely on <object>, <embed> or <base href> in Application HTML. Use ordinary elements for embedded content and explicit or document-relative URLs for scripts, styles, links and requests.<html> <head> <!-- injected: import map or CDN scripts --> <!-- injected: compiled CSS --> </head> <body> <!-- your HTML template content --> <script src="charmiq-bridge.js"></script> <!-- bridge injected --> <script type="module">/* bundled JS */</script> </body> </html>
window.charmiq is available before any of your code runs.?debug=true to your Application URL to see build diagnostics:window.charmiq.debug.buildInfo // Full build metadata window.charmiq.debug.showWarnings() // Log build warnings window.charmiq.debug.openDebugPage() // Open debug dashboard
files map. The manifest only needs entry points:{ "id": "my_folder_app", "name": "My App", "bundle": { "format": "esm", "entry": { "html": "/index.html", "script": "/main.tsx" }, "dependencies": { "importMap": { "react": "https://esm.sh/react@18.2.0", "react-dom/client": "https://esm.sh/react-dom@18.2.0/client" } } } }
build_from_content accepts a manifest and inline virtual files. build_from_uri builds an existing CharmIQ Folder or Application document.App.tsx, styles.scss, utils.ts. The extension is how the build pipeline knows which loader to use.https://esm.sh/react@18.2.0 not https://esm.sh/react. Unpinned CDN URLs can introduce breaking changes silently.<style> tags in your HTML template work fine for simple Applications. Add a SCSS entry point when you need variables, nesting, or multiple stylesheets.window.charmiq.