﻿{"id":835333,"date":"2026-02-18T12:47:54","date_gmt":"2026-02-18T12:47:54","guid":{"rendered":"https:\/\/gridnet.org\/wpp\/?p=835333"},"modified":"2026-02-20T07:57:33","modified_gmt":"2026-02-20T07:57:33","slug":"gridnet-os-ui-dapp-design-guidelines-the-complete-developer-reference","status":"publish","type":"post","link":"https:\/\/mag.gridnet.org\/index.php\/2026\/02\/18\/gridnet-os-ui-dapp-design-guidelines-the-complete-developer-reference\/","title":{"rendered":"GRIDNET OS UI dApp Design Guidelines \u2014 The Complete Developer Reference"},"content":{"rendered":"<h2>GRIDNET OS UI dApp Design Guidelines \u2014 The Complete Developer Reference<\/h2>\n<p><em>Everything a third-party developer needs to build production-grade decentralized applications for the world&#8217;s first decentralized operating system.<\/em><\/p>\n<p><strong>The most important thing we can tell you upfront:<\/strong> If you know HTML, CSS, and JavaScript, you already know how to build UI dApps for GRIDNET OS. There is no new language to learn. No proprietary framework to master. No paradigm shift to survive. You write standard web code \u2014 the same <code>&lt;div&gt;<\/code>s, the same flexbox, the same ES6 classes you&#8217;ve been writing for years \u2014 and it runs inside a decentralized operating system. The blockchain is underneath, but you interact with it through <code>CVMContext<\/code>: a clean, familiar JavaScript API. You call methods. You get callbacks. You <code>await<\/code> promises. That&#8217;s it.<\/p>\n<p>Even when you venture deeper \u2014 into GridScript, GRIDNET OS&#8217;s native shell \u2014 you&#8217;ll find yourself on familiar ground. GridScript commands mirror the Linux\/DOS commands you already know: <code>cd<\/code>, <code>ls<\/code>, <code>cat<\/code>, <code>rm<\/code>, <code>mkdir<\/code>, <code>touch<\/code>, <code>chown<\/code>, <code>setfacl<\/code>, <code>getfacl<\/code>. The <code>send<\/code> command transfers tokens. <code>BT<\/code>\/<code>CT<\/code> (Begin Transaction \/ Commit Transaction) map directly to <code>BEGIN<\/code>\/<code>COMMIT<\/code> from SQL \u2014 same concept, blockchain-backed. It&#8217;s a familiar shell with decentralized superpowers underneath.<\/p>\n<p>This guide isn&#8217;t about learning something alien. It&#8217;s about learning the <em>specific rules<\/em> that make your existing web development skills work inside a multi-window, Shadow DOM-isolated, decentralized environment. The rules are few, well-motivated, and \u2014 once understood \u2014 make your dApps automatically responsive, secure, and multi-instance-safe. The payoff is enormous.<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/gridnet.org\/wpp\/wp-content\/uploads\/2026\/02\/dapp-guidelines-dapp-guide-featured.png\" alt=\"GRIDNET OS dApp Architecture\" \/><figcaption>The architecture of a decentralized windowed operating system \u2014 where every application is an island of sovereign computation.<\/figcaption><\/figure>\n<h2>I. The Paradigm: Why GRIDNET OS dApps Are Different (And Why That&#8217;s Easy)<\/h2>\n<p>GRIDNET OS dApp development <em>is<\/em> web development. You use HTML for structure, CSS for styling, JavaScript for logic. The browser is the runtime. But there is one key difference you must internalise: <strong>your application does not own the viewport<\/strong>. Your application lives inside a <strong>window<\/strong> \u2014 one of potentially dozens of simultaneously running decentralized applications, each isolated from the others by Shadow DOM encapsulation, each communicating with a decentralized blockchain backend through a single, clean JavaScript gateway.<\/p>\n<p>This is not a limitation. This is a <strong>superpower<\/strong>.<\/p>\n<p>Shadow DOM isolation means your dApp cannot be tampered with by other applications. It means you can run ten instances of the same application simultaneously without a single variable collision. It means your CSS cannot leak into \u2014 or be corrupted by \u2014 anything else running on the system. In a decentralized operating system where untrusted code from unknown developers runs side by side, this isolation is not merely convenient \u2014 it is <em>essential for security<\/em>.<\/p>\n<p>The viewport-independence (no <code>vw<\/code>\/<code>vh<\/code> units) means your application automatically responds to its window being resized, maximised, minimised, or snapped \u2014 just like a native desktop application. You design for a container, not a screen, and the system handles the rest.<\/p>\n<p>This guide covers every rule, every pattern, and every technique you need to build a production dApp for GRIDNET OS. If you can write HTML, CSS, and JavaScript, you can build for GRIDNET OS. But you must understand the rules \u2014 and more importantly, <em>why<\/em> they exist.<\/p>\n<p><strong>Prerequisites:<\/strong> Familiarity with JavaScript ES6 (classes, modules, arrow functions), HTML5, and CSS3. For a gentler introduction, see the <a href=\"https:\/\/gridnet.org\/wpp\/?p=4152\">Hello World UI dApp Tutorial<\/a>.<\/p>\n<h2>II. Architecture: The Three-Layer Stack<\/h2>\n<figure><img decoding=\"async\" src=\"https:\/\/gridnet.org\/wpp\/wp-content\/uploads\/2026\/02\/dapp-guidelines-dapp-guide-architecture.png\" alt=\"Three-layer architecture diagram\" \/><figcaption>The three-layer architecture: your dApp, CVMContext, and the decentralized GRIDNET OS Core.<\/figcaption><\/figure>\n<p>Every GRIDNET OS UI dApp operates within a three-layer architecture:<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/gridnet.org\/wpp\/wp-content\/uploads\/2026\/02\/dapp-guide-diagram-architecture-1.png\" alt=\"GRIDNET OS Three-Layer Architecture Diagram\" style=\"width:100%;max-width:1200px;\" \/><figcaption>The three-layer architecture: Your UI dApp communicates through CVMContext to the GRIDNET OS Core \u2014 clean separation of concerns.<\/figcaption><\/figure>\n<h3>Layer 1: Your UI dApp (CWindow Instance)<\/h3>\n<p>Your application is a JavaScript ES6 class that <strong>extends <code>CWindow<\/code><\/strong> from <code>\/lib\/window.js<\/code>. The CWindow base class provides window management (dragging, resizing, minimising, maximising), Shadow DOM encapsulation, DOM access methods, curtain\/loading UI, dialog systems, and lifecycle hooks. Your dApp inherits all of this automatically.<\/p>\n<h3>Layer 2: CVMContext (The Gateway)<\/h3>\n<p>The <code>CVMContext<\/code> singleton (<code>\/lib\/VMContext.js<\/code>) is the <strong>only legitimate interface<\/strong> between your dApp and the GRIDNET OS Core. It provides APIs for blockchain queries, Decentralized File System (DFS) operations, network messaging, thread management, settings persistence, and event subscription. Access it via <code>CVMContext.getInstance()<\/code>. For comprehensive CVMContext documentation, see <a href=\"https:\/\/gridnet.org\/wpp\/?p=94437\">the CVMContext reference<\/a>.<\/p>\n<h3>Layer 3: GRIDNET OS Core<\/h3>\n<p>The C++ full-node software that manages the blockchain, consensus, networking, and decentralized file storage. Your dApp never communicates with it directly \u2014 CVMContext handles all communication over WebSocket\/WebRTC. For blockchain data exploration APIs, see the <a href=\"https:\/\/gridnet.org\/wpp\/?p=666579\">Explorer API documentation<\/a>.<\/p>\n<h2>III. Getting Started: The Official Template<\/h2>\n<figure><img decoding=\"async\" src=\"https:\/\/gridnet.org\/wpp\/wp-content\/uploads\/2026\/02\/dapp-guidelines-dapp-guide-template.png\" alt=\"Template structure blueprint\" \/><figcaption>The official AppTemplate.js \u2014 your blueprint for every GRIDNET OS dApp.<\/figcaption><\/figure>\n<p>The fastest path to a working dApp is the official template at <code>WebUI\/dApps\/AppTemplate.js<\/code>. Here is the customisation checklist:<\/p>\n<ol>\n<li><strong>Copy and rename<\/strong> <code>AppTemplate.js<\/code> to your dApp name (e.g., <code>MyCoolDApp.js<\/code>)<\/li>\n<li><strong>Rename the class<\/strong> from <code>CUIAppTemplate<\/code> to your class name (e.g., <code>CMyCoolDApp<\/code>)<\/li>\n<li><strong>Set the window title<\/strong> in the <code>super()<\/code> call<\/li>\n<li><strong>Update the default export<\/strong> at the bottom of the file<\/li>\n<li><strong>Set your icon<\/strong> in <code>static getIcon()<\/code> as a Base64-encoded <code>data:image\/png;base64,\u2026<\/code> string<\/li>\n<li><strong>Define your HTML body<\/strong> in the body variable<\/li>\n<li><strong>Set a unique package ID<\/strong> in <code>static getPackageID()<\/code> \u2014 must use reverse-domain notation starting with <code>org.gridnetproject.UIdApps.<\/code><\/li>\n<li><strong>Optionally set a category<\/strong> via <code>static getDefaultCategory()<\/code> \u2014 options include <code>'dApps'<\/code>, <code>'explore'<\/code>, <code>'productivity'<\/code><\/li>\n<li><strong>Optionally register file handlers<\/strong> via <code>static getFileHandlers()<\/code><\/li>\n<\/ol>\n<h3>Minimal Complete Example<\/h3>\n<p>Here is the absolute minimum viable dApp \u2014 a complete, working application:<\/p>\n<pre>\n\"use strict\"\n\nimport { CWindow } from \"\/lib\/window.js\"\n\nconst myBody = `\n&lt;link rel=\"stylesheet\" href=\"\/css\/windowDefault.css\" \/&gt;\n&lt;style&gt;\n.container {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    height: 100%;\n    width: 100%;\n    font-family: 'Rajdhani', sans-serif;\n    color: #22fafc;\n    background: #0a0a14;\n}\n.greeting {\n    font-size: 2rem;\n    text-shadow: 0 0 10px rgba(34, 250, 252, 0.5);\n}\n&lt;\/style&gt;\n&lt;div class=\"container\"&gt;\n    &lt;div class=\"greeting\" id=\"msg\"&gt;Hello, GRIDNET OS!&lt;\/div&gt;\n&lt;\/div&gt;\n`;\n\nclass CMyFirstDApp extends CWindow {\n    constructor(positionX, positionY, width, height, data, dataType, filePath, thread) {\n        super(positionX, positionY, width, height, myBody, \"My First dApp\",\n              CMyFirstDApp.getIcon(), true);\n    }\n\n    static getPackageID() {\n        return \"org.gridnetproject.UIdApps.myFirstDApp\";\n    }\n\n    static getDefaultCategory() {\n        return 'dApps';\n    }\n\n    static getIcon() {\n        return ''; \/\/ data:image\/png;base64,...\n    }\n\n    open() {\n        super.open();\n        const msg = this.getControl('msg');\n        msg.textContent = 'Welcome to the Decentralized Future!';\n    }\n\n    closeWindow() {\n        super.closeWindow();\n    }\n}\n\nexport default CMyFirstDApp;\n<\/pre>\n<h2>IV. JavaScript Rules: The Ten Commandments<\/h2>\n<figure><img decoding=\"async\" src=\"https:\/\/gridnet.org\/wpp\/wp-content\/uploads\/2026\/02\/dapp-guidelines-dapp-guide-js-rules.png\" alt=\"JavaScript rules enforcement\" \/><figcaption>The JavaScript rules \u2014 forged in the fires of multi-instance isolation and decentralized security.<\/figcaption><\/figure>\n<p>These rules are <strong>mandatory<\/strong>. Violating them will cause subtle, hard-to-debug failures when multiple dApp instances run simultaneously, or when your dApp interacts with others on the system.<\/p>\n<h3>Rule 1: No Global Scope Pollution<\/h3>\n<p><strong>The rule:<\/strong> DO NOT define variables, functions, or objects in the global (<code>window<\/code>) scope.<\/p>\n<p><strong>Why it exists:<\/strong> GRIDNET OS runs multiple dApp instances in the same browser tab. Global variables from one instance will collide with globals from another instance \u2014 or worse, from a completely different dApp. A global <code>let count = 0<\/code> in your code becomes a shared mutable variable across every running application.<\/p>\n<p><strong>\u274c BAD \u2014 Global state that will collide:<\/strong><\/p>\n<pre>\nlet balance = 0;  \/\/ GLOBAL \u2014 shared across ALL instances!\nlet isConnected = false;\n\nfunction updateBalance(val) {  \/\/ GLOBAL function!\n    balance = val;\n}\n\nclass CMyDApp extends CWindow {\n    constructor(...args) {\n        super(...args);\n        updateBalance(100); \/\/ Modifies shared global state\n    }\n}\n<\/pre>\n<p><strong>\u2705 GOOD \u2014 All state encapsulated in the class:<\/strong><\/p>\n<pre>\n\"use strict\"\n\nimport { CWindow } from \"\/lib\/window.js\"\n\nclass CMyDApp extends CWindow {\n    constructor(positionX, positionY, width, height, data, dataType, filePath, thread) {\n        super(positionX, positionY, width, height, body, \"My dApp\", CMyDApp.getIcon(), true);\n        this.mBalance = 0;        \/\/ Instance-scoped\n        this.mIsConnected = false; \/\/ Instance-scoped\n    }\n\n    updateBalance(val) {  \/\/ Instance method\n        this.mBalance = val;\n    }\n}\n<\/pre>\n<p><strong>Production example<\/strong> \u2014 from the Wallet dApp constructor:<\/p>\n<pre>\nclass CUIWallet extends CWindow {\n    constructor(positionX, positionY, width, height, data, dataType, filePath, thread) {\n        super(positionX, positionY, width, height, windowBodyHTML, \"Wallet\",\n              CUIWallet.getIcon(), true, data, dataType, filePath, thread);\n        \/\/ ALL state is instance-scoped via this.*\n        this.mBalance = 0;\n        this.mElements = {};\n        this.mTxHistoryTable = null;\n        this.mRecentTxTable = null;\n        this.mAutoLockTimer = null;\n        \/\/ ... dozens more instance properties\n    }\n}\n<\/pre>\n<h3>Rule 2: No Direct DOM Access \u2014 Use Shadow DOM Methods<\/h3>\n<p><strong>The rule:<\/strong> DO NOT use <code>document.getElementById()<\/code>, <code>document.querySelector()<\/code>, or <code>document.querySelectorAll()<\/code>.<\/p>\n<p><strong>Why it exists:<\/strong> Your dApp&#8217;s elements live inside a Shadow DOM tree. The <code>document.*<\/code> methods search the <em>main document<\/em> \u2014 the GRIDNET OS shell \u2014 not your dApp&#8217;s shadow root. They will never find your elements, and if they find anything, it will be a system element you should not be touching.<\/p>\n<p><strong>\u274c BAD \u2014 Searches the main document, finds nothing:<\/strong><\/p>\n<pre>\nopen() {\n    super.open();\n    const btn = document.getElementById('send-btn');  \/\/ Returns null!\n    const labels = document.querySelectorAll('.label'); \/\/ Returns OS shell elements!\n    btn.addEventListener('click', () => {}); \/\/ TypeError: null\n}\n<\/pre>\n<p><strong>\u2705 GOOD \u2014 Uses CWindow&#8217;s scoped access methods:<\/strong><\/p>\n<pre>\nopen() {\n    super.open();\n    \/\/ getControl(id) \u2014 find element by ID within Shadow DOM\n    const btn = this.getControl('send-btn');\n\n    \/\/ getBody.querySelector \u2014 CSS selector within Shadow DOM\n    const firstLabel = this.getBody.querySelector('.label');\n\n    \/\/ getBody.querySelectorAll \u2014 all matching elements within Shadow DOM\n    const allLabels = this.getBody.querySelectorAll('.label');\n\n    btn.addEventListener('click', () => this.handleSend());\n}\n<\/pre>\n<p><strong>Production example<\/strong> \u2014 from the Wallet dApp&#8217;s dashboard initialisation (showing extensive use of <code>getControl<\/code>):<\/p>\n<pre>\n\/\/ Wallet.js \u2014 caching element references for performance\nthis.mElements.availableBalance = this.getControl('available-balance-value');\nthis.mElements.lockedBalance = this.getControl('locked-balance-value');\nthis.mElements.totalBalance = this.getControl('total-balance-value');\nthis.mElements.currentAddress = this.getControl('current-address-value');\nthis.mElements.activeKeychainName = this.getControl('active-keychain-name');\n\n\/\/ Event listeners using getControl\nthis.getControl('refresh-balance-btn').addEventListener('click', () => this.retrieveBalance(true));\nthis.getControl('send-action-btn').addEventListener('click', () => this.changeTab('send'));\nthis.getControl('receive-action-btn').addEventListener('click', () => this.changeTab('receive'));\nthis.getControl('copy-address-btn').addEventListener('click', () => this.copyAddressToClipboard());\n<\/pre>\n<p><strong>Production example<\/strong> \u2014 using <code>getBody<\/code> for querySelector operations:<\/p>\n<pre>\n\/\/ Creating and appending elements to Shadow DOM\nconst notification = \/* create element *\/;\nthis.getBody.appendChild(notification);\n\n\/\/ Querying within shadow root\nconst existingModal = this.getBody.querySelector('.tt-result-modal');\nconst typeOptions = this.getBody.querySelectorAll('.type-option');\nconst shadowRoot = this.getBody.getRootNode();\n<\/pre>\n<h3>Rule 3: Single Module Deployment<\/h3>\n<p><strong>The rule:<\/strong> DO NOT rely on multi-file JavaScript deployments. Your dApp must be a single <code>.app<\/code> file.<\/p>\n<p><strong>Why it exists:<\/strong> The GRIDNET OS PackageManager expects a single file. The <code>.app<\/code> extension is simply a renamed <code>.js<\/code> file containing your bundled module.<\/p>\n<p><strong>\u274c BAD \u2014 Multiple files that won&#8217;t deploy:<\/strong><\/p>\n<pre>\n\/\/ utils.js (separate file \u2014 won't be available at runtime!)\nexport function formatBalance(val) { return val.toFixed(2); }\n\n\/\/ myDApp.js\nimport { formatBalance } from '.\/utils.js'; \/\/ FAILS at runtime\n<\/pre>\n<p><strong>\u2705 GOOD \u2014 Everything in one file, or use a bundler:<\/strong><\/p>\n<pre>\n\/\/ Either inline everything:\nfunction formatBalance(val) { return val.toFixed(2); }\n\nclass CMyDApp extends CWindow { \/* ... *\/ }\n\n\/\/ Or use Rollup\/Webpack\/Parcel to bundle before deployment.\n\/\/ System libraries (\/lib\/*) are imported normally \u2014 they're provided by the OS.\nimport { CWindow } from \"\/lib\/window.js\"      \/\/ OK \u2014 system-provided\nimport { CTools } from \"\/lib\/tools.js\"          \/\/ OK \u2014 system-provided\n<\/pre>\n<p><strong>Important distinction:<\/strong> System libraries (anything under <code>\/lib\/<\/code>) and pre-loaded third-party libraries (Tabulator.js, Plotly.js, etc.) are provided by the OS environment. Import them freely. Only <em>your own<\/em> code and non-system dependencies must be bundled.<\/p>\n<h3>Rule 4: Use WeakMap for Private Instance Data<\/h3>\n<p><strong>The rule:<\/strong> Use <code>WeakMap<\/code> to store truly private instance data when needed.<\/p>\n<p><strong>Why it exists:<\/strong> JavaScript class properties prefixed with <code>this.<\/code> are technically accessible from outside the class. <code>WeakMap<\/code> provides true encapsulation with the added benefit of automatic garbage collection when the instance is destroyed \u2014 preventing memory leaks in a long-running OS environment.<\/p>\n<p><strong>\u274c BAD \u2014 Pseudo-private with underscore convention:<\/strong><\/p>\n<pre>\nclass CMyDApp extends CWindow {\n    constructor(...args) {\n        super(...args);\n        this._privateKey = 'secret123'; \/\/ Accessible as instance._privateKey\n    }\n}\n<\/pre>\n<p><strong>\u2705 GOOD \u2014 Truly private via WeakMap:<\/strong><\/p>\n<pre>\nconst _private = new WeakMap();\n\nclass CMyDApp extends CWindow {\n    constructor(positionX, positionY, width, height, data, dataType, filePath, thread) {\n        super(positionX, positionY, width, height, body, \"My dApp\", CMyDApp.getIcon(), true);\n        _private.set(this, {\n            privateKey: null,\n            encryptionState: null,\n            sensitiveData: new Map()\n        });\n    }\n\n    setPrivateKey(key) {\n        _private.get(this).privateKey = key;\n    }\n\n    getPrivateKey() {\n        return _private.get(this).privateKey;\n    }\n}\n\/\/ _private is module-scoped, not global \u2014 only accessible within this file.\n<\/pre>\n<h3>Rule 5: Strict Mode<\/h3>\n<p><strong>The rule:<\/strong> Always use <code>\"use strict\";<\/code> at the beginning of your module.<\/p>\n<p><strong>Why it exists:<\/strong> Strict mode catches common coding mistakes (silent errors become thrown errors), prevents accidental globals, and enables JavaScript engine optimisations. In a multi-instance environment, accidental globals are catastrophic.<\/p>\n<pre>\n\"use strict\"  \/\/ FIRST LINE of your file\n\nimport { CWindow } from \"\/lib\/window.js\"\n\/\/ ... rest of your code\n<\/pre>\n<p>Both the Wallet and Terminal dApps begin with <code>\"use strict\"<\/code>.<\/p>\n<h3>Rule 6: All OS Interaction Through CVMContext<\/h3>\n<p><strong>The rule:<\/strong> Use <code>CVMContext.getInstance()<\/code> for ALL interactions with GRIDNET OS features \u2014 blockchain, DFS, network, threading, settings.<\/p>\n<p><strong>Why it exists:<\/strong> CVMContext is the designated, secure, managed gateway. It handles connection management, request routing, event dispatch, and thread synchronisation. Bypassing it would create unmanaged connections, resource leaks, and security vulnerabilities.<\/p>\n<p><strong>\u274c BAD \u2014 Direct WebSocket connection:<\/strong><\/p>\n<pre>\n\/\/ NEVER do this\nconst ws = new WebSocket('wss:\/\/node.gridnet.org:8080');\nws.onmessage = (e) => { \/* ... *\/ };\n<\/pre>\n<p><strong>\u2705 GOOD \u2014 All communication through CVMContext:<\/strong><\/p>\n<pre>\nconst vmContext = CVMContext.getInstance();\n\n\/\/ Register for blockchain events\nvmContext.addVMMetaDataListener(this.newVMMetaDataCallback.bind(this), this.mID);\nvmContext.addNewDFSMsgListener(this.newDFSMsgCallback.bind(this), this.mID);\nvmContext.addNewGridScriptResultListener(this.newGridScriptResultCallback.bind(this), this.mID);\n\n\/\/ Request data asynchronously\nconst reqID = vmContext.genRequestID(); \/\/ Generate unique request ID\nthis.addNetworkRequestID(reqID);  \/\/ Track request ownership\n\n\/\/ Async pattern\nasync initialize() {\n    try {\n        const status = await vmContext.getBlockchainStatusA(\n            this.getSystemThreadID(), this, eVMMetaCodeExecutionMode.RAW\n        );\n        this.updateUI(status.data);\n    } catch (error) {\n        this.handleError(error);\n    }\n}\n<\/pre>\n<h3>Rule 7: Do Not Bundle System Libraries<\/h3>\n<p><strong>The rule:<\/strong> DO NOT include GRIDNET OS system libraries or OS-provided third-party libraries in your bundle.<\/p>\n<p><strong>Why it exists:<\/strong> System libraries are loaded and managed by the OS. Bundling your own copy would create version conflicts, waste memory, and potentially break when the OS updates.<\/p>\n<pre>\n\/\/ \u2705 GOOD \u2014 import system libraries directly\nimport { CWindow } from \"\/lib\/window.js\"\nimport { CVMMetaSection, CVMMetaEntry, CVMMetaGenerator, CVMMetaParser } from '\/lib\/MetaData.js'\nimport { CNetMsg } from '\/lib\/NetMsg.js'\nimport { CTools, CDataConcatenator } from '\/lib\/tools.js'\nimport { CAppSettings, CSettingsManager } from \"\/lib\/SettingsManager.js\"\nimport { CContentHandler } from \"\/lib\/AppSelector.js\"\nimport { CBlockDesc } from '\/lib\/BlockDesc.js'\nimport { CTransaction } from '\/lib\/Transaction.js'\nimport { GridScriptCompiler } from '\/lib\/GridScriptCompiler.js'\n\n\/\/ Libraries like Tabulator.js, Plotly.js are pre-loaded by the OS\n\/\/ Reference them directly if they're available in the environment\n<\/pre>\n<h3>Rule 8: Bind Event Listener Context<\/h3>\n<p><strong>The rule:<\/strong> Always bind <code>this<\/code> context when using class methods as callbacks.<\/p>\n<p><strong>Why it exists:<\/strong> When a method is passed as a callback, <code>this<\/code> loses its class context and becomes the event target (or <code>undefined<\/code> in strict mode). Without binding, your callback cannot access instance properties or methods.<\/p>\n<p><strong>\u274c BAD \u2014 Lost context:<\/strong><\/p>\n<pre>\nconstructor(...args) {\n    super(...args);\n    \/\/ this.handleClick will have wrong 'this' when called\n    CVMContext.getInstance().addVMMetaDataListener(this.newVMMetaDataCallback, this.mID);\n}\n<\/pre>\n<p><strong>\u2705 GOOD \u2014 Bound context:<\/strong><\/p>\n<pre>\nconstructor(...args) {\n    super(...args);\n    \/\/ .bind(this) preserves class context\n    CVMContext.getInstance().addVMMetaDataListener(this.newVMMetaDataCallback.bind(this), this.mID);\n    CVMContext.getInstance().addNewDFSMsgListener(this.newDFSMsgCallback.bind(this), this.mID);\n    CVMContext.getInstance().addNewGridScriptResultListener(\n        this.newGridScriptResultCallback.bind(this), this.mID\n    );\n}\n\n\/\/ For UI event listeners, arrow functions also work:\nopen() {\n    super.open();\n    this.getControl('my-btn').addEventListener('click', (e) => this.handleClick(e));\n    \/\/ OR\n    this.getControl('my-btn').addEventListener('click', this.handleClick.bind(this));\n}\n<\/pre>\n<p><strong>Production example<\/strong> \u2014 the Terminal dApp constructor registers five listeners, all with <code>.bind(this)<\/code>:<\/p>\n<pre>\n\/\/ Terminal.js constructor\nCVMContext.getInstance().addVMMetaDataListener(this.newVMMetaDataCallback.bind(this), this.mID);\nCVMContext.getInstance().addNewDFSMsgListener(this.newDFSMsgCallback.bind(this), this.mID);\nCVMContext.getInstance().addNewGridScriptResultListener(\n    this.newGridScriptResultCallback.bind(this), this.mID\n);\nCVMContext.getInstance().addNewTerminalDataListener(\n    this.newTerminalDataCallback.bind(this), this.mID\n);\nCVMContext.getInstance().addVMStateChangedListener(\n    this.VMStateChangedCallback.bind(this), this.mID\n);\n<\/pre>\n<h3>Rule 9: Track Network Request Ownership<\/h3>\n<p><strong>The rule:<\/strong> Register network request IDs and verify ownership in callbacks.<\/p>\n<p><strong>Why it exists:<\/strong> Multiple dApp instances share CVMContext event channels. Without checking request IDs, your callback would process data meant for another instance.<\/p>\n<pre>\n\/\/ Requesting data\nconst reqID = vmContext.genRequestID(); \/\/ Generate unique request ID\nthis.addNetworkRequestID(reqID);\n\n\/\/ In callback \u2014 verify this response is yours\nnewDFSMsgCallback(dfsMsg) {\n    if (!this.hasNetworkRequestID(dfsMsg.getReqID))\n        return; \/\/ Not our request \u2014 ignore\n\n    \/\/ Process the data\n    if (dfsMsg.getData1.byteLength > 0) {\n        let metaData = this.mMetaParser.parse(dfsMsg.getData1);\n        \/\/ ...\n    }\n}\n<\/pre>\n<h3>Rule 10: Clean Up in closeWindow()<\/h3>\n<p><strong>The rule:<\/strong> Always override <code>closeWindow()<\/code> to stop threads, clear timers, unregister listeners, and destroy resources. Always call <code>super.closeWindow()<\/code> at the end.<\/p>\n<p><strong>Why it exists:<\/strong> Without cleanup, threads keep running, timers keep firing, event listeners keep receiving data \u2014 all for a window that no longer exists. This causes memory leaks, phantom processing, and eventual browser tab crashes.<\/p>\n<p><strong>Production example<\/strong> \u2014 the Wallet dApp&#8217;s comprehensive cleanup:<\/p>\n<pre>\ncloseWindow() {\n    \/\/ Stop auto-lock timer\n    if (this.mAutoLockTimer) {\n        clearInterval(this.mAutoLockTimer);\n        this.mAutoLockTimer = null;\n    }\n\n    \/\/ Stop transaction tracking timer\n    if (this.mTxTrackingInterval) {\n        clearInterval(this.mTxTrackingInterval);\n        this.mTxTrackingInterval = null;\n    }\n\n    \/\/ Unregister all event listeners\n    this.mVMContext.unregisterEventListenerByID(this.mID);\n\n    \/\/ Clean up tables\n    if (this.mTxHistoryTable) {\n        this.mTxHistoryTable.destroy();\n    }\n    if (this.mRecentTxTable) {\n        this.mRecentTxTable.destroy();\n    }\n    if (this.mOutgressPoolsTable) {\n        this.mOutgressPoolsTable.destroy();\n    }\n    if (this.mIngressPoolsTable) {\n        this.mIngressPoolsTable.destroy();\n    }\n\n    \/\/ ALWAYS call parent close last\n    super.closeWindow();\n}\n<\/pre>\n<h2>V. CSS Rules: Designing for a Window, Not a Viewport<\/h2>\n<figure><img decoding=\"async\" src=\"https:\/\/gridnet.org\/wpp\/wp-content\/uploads\/2026\/02\/dapp-guidelines-dapp-guide-css-rules.png\" alt=\"CSS responsive design in windows\" \/><figcaption>CSS in GRIDNET OS \u2014 designing fluid layouts that respond to windows, not viewports.<\/figcaption><\/figure>\n<p>The CSS rules flow from one fundamental truth: your dApp lives in a resizable window, not a full-screen browser tab. Every rule below serves this reality.<\/p>\n<h3>Rule 1: No Viewport Units (vw\/vh)<\/h3>\n<p><strong>The rule:<\/strong> DO NOT use <code>vw<\/code> or <code>vh<\/code> units anywhere in your CSS.<\/p>\n<p><strong>Why it exists:<\/strong> <code>vw<\/code> and <code>vh<\/code> are relative to the browser viewport, not your CWindow container. A <code>width: 50vw<\/code> element will be half the <em>browser window<\/em> \u2014 which could be vastly larger or smaller than your dApp&#8217;s window. When the user resizes the dApp window, <code>vw<\/code>\/<code>vh<\/code> values don&#8217;t change, breaking your layout.<\/p>\n<p><strong>The strength:<\/strong> By using <code>%<\/code>, <code>flex<\/code>, and <code>grid<\/code> instead, your layout automatically responds to window resize, maximise, minimise, and snap \u2014 no media queries needed for basic responsiveness.<\/p>\n<p><strong>\u274c BAD \u2014 Viewport-relative sizing:<\/strong><\/p>\n<pre>\n.sidebar {\n    width: 25vw;     \/* Relative to browser, not dApp window! *\/\n    height: 100vh;   \/* Will overflow or underflow the window! *\/\n}\n\n.modal {\n    max-width: 80vw; \/* Meaningless inside a 400px window *\/\n    font-size: 3vw;  \/* Text size changes with browser zoom, not window size *\/\n}\n<\/pre>\n<p><strong>\u2705 GOOD \u2014 Container-relative sizing:<\/strong><\/p>\n<pre>\n.sidebar {\n    width: 25%;      \/* 25% of the dApp's container *\/\n    height: 100%;    \/* Full height of the container *\/\n}\n\n.modal {\n    max-width: 90%;  \/* Relative to the window content area *\/\n    font-size: 1.2rem; \/* Consistent, readable size *\/\n}\n<\/pre>\n<h3>Rule 2: No position: fixed or position: sticky<\/h3>\n<p><strong>The rule:<\/strong> DO NOT use <code>position: fixed<\/code> or <code>position: sticky<\/code> in ways that depend on the browser window.<\/p>\n<p><strong>Why it exists:<\/strong> <code>position: fixed<\/code> positions relative to the browser viewport, not your dApp&#8217;s window. A &#8220;fixed&#8221; header would float over other dApp windows or the OS shell. <code>position: sticky<\/code> can behave unpredictably within Shadow DOM scroll containers.<\/p>\n<p><strong>\u274c BAD \u2014 Escapes the window:<\/strong><\/p>\n<pre>\n.header {\n    position: fixed;  \/* Positions relative to browser viewport! *\/\n    top: 0;\n    left: 0;\n    width: 100%;\n    z-index: 9999;    \/* Floats above EVERYTHING, including other windows *\/\n}\n<\/pre>\n<p><strong>\u2705 GOOD \u2014 Contained within the window:<\/strong><\/p>\n<pre>\n.wallet-container {\n    position: absolute; \/* OK \u2014 relative to parent within Shadow DOM *\/\n    top: 0;\n    left: 0;\n    height: 100%;\n    width: 100%;\n    display: flex;\n    flex-direction: column;\n}\n\n.wallet-header {\n    \/* No position: fixed needed \u2014 flex layout keeps it at top *\/\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    height: 60px;\n    flex-shrink: 0; \/* Don't compress the header *\/\n}\n\n.wallet-content {\n    flex: 1;         \/* Takes remaining space *\/\n    overflow-y: auto; \/* Scrollable content area *\/\n}\n<\/pre>\n<p><strong>Production example<\/strong> \u2014 the Wallet dApp uses exactly this pattern:<\/p>\n<pre>\n.wallet-container {\n    height: 100%;\n    width: 100%;\n    position: absolute;\n    top: 0;\n    left: 0;\n    display: flex;\n    flex-direction: column;\n    overflow: hidden;\n}\n<\/pre>\n<h3>Rule 3: Use Flexbox and Grid for Layouts<\/h3>\n<p><strong>The rule:<\/strong> Use <code>display: flex<\/code> and <code>display: grid<\/code> with relative units (<code>%<\/code>, <code>fr<\/code>, <code>em<\/code>, <code>rem<\/code>) for all layouts.<\/p>\n<p><strong>Why it exists:<\/strong> Flex and grid layouts automatically adapt to their container size. Combined with the ban on viewport units, this gives you inherently responsive layouts that work at any window size.<\/p>\n<pre>\n\/* Three-column layout that adapts to window width *\/\n.dashboard-grid {\n    display: grid;\n    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n    gap: 1rem;\n    padding: 1rem;\n}\n\n\/* Vertical stack with fixed header\/footer, flexible content *\/\n.app-layout {\n    display: flex;\n    flex-direction: column;\n    height: 100%;\n}\n\n.app-header { flex-shrink: 0; }\n.app-content { flex: 1; overflow-y: auto; }\n.app-footer { flex-shrink: 0; }\n\n\/* Horizontal toolbar with even spacing *\/\n.toolbar {\n    display: flex;\n    gap: 0.5rem;\n    align-items: center;\n    flex-wrap: wrap; \/* Wraps on narrow windows *\/\n}\n<\/pre>\n<h3>Rule 4: No Pixel-Perfect Major Layouts<\/h3>\n<p><strong>The rule:<\/strong> DO NOT use absolute pixel positioning for major layout elements.<\/p>\n<p><strong>Why it exists:<\/strong> Pixel values don&#8217;t scale with window resize. Reserve pixels only for fine details: icon padding, border widths, small gaps.<\/p>\n<p><strong>\u274c BAD:<\/strong><\/p>\n<pre>\n.content {\n    width: 800px;   \/* What if the window is 600px wide? *\/\n    margin-left: 200px; \/* What if sidebar is different size? *\/\n}\n<\/pre>\n<p><strong>\u2705 GOOD:<\/strong><\/p>\n<pre>\n.content {\n    flex: 1;        \/* Takes remaining space after sidebar *\/\n    min-width: 0;   \/* Prevents flex overflow *\/\n}\n\n.sidebar {\n    width: 25%;\n    min-width: 150px;\n    max-width: 300px;\n}\n<\/pre>\n<h3>Rule 5: Avoid !important<\/h3>\n<p><strong>The rule:<\/strong> DO NOT use <code>!important<\/code> excessively.<\/p>\n<p><strong>Why it exists:<\/strong> <code>!important<\/code> breaks the CSS cascade and makes styles unmaintainable. Use more specific selectors instead. The <em>one acceptable use<\/em> is overriding third-party library styles (like Tabulator.js) that require specificity battles.<\/p>\n<p><strong>\u274c BAD:<\/strong><\/p>\n<pre>\n.button {\n    color: blue !important;\n    background: red !important;\n    padding: 10px !important;\n}\n<\/pre>\n<p><strong>\u2705 GOOD:<\/strong><\/p>\n<pre>\n\/* More specific selector instead of !important *\/\n.wallet-container .action-panel .button {\n    color: blue;\n    background: red;\n    padding: 10px;\n}\n\n\/* Acceptable: overriding third-party library *\/\n.tabulator-col-title {\n    color: #22fafc !important;  \/* Tabulator needs !important for theme overrides *\/\n}\n<\/pre>\n<h3>Rule 6: All Styles Scoped to Shadow DOM<\/h3>\n<p><strong>The rule:<\/strong> Define ALL styles within your dApp&#8217;s HTML\/CSS payload. Do not rely on external stylesheets not loaded inside your Shadow DOM.<\/p>\n<p><strong>Why it exists:<\/strong> Shadow DOM provides style isolation \u2014 styles from outside cannot leak in, and your styles cannot leak out. This is a security feature. But it means you must include everything you need.<\/p>\n<p><strong>The strength:<\/strong> You can use any class names without worrying about collisions. <code>.button<\/code>, <code>.container<\/code>, <code>.header<\/code> \u2014 these are yours and yours alone.<\/p>\n<pre>\nconst myBody = `\n&lt;!-- System stylesheet \u2014 provided by OS --&gt;\n&lt;link rel=\"stylesheet\" href=\"\/css\/windowDefault.css\" \/&gt;\n\n&lt;!-- YOUR styles \u2014 scoped to this Shadow DOM --&gt;\n&lt;style&gt;\n\/* No collision risk \u2014 .header is ONLY yours *\/\n.header {\n    background: linear-gradient(90deg, #090918, #141432);\n    border-bottom: 1px solid #22fafc;\n}\n\n\/* Class name 'button' won't affect any other dApp *\/\n.button {\n    background: linear-gradient(90deg, #22fafc, #00b4ff);\n    color: #0a0a14;\n}\n&lt;\/style&gt;\n\n&lt;div class=\"header\"&gt;...&lt;\/div&gt;\n`;\n<\/pre>\n<h3>Rule 7: No External CSS Variables<\/h3>\n<p><strong>The rule:<\/strong> DO NOT rely on CSS variables (<code>--my-color<\/code>) defined outside your Shadow DOM. Define all variables within your own scope.<\/p>\n<p><strong>Why it exists:<\/strong> CSS custom properties defined in the main document do <em>not<\/em> penetrate Shadow DOM boundaries (unless explicitly inherited). Never assume external variables exist.<\/p>\n<pre>\n\/* \u2705 Define your own variables *\/\n&lt;style&gt;\n:host {\n    --primary: #22fafc;\n    --bg-dark: #0a0a14;\n    --text: #e0e0ff;\n}\n\n.panel {\n    color: var(--text);\n    background: var(--bg-dark);\n    border-color: var(--primary);\n}\n&lt;\/style&gt;\n<\/pre>\n<h2>VI. CVMContext Interaction Patterns<\/h2>\n<figure><img decoding=\"async\" src=\"https:\/\/gridnet.org\/wpp\/wp-content\/uploads\/2026\/02\/dapp-guidelines-dapp-guide-cvmcontext.png\" alt=\"CVMContext event flow\" \/><figcaption>CVMContext \u2014 the single point of truth between your dApp and the decentralized world.<\/figcaption><\/figure>\n<p>All communication between your dApp and GRIDNET OS flows through the <code>CVMContext<\/code> singleton. Here are the essential interaction patterns:<\/p>\n<h3>Event Listener Registration<\/h3>\n<p>Register in the constructor, unregister in <code>closeWindow()<\/code>:<\/p>\n<pre>\nconstructor(...args) {\n    super(...args);\n    const vm = CVMContext.getInstance();\n\n    \/\/ Blockchain metadata responses\n    vm.addVMMetaDataListener(this.newVMMetaDataCallback.bind(this), this.mID);\n\n    \/\/ Decentralized File System responses\n    vm.addNewDFSMsgListener(this.newDFSMsgCallback.bind(this), this.mID);\n\n    \/\/ GridScript execution results\n    vm.addNewGridScriptResultListener(this.newGridScriptResultCallback.bind(this), this.mID);\n\n    \/\/ VM state changes (connection state, etc.)\n    vm.addVMStateChangedListener(this.VMStateChangedCallback.bind(this), this.mID);\n\n    \/\/ Terminal data (for terminal-type dApps)\n    vm.addNewTerminalDataListener(this.newTerminalDataCallback.bind(this), this.mID);\n}\n\ncloseWindow() {\n    CVMContext.getInstance().unregisterEventListenerByID(this.mID);\n    super.closeWindow();\n}\n<\/pre>\n<h3>Async Data Retrieval<\/h3>\n<pre>\nasync loadBlockchainStatus() {\n    try {\n        const vmContext = CVMContext.getInstance();\n        const status = await vmContext.getBlockchainStatusA(\n            this.getSystemThreadID(),\n            this,\n            eVMMetaCodeExecutionMode.RAW\n        );\n        this.updateUIWithStatus(status.data);\n    } catch (error) {\n        console.error('Failed to load status:', error);\n        this.showNotification('Error loading blockchain status', 'error');\n    }\n}\n<\/pre>\n<h3>Thread Management<\/h3>\n<pre>\n\/\/ Create a periodic background thread\ninitialize() {\n    this.mControllerThreadInterval = 1000; \/\/ 1 second\n    this.mControler = CVMContext.getInstance().createJSThread(\n        this.mControllerThreadF.bind(this),\n        this.getProcessID,\n        this.mControllerThreadInterval\n    );\n}\n\n\/\/ Thread function with mutex protection\nmControllerThreadF() {\n    if (this.mControllerExecuting) return false;\n    this.mControllerExecuting = true;\n\n    \/\/ Your periodic logic here\n    this.refreshData();\n\n    this.mControllerExecuting = false;\n}\n\n\/\/ Always stop in closeWindow()\ncloseWindow() {\n    if (this.mControler > 0) {\n        CVMContext.getInstance().stopJSThread(this.mControler);\n    }\n    super.closeWindow();\n}\n<\/pre>\n<h3>Settings Persistence<\/h3>\n<pre>\n\/\/ Load settings\nloadSettings() {\n    CVMContext.getInstance().getSettingsManager.loadSettings(CMyDApp.getPackageID());\n    return this.activateSettings();\n}\n\n\/\/ Save settings\nsaveSettings() {\n    let sets = CMyDApp.getSettings();\n    CVMContext.getInstance().getSettingsManager.saveAppSettings(sets);\n}\n\n\/\/ Static settings storage\nstatic getSettings() {\n    return CMyDApp.sCurrentSettings;\n}\n\nstatic setSettings(sets) {\n    if (!(sets instanceof CAppSettings)) return false;\n    CMyDApp.sCurrentSettings = sets;\n    return true;\n}\n\n\/\/ Default settings\nstatic getDefaultSettings() {\n    let obj = { theme: 'dark', refreshInterval: 30, version: 1 };\n    return new CAppSettings(CMyDApp.getPackageID(), obj);\n}\n\n\/\/ Initialize at bottom of file\nCMyDApp.sCurrentSettings = new CAppSettings(CMyDApp.getPackageID());\n<\/pre>\n<h2>VII. Window Lifecycle and Events<\/h2>\n<figure><img decoding=\"async\" src=\"https:\/\/gridnet.org\/wpp\/wp-content\/uploads\/2026\/02\/dapp-guidelines-dapp-guide-lifecycle.png\" alt=\"Window lifecycle\" \/><figcaption>The lifecycle of a GRIDNET OS window \u2014 from creation to destruction.<\/figcaption><\/figure>\n<figure><img decoding=\"async\" src=\"https:\/\/gridnet.org\/wpp\/wp-content\/uploads\/2026\/02\/dapp-guide-diagram-lifecycle-1.png\" alt=\"CWindow Lifecycle Diagram\" style=\"width:100%;max-width:1200px;\" \/><figcaption>The complete CWindow lifecycle: constructor &rarr; open() &rarr; runtime &rarr; closeWindow() &rarr; destroyed.<\/figcaption><\/figure>\n<p>Your dApp&#8217;s lifecycle follows a predictable sequence of events:<\/p>\n<h3>Constructor \u2192 open() \u2192 [Runtime] \u2192 closeWindow()<\/h3>\n<pre>\nclass CMyDApp extends CWindow {\n    \/\/ 1. CONSTRUCTOR \u2014 called when window object is created\n    constructor(positionX, positionY, width, height, data, dataType, filePath, thread) {\n        super(positionX, positionY, width, height, body, \"Title\", CMyDApp.getIcon(), true);\n\n        \/\/ Initialize instance state\n        this.mTools = CTools.getInstance();\n        this.mMetaParser = new CVMMetaParser();\n\n        \/\/ Register event listeners\n        CVMContext.getInstance().addVMMetaDataListener(\n            this.newVMMetaDataCallback.bind(this), this.mID\n        );\n    }\n\n    \/\/ 2. OPEN \u2014 called when window is displayed and ready for interaction\n    open() {\n        super.open(); \/\/ MUST call super\n        this.initialize();\n        \/\/ DOM is now accessible via getControl\/getBody\n        \/\/ Set up UI event listeners, populate content\n    }\n\n    \/\/ 3. RESIZE EVENTS \u2014 fired during window resize\n    finishResize(isFallbackEvent) {\n        super.finishResize(isFallbackEvent);\n        \/\/ React to new dimensions:\n        \/\/ this.getClientWidth, this.getClientHeight\n    }\n\n    stopResize(handle) {\n        super.stopResize(handle);\n    }\n\n    \/\/ 4. SCROLL EVENTS\n    onScroll(event) {\n        super.onScroll(event);\n    }\n\n    \/\/ 5. CLOSE \u2014 clean up everything\n    closeWindow() {\n        \/\/ Stop threads\n        if (this.mControler > 0) {\n            CVMContext.getInstance().stopJSThread(this.mControler);\n        }\n        \/\/ Unregister listeners\n        CVMContext.getInstance().unregisterEventListenerByID(this.mID);\n        \/\/ Destroy UI components\n        \/\/ ALWAYS call super last\n        super.closeWindow();\n    }\n}\n<\/pre>\n<h3>Curtain Control<\/h3>\n<p>The &#8220;curtain&#8221; is a loading overlay managed by CWindow. It appears during DOM mutations to prevent users from interacting with partially-rendered UI:<\/p>\n<pre>\n\/\/ Pause curtain during rapid DOM updates (e.g., slider interactions)\nthis.pauseCurtain(3); \/\/ Pause for 3 seconds\n\n\/\/ Whitelist elements that cause frequent DOM mutations\nthis.addCurtainWhitelist({\n    classNames: ['slider-handle', 'slider-track', 'live-counter'],\n    ids: ['main-balance-display', 'progress-indicator'],\n    maxDepth: 5  \/\/ Check up to 5 parent levels\n});\n\n\/\/ Manual curtain control for long operations\nthis.showCurtain(true, false, false);\n\/\/ ... perform long operation ...\nthis.hideCurtain();\n<\/pre>\n<h3>User Dialogs<\/h3>\n<pre>\n\/\/ Ask user for string input\nthis.askString('\u22ee\u22ee\u22ee Title', 'What is your question?', this.handleResponse.bind(this), true);\n\n\/\/ Handle the response\nhandleResponse(e) {\n    if (e.answer) {\n        const userInput = e.answer;\n        \/\/ Use the input\n    }\n}\n\n\/\/ Log notifications\nthis.mTools.logEvent('Operation complete!',\n    eLogEntryCategory.dApp, 0, eLogEntryType.notification, this);\n<\/pre>\n<h2>VIII. Responsive Design Patterns for Windowed Applications<\/h2>\n<figure><img decoding=\"async\" src=\"https:\/\/gridnet.org\/wpp\/wp-content\/uploads\/2026\/02\/dapp-guidelines-dapp-guide-responsive.png\" alt=\"Responsive window design\" \/><figcaption>Responsive design in a windowed world \u2014 where media queries meet window dimensions.<\/figcaption><\/figure>\n<p>Since you can&#8217;t use viewport units or media queries based on screen size, responsive design in GRIDNET OS uses a different approach: <strong>class-based breakpoints applied programmatically<\/strong> via JavaScript, combined with fluid CSS.<\/p>\n<h3>The Pattern: JS-Driven Breakpoint Classes<\/h3>\n<p>The Wallet dApp demonstrates this pattern in its <code>finishResize()<\/code> handler:<\/p>\n<pre>\nfinishResize(isFallbackEvent) {\n    super.finishResize(isFallbackEvent);\n\n    const width = this.getClientWidth;\n    const container = this.getControl('wallet-container');\n\n    \/\/ Apply breakpoint classes based on window width\n    container.classList.remove('wallet-narrow', 'wallet-mobile', 'wallet-tablet');\n\n    if (width &lt; 480) {\n        container.classList.add('wallet-narrow');\n    } else if (width &lt; 600) {\n        container.classList.add('wallet-mobile');\n    } else if (width &lt; 900) {\n        container.classList.add('wallet-tablet');\n    }\n}\n<\/pre>\n<p>Then in CSS, target these classes instead of media queries:<\/p>\n<pre>\n\/* Default (wide) layout *\/\n.wallet-header {\n    padding: 10px 20px;\n    height: 60px;\n}\n\n\/* Narrow window layout *\/\n.wallet-narrow .wallet-header {\n    padding: 8px 10px;\n    height: 50px;\n}\n\n.wallet-narrow .wallet-logo {\n    display: none; \/* Hide logo in very narrow windows *\/\n}\n\n.wallet-narrow .wallet-nav-item {\n    width: 30px;\n    height: 30px;\n}\n\n\/* Mobile-width window layout *\/\n.wallet-mobile .wallet-header {\n    padding: 10px 12px;\n    height: 54px;\n}\n\n.wallet-mobile .wallet-logo {\n    display: none;\n}\n<\/pre>\n<p>Note: CSS <code>@media<\/code> queries referencing <code>max-width<\/code> still work but they reference the <em>viewport<\/em> width, not the window width. The JS-driven approach gives you true window-responsive behavior.<\/p>\n<h2>IX. Complete Production-Ready Example: A Data Dashboard dApp<\/h2>\n<figure><img decoding=\"async\" src=\"https:\/\/gridnet.org\/wpp\/wp-content\/uploads\/2026\/02\/dapp-guidelines-dapp-guide-example.png\" alt=\"Complete example dApp\" \/><figcaption>From template to production \u2014 a complete working dApp demonstrating every pattern.<\/figcaption><\/figure>\n<p>Here is a complete, production-ready dApp demonstrating all the patterns covered in this guide \u2014 a blockchain data dashboard that queries blockchain status, displays it in a responsive layout, persists settings, and handles all lifecycle events correctly:<\/p>\n<pre>\n\"use strict\"\n\nimport { CWindow } from \"\/lib\/window.js\"\nimport { CVMMetaSection, CVMMetaEntry, CVMMetaGenerator, CVMMetaParser } from '\/lib\/MetaData.js'\nimport { CTools, CDataConcatenator } from '\/lib\/tools.js'\nimport { CAppSettings, CSettingsManager } from \"\/lib\/SettingsManager.js\"\nimport { CContentHandler } from \"\/lib\/AppSelector.js\"\n\nconst _private = new WeakMap();\n\nconst dashboardBody = `\n&lt;link rel=\"stylesheet\" href=\"\/css\/windowDefault.css\" \/&gt;\n&lt;style&gt;\n:host {\n    --primary: #22fafc;\n    --bg-dark: #0a0a14;\n    --bg-panel: rgba(10, 10, 25, 0.8);\n    --text: #e0e0ff;\n    --text-dim: #b0b0dd;\n    --border: rgba(34, 250, 252, 0.2);\n}\n.dashboard {\n    font-family: 'Rajdhani', 'Roboto', sans-serif;\n    color: var(--text);\n    background: var(--bg-dark);\n    height: 100%; width: 100%;\n    position: absolute; top: 0; left: 0;\n    display: flex; flex-direction: column;\n    overflow: hidden;\n}\n.dash-header {\n    background: linear-gradient(90deg, #090918, #141432);\n    border-bottom: 1px solid var(--primary);\n    padding: 10px 20px;\n    display: flex; justify-content: space-between; align-items: center;\n    flex-shrink: 0;\n}\n.dash-title {\n    font-size: 1.3rem; color: var(--primary);\n    text-shadow: 0 0 8px rgba(34, 250, 252, 0.4);\n}\n.dash-content {\n    flex: 1; padding: 20px; overflow-y: auto;\n}\n.cards {\n    display: grid;\n    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n    gap: 15px;\n}\n.card {\n    background: var(--bg-panel);\n    border: 1px solid var(--border);\n    border-radius: 4px; padding: 15px;\n}\n.card-label { color: var(--text-dim); font-size: 0.85rem; text-transform: uppercase; }\n.card-value { font-size: 1.5rem; color: var(--primary); font-weight: bold; margin-top: 5px; }\n.status-bar {\n    background: linear-gradient(90deg, #090918, #141432);\n    border-top: 1px solid var(--primary);\n    padding: 6px 20px;\n    font-size: 0.8rem; color: #808080;\n    flex-shrink: 0;\n    display: flex; justify-content: space-between;\n}\n.btn-refresh {\n    background: linear-gradient(90deg, rgba(34, 250, 252, 0.1), rgba(34, 250, 252, 0.2));\n    border: 1px solid rgba(34, 250, 252, 0.3);\n    border-radius: 4px; padding: 6px 12px;\n    color: var(--primary); cursor: pointer;\n    font-family: inherit; font-size: 0.85rem;\n}\n.btn-refresh:hover {\n    background: rgba(34, 250, 252, 0.2);\n    box-shadow: 0 0 10px rgba(34, 250, 252, 0.3);\n}\n.dash-narrow .dash-header { padding: 8px 12px; }\n.dash-narrow .dash-content { padding: 10px; }\n.dash-narrow .cards { grid-template-columns: 1fr; }\n&lt;\/style&gt;\n\n&lt;div class=\"dashboard\" id=\"dashboard-container\"&gt;\n    &lt;div class=\"dash-header\"&gt;\n        &lt;span class=\"dash-title\"&gt;Blockchain Dashboard&lt;\/span&gt;\n        &lt;button class=\"btn-refresh\" id=\"refresh-btn\"&gt;\u27f3 Refresh&lt;\/button&gt;\n    &lt;\/div&gt;\n    &lt;div class=\"dash-content\"&gt;\n        &lt;div class=\"cards\"&gt;\n            &lt;div class=\"card\"&gt;\n                &lt;div class=\"card-label\"&gt;Block Height&lt;\/div&gt;\n                &lt;div class=\"card-value\" id=\"block-height\"&gt;\u2014&lt;\/div&gt;\n            &lt;\/div&gt;\n            &lt;div class=\"card\"&gt;\n                &lt;div class=\"card-label\"&gt;Network Peers&lt;\/div&gt;\n                &lt;div class=\"card-value\" id=\"peer-count\"&gt;\u2014&lt;\/div&gt;\n            &lt;\/div&gt;\n            &lt;div class=\"card\"&gt;\n                &lt;div class=\"card-label\"&gt;Transactions Today&lt;\/div&gt;\n                &lt;div class=\"card-value\" id=\"tx-count\"&gt;\u2014&lt;\/div&gt;\n            &lt;\/div&gt;\n            &lt;div class=\"card\"&gt;\n                &lt;div class=\"card-label\"&gt;Connection State&lt;\/div&gt;\n                &lt;div class=\"card-value\" id=\"conn-state\"&gt;\u2014&lt;\/div&gt;\n            &lt;\/div&gt;\n        &lt;\/div&gt;\n    &lt;\/div&gt;\n    &lt;div class=\"status-bar\"&gt;\n        &lt;span id=\"last-update\"&gt;Last update: never&lt;\/span&gt;\n        &lt;span id=\"refresh-interval\"&gt;Auto-refresh: 30s&lt;\/span&gt;\n    &lt;\/div&gt;\n&lt;\/div&gt;\n`;\n\nclass CBlockchainDashboard extends CWindow {\n    constructor(positionX, positionY, width, height, data, dataType, filePath, thread) {\n        super(positionX, positionY, width, height, dashboardBody,\n              \"Blockchain Dashboard\", CBlockchainDashboard.getIcon(), true);\n\n        \/\/ Private data via WeakMap\n        _private.set(this, {\n            lastUpdateTime: null,\n            refreshCount: 0\n        });\n\n        \/\/ Instance state\n        this.mTools = CTools.getInstance();\n        this.mMetaParser = new CVMMetaParser();\n        this.mControllerThreadInterval = 30000; \/\/ 30 seconds\n        this.mControlerExecuting = false;\n        this.mControler = 0;\n\n        \/\/ Register for events\n        CVMContext.getInstance().addVMMetaDataListener(\n            this.newVMMetaDataCallback.bind(this), this.mID\n        );\n        CVMContext.getInstance().addNewDFSMsgListener(\n            this.newDFSMsgCallback.bind(this), this.mID\n        );\n        CVMContext.getInstance().addVMStateChangedListener(\n            this.onConnectionStateChanged.bind(this), this.mID\n        );\n    }\n\n    static getPackageID() {\n        return \"org.gridnetproject.UIdApps.blockchainDashboard\";\n    }\n\n    static getDefaultCategory() { return 'dApps'; }\n\n    static getIcon() { return ''; }\n\n    static getFileHandlers() {\n        return []; \/\/ No file associations\n    }\n\n    \/\/ \u2014 Lifecycle \u2014\n\n    open() {\n        super.open();\n        this.initialize();\n\n        \/\/ Set up UI event listeners\n        this.getControl('refresh-btn').addEventListener('click',\n            () => this.refreshData()\n        );\n\n        \/\/ Initial data load\n        this.refreshData();\n    }\n\n    initialize() {\n        \/\/ Load saved settings\n        if (this.loadSettings()) {\n            this.mTools.logEvent('[Dashboard] Settings loaded.',\n                eLogEntryCategory.dApp, 0, eLogEntryType.notification);\n        } else {\n            CBlockchainDashboard.setSettings(\n                CBlockchainDashboard.getDefaultSettings()\n            );\n        }\n\n        \/\/ Start auto-refresh thread\n        this.mControler = CVMContext.getInstance().createJSThread(\n            this.mControllerThreadF.bind(this),\n            this.getProcessID,\n            this.mControllerThreadInterval\n        );\n    }\n\n    closeWindow() {\n        if (this.mControler > 0) {\n            CVMContext.getInstance().stopJSThread(this.mControler);\n        }\n        CVMContext.getInstance().unregisterEventListenerByID(this.mID);\n        _private.delete(this);\n        super.closeWindow();\n    }\n\n    \/\/ \u2014 Resize handling \u2014\n\n    finishResize(isFallbackEvent) {\n        super.finishResize(isFallbackEvent);\n        const container = this.getControl('dashboard-container');\n        container.classList.remove('dash-narrow');\n        if (this.getClientWidth &lt; 500) {\n            container.classList.add('dash-narrow');\n        }\n    }\n\n    \/\/ \u2014 Data refresh \u2014\n\n    mControllerThreadF() {\n        if (this.mControlerExecuting) return false;\n        this.mControlerExecuting = true;\n        this.refreshData();\n        this.mControlerExecuting = false;\n    }\n\n    async refreshData() {\n        try {\n            const vm = CVMContext.getInstance();\n            const status = await vm.getBlockchainStatusA(\n                this.getSystemThreadID(), this, eVMMetaCodeExecutionMode.RAW\n            );\n            if (status &amp;&amp; status.data) {\n                this.updateDashboard(status.data);\n            }\n        } catch (err) {\n            console.error('[Dashboard] Refresh failed:', err);\n        }\n    }\n\n    updateDashboard(data) {\n        const priv = _private.get(this);\n        priv.lastUpdateTime = new Date();\n        priv.refreshCount++;\n\n        if (data.blockHeight !== undefined) {\n            this.getControl('block-height').textContent =\n                data.blockHeight.toLocaleString();\n        }\n        if (data.peerCount !== undefined) {\n            this.getControl('peer-count').textContent = data.peerCount;\n        }\n        if (data.txCount !== undefined) {\n            this.getControl('tx-count').textContent =\n                data.txCount.toLocaleString();\n        }\n\n        this.getControl('last-update').textContent =\n            'Last update: ' + priv.lastUpdateTime.toLocaleTimeString();\n    }\n\n    \/\/ \u2014 Event callbacks \u2014\n\n    onConnectionStateChanged(eventData) {\n        const stateEl = this.getControl('conn-state');\n        if (eventData &amp;&amp; eventData.state !== undefined) {\n            stateEl.textContent = eventData.state === eConnectionState.connected\n                ? '\ud83d\udfe2 Connected' : '\ud83d\udd34 Disconnected';\n        }\n    }\n\n    newVMMetaDataCallback(msg) {\n        if (!this.hasNetworkRequestID(msg.getReqID)) return;\n        \/\/ Process metadata response\n    }\n\n    newDFSMsgCallback(dfsMsg) {\n        if (!this.hasNetworkRequestID(dfsMsg.getReqID)) return;\n        \/\/ Process DFS response\n    }\n\n    \/\/ \u2014 Settings \u2014\n\n    static getSettings() { return CBlockchainDashboard.sCurrentSettings; }\n    static setSettings(sets) {\n        if (!(sets instanceof CAppSettings)) return false;\n        CBlockchainDashboard.sCurrentSettings = sets;\n        return true;\n    }\n\n    loadSettings() {\n        CVMContext.getInstance().getSettingsManager.loadSettings(\n            CBlockchainDashboard.getPackageID()\n        );\n        return this.activateSettings();\n    }\n\n    activateSettings() {\n        const sets = CBlockchainDashboard.getSettings();\n        if (!sets || typeof sets.getVersion === 'undefined') return false;\n        if (sets.getVersion !== 1) return false;\n        const data = sets.getData;\n        if (!data) return false;\n        if (data.refreshInterval) {\n            this.mControllerThreadInterval = data.refreshInterval * 1000;\n        }\n        return true;\n    }\n\n    saveSettings() {\n        const sets = CBlockchainDashboard.getSettings();\n        CVMContext.getInstance().getSettingsManager.saveAppSettings(sets);\n    }\n\n    static getDefaultSettings() {\n        return new CAppSettings(CBlockchainDashboard.getPackageID(), {\n            refreshInterval: 30,\n            version: 1\n        });\n    }\n}\n\nCBlockchainDashboard.sCurrentSettings = new CAppSettings(\n    CBlockchainDashboard.getPackageID()\n);\n\nexport default CBlockchainDashboard;\n<\/pre>\n<h2>X. Deployment Workflow<\/h2>\n<figure><img decoding=\"async\" src=\"https:\/\/gridnet.org\/wpp\/wp-content\/uploads\/2026\/02\/dapp-guidelines-dapp-guide-deploy.png\" alt=\"Deployment pipeline\" \/><figcaption>From code to the decentralised network \u2014 the deployment pipeline.<\/figcaption><\/figure>\n<figure><img decoding=\"async\" src=\"https:\/\/gridnet.org\/wpp\/wp-content\/uploads\/2026\/02\/dapp-guide-diagram-deploy-1.png\" alt=\"Deployment Pipeline Diagram\" style=\"width:100%;max-width:1200px;\" \/><figcaption>The five-step deployment pipeline: Bundle &rarr; Rename &rarr; Upload &rarr; Analyse &rarr; Commit to DFS.<\/figcaption><\/figure>\n<p>Deploying your dApp to GRIDNET OS follows five steps:<\/p>\n<ol>\n<li><strong>Bundle<\/strong> \u2014 Combine all your custom JavaScript and non-system dependencies into a single file using Rollup, Webpack, or Parcel. System libraries (<code>\/lib\/*<\/code>) and OS-provided libraries (Tabulator, Plotly) should <em>not<\/em> be bundled.<\/li>\n<li><strong>Rename<\/strong> \u2014 Change the file extension from <code>.js<\/code> to <code>.app<\/code> (e.g., <code>BlockchainDashboard.app<\/code>).<\/li>\n<li><strong>Upload<\/strong> \u2014 Connect to the GRIDNET OS UI at <code>https:\/\/ui.gridnet.org<\/code> (or your local instance). Drag and drop the <code>.app<\/code> file onto the Desktop or into the File Manager dApp.<\/li>\n<li><strong>Analyse &amp; Install<\/strong> \u2014 The OS analyses the package. Events appear in the log pane. Once analysed, your dApp&#8217;s icon appears on the Desktop. You can run it in &#8220;sandbox&#8221; mode for testing.<\/li>\n<li><strong>(Optional) Commit to DFS<\/strong> \u2014 To make the dApp persistent and available across the decentralised network, select the <code>.app<\/code> file in the File Manager and use the \u22ee\u22ee\u22ee Magic Button to commit it to the Decentralised File System. This requires GNC for storage fees.<\/li>\n<\/ol>\n<h2>XI. Going Deeper \u2014 GridScript: A Familiar Shell with Blockchain Superpowers<\/h2>\n<p>Most UI dApp developers will never need to write GridScript directly \u2014 <code>CVMContext<\/code> abstracts the blockchain layer into clean JavaScript calls. But when you <em>do<\/em> venture deeper \u2014 for advanced operations, custom smart contracts, or direct blockchain manipulation via the Terminal dApp \u2014 you&#8217;ll find a command set that feels like coming home.<\/p>\n<p>GridScript&#8217;s commands are deliberately modelled on Linux\/UNIX and DOS conventions:<\/p>\n<table>\n<thead>\n<tr>\n<th>GridScript Command<\/th>\n<th>Linux\/DOS Equivalent<\/th>\n<th>What It Does in GRIDNET OS<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>ls<\/code><\/td>\n<td><code>ls<\/code> \/ <code>dir<\/code><\/td>\n<td>List contents of the current domain (blockchain namespace)<\/td>\n<\/tr>\n<tr>\n<td><code>cd<\/code><\/td>\n<td><code>cd<\/code><\/td>\n<td>Change current domain (navigate the blockchain namespace tree)<\/td>\n<\/tr>\n<tr>\n<td><code>cat<\/code><\/td>\n<td><code>cat<\/code> \/ <code>type<\/code><\/td>\n<td>Display contents of a blockchain state entry<\/td>\n<\/tr>\n<tr>\n<td><code>mkdir<\/code><\/td>\n<td><code>mkdir<\/code><\/td>\n<td>Create a new domain (blockchain namespace)<\/td>\n<\/tr>\n<tr>\n<td><code>touch<\/code><\/td>\n<td><code>touch<\/code><\/td>\n<td>Create a new state entry<\/td>\n<\/tr>\n<tr>\n<td><code>rm<\/code><\/td>\n<td><code>rm<\/code> \/ <code>del<\/code><\/td>\n<td>Remove a state entry or domain<\/td>\n<\/tr>\n<tr>\n<td><code>chown<\/code><\/td>\n<td><code>chown<\/code><\/td>\n<td>Change ownership of a domain<\/td>\n<\/tr>\n<tr>\n<td><code>setfacl<\/code><\/td>\n<td><code>setfacl<\/code><\/td>\n<td>Set access control lists on blockchain resources<\/td>\n<\/tr>\n<tr>\n<td><code>getfacl<\/code><\/td>\n<td><code>getfacl<\/code><\/td>\n<td>View access control lists<\/td>\n<\/tr>\n<tr>\n<td><code>send<\/code><\/td>\n<td><em>(blockchain-native)<\/em><\/td>\n<td>Transfer GNC tokens to another domain<\/td>\n<\/tr>\n<tr>\n<td><code>BT<\/code><\/td>\n<td>SQL <code>BEGIN<\/code><\/td>\n<td>Begin a blockchain transaction<\/td>\n<\/tr>\n<tr>\n<td><code>CT<\/code><\/td>\n<td>SQL <code>COMMIT<\/code><\/td>\n<td>Commit the transaction to the blockchain<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The analogy to SQL is particularly illuminating: just as a database transaction groups multiple operations into an atomic unit with <code>BEGIN<\/code> and <code>COMMIT<\/code>, GridScript&#8217;s <code>BT<\/code>\/<code>CT<\/code> pair groups blockchain operations into an atomic on-chain transaction. If you&#8217;ve ever written <code>BEGIN; UPDATE ...; INSERT ...; COMMIT;<\/code>, you already understand the pattern \u2014 except now the database is a global, decentralized, tamper-proof state machine.<\/p>\n<p>Your UI dApp typically invokes these operations <em>indirectly<\/em> through <code>CVMContext<\/code> methods \u2014 you don&#8217;t type <code>ls<\/code> in your JavaScript. But understanding the underlying model makes debugging easier, the Terminal dApp immediately useful, and the entire system less mysterious. GRIDNET OS was designed to feel familiar to anyone who&#8217;s ever opened a terminal.<\/p>\n<h2>XII. Quick Reference Card<\/h2>\n<table>\n<thead>\n<tr>\n<th>Category<\/th>\n<th>DO<\/th>\n<th>DON&#8217;T<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>DOM Access<\/td>\n<td><code>this.getControl('id')<\/code>, <code>this.getBody.querySelector()<\/code><\/td>\n<td><code>document.getElementById()<\/code>, <code>document.querySelector()<\/code><\/td>\n<\/tr>\n<tr>\n<td>State<\/td>\n<td><code>this.mMyVar<\/code>, <code>WeakMap<\/code><\/td>\n<td>Global variables, <code>window.myVar<\/code><\/td>\n<\/tr>\n<tr>\n<td>CSS Sizing<\/td>\n<td><code>%<\/code>, <code>em<\/code>, <code>rem<\/code>, <code>fr<\/code>, <code>flex<\/code><\/td>\n<td><code>vw<\/code>, <code>vh<\/code><\/td>\n<\/tr>\n<tr>\n<td>Positioning<\/td>\n<td><code>relative<\/code>, <code>absolute<\/code> (within container), <code>flex<\/code><\/td>\n<td><code>fixed<\/code>, <code>sticky<\/code><\/td>\n<\/tr>\n<tr>\n<td>Layout<\/td>\n<td><code>display: flex<\/code>, <code>display: grid<\/code><\/td>\n<td>Pixel-perfect absolute positioning<\/td>\n<\/tr>\n<tr>\n<td>OS Interaction<\/td>\n<td><code>CVMContext.getInstance()<\/code><\/td>\n<td>Direct WebSocket, <code>fetch()<\/code> to blockchain<\/td>\n<\/tr>\n<tr>\n<td>Event Listeners<\/td>\n<td><code>callback.bind(this)<\/code>, arrow functions<\/td>\n<td>Unbound method references<\/td>\n<\/tr>\n<tr>\n<td>Cleanup<\/td>\n<td>Stop threads, unregister listeners in <code>closeWindow()<\/code><\/td>\n<td>Leaving threads running, listeners leaking<\/td>\n<\/tr>\n<tr>\n<td>Deployment<\/td>\n<td>Single <code>.app<\/code> file, system imports<\/td>\n<td>Multi-file deployments, bundled system libs<\/td>\n<\/tr>\n<tr>\n<td>Styles<\/td>\n<td>Scoped within Shadow DOM, own CSS variables<\/td>\n<td>External CDN stylesheets, external CSS vars<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>XIII. Further Reading<\/h2>\n<ul>\n<li><a href=\"https:\/\/gridnet.org\/wpp\/?p=4152\">Hello World UI dApp Tutorial<\/a> \u2014 Build your first dApp step by step<\/li>\n<li><a href=\"https:\/\/gridnet.org\/wpp\/?p=94437\">CVMContext Documentation<\/a> \u2014 Complete API reference for the GRIDNET OS JavaScript gateway<\/li>\n<li><a href=\"https:\/\/gridnet.org\/wpp\/?p=666579\">Blockchain Explorer API<\/a> \u2014 Query blockchain data, blocks, transactions, and domains<\/li>\n<li><a href=\"https:\/\/github.com\/GRIDNETOS\/GRIDNETOS\">GRIDNET OS GitHub Repository<\/a> \u2014 Source code, templates, and examples<\/li>\n<\/ul>\n<p><em>Welcome to the decentralised future. Build something extraordinary.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>GRIDNET OS UI dApp Design Guidelines \u2014 The Complete Developer Reference Everything a third-party developer needs to build production-grade decentralized applications for&#8230;<\/p>\n","protected":false},"author":1,"featured_media":835402,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[162,125,10,17],"tags":[157,155,154,151,197,158,161,160,142,156,153,159,152,212],"class_list":["post-835333","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development","category-documentation","category-os-news","category-tutorial","tag-css","tag-cvmcontext","tag-cwindow","tag-dapp-development","tag-dapps","tag-decentralized-applications","tag-design-guidelines","tag-developer-guide","tag-gridnet-os","tag-javascript","tag-shadow-dom","tag-tutorial","tag-ui-dapp","tag-ui-development"],"_links":{"self":[{"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/posts\/835333","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/comments?post=835333"}],"version-history":[{"count":8,"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/posts\/835333\/revisions"}],"predecessor-version":[{"id":835531,"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/posts\/835333\/revisions\/835531"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/media\/835402"}],"wp:attachment":[{"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/media?parent=835333"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/categories?post=835333"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/tags?post=835333"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}