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). The manifest's requestedScopes declares the scopes your Application uses."requestedScopes": [] declares "needs nothing" — everything gated is denied.)requestedScopes at all? The Application falls back to per-capability prompts. Applications written before declarations existed (and inline Applications, which have no manifest) still work: each capability asks for consent the first time it comes up, one dialog at a time. Nothing runs unconsented — it just cannot batch or bound the asking. Declaring is both the better experience and the stronger promise.<script src="charmiq://…"> tag or a charmiq.fetch of a CharmIQ URL runs with the user's credentials and requires fetch.platform.denied by the consent gate — scope 'fetch.platform' is not declared in the manifest's 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.<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.