The two storage channels every Application has — when to use each and how to avoid the pitfalls.
|
|
appContent | ||
appContent | ||
appContent | ||
appState | ||
appState | ||
appState |
onChange$() observable fires immediately with the current content, then again on every change — from any source.// Single block (default) const text = await window.charmiq.appContent.get(); // Named block const code = await window.charmiq.appContent.get("[name='editor']"); // By stable node ID (most reliable across renames) const text = await window.charmiq.appContent.get("[id='kRP9xNbCmQ']");
// Watch the default block window.charmiq.appContent.onChange$().subscribe(change => { console.log(change.content); // current text console.log(change.id); // stable node ID console.log(change.name); // name attribute (if set) }); // Watch all blocks — fires once per block on init, then on add/remove/change window.charmiq.appContent.onChange$().subscribe(change => { if (change.deleted) { // Block was removed } else { // Block was added or updated } });
set() replaces the full content of a block. It creates a new block if the selector doesn't match anything.// Replace entire content await window.charmiq.appContent.set('Hello, world!'); // Replace a named block await window.charmiq.appContent.set(newText, "[name='output']"); // Create a new named block await window.charmiq.appContent.set('', "[name='log']", 'Log');
applyChanges() sends incremental edits through the OT pipeline. Use this for editor-like Applications where the user is actively typing — it produces minimal diffs that merge better with concurrent edits.await window.charmiq.appContent.applyChanges([ { from: 0, to: 5, insert: 'Goodbye' }, // Replace chars 0–5 { from: 20, to: 20, insert: ' world' } // Insert at position 20 ]);
await window.charmiq.appContent.remove("[name='log']");
onChange$(). Without a guard, you create an infinite loop: write → notification → write → ...let updating = false; window.charmiq.appContent.onChange$().subscribe(change => { if (updating) return; updating = true; editor.setValue(change.content); updating = false; }); editor.on('change', () => { if (updating) return; window.charmiq.appContent.applyChanges(computeDiff(editor)); });
const blocks = new Map(); window.charmiq.appContent.onChange$().subscribe(change => { if (change.deleted) { blocks.delete(change.id); } else { blocks.set(change.id, { name: change.name, content: change.content }); } render(blocks); }); // Write to a specific block by ID await window.charmiq.appContent.set(newText, `[id='${blockId}']`);
// Read current state const config = await window.charmiq.appState.get(); // → { theme: 'dark', language: 'javascript', fontSize: 14 } // Watch for changes (fires immediately with current state) window.charmiq.appState.onChange$().subscribe(state => { applyTheme(state?.theme ?? 'light'); }); // Write (replaces the entire object) await window.charmiq.appState.set({ theme: 'light', language: 'python', fontSize: 16, });
[id='kRP9xNbCmQ'] | |
[name='editor'] | |
[0], [1] |
window.charmiq API reference including appContent, appState, OAuth, and MCP.