Skip to content

miadey/MotoView

Repository files navigation

MotoView

Write Motoko. Ship interactive, SEO-friendly ICP apps. No frontend JavaScript.

status: examples verified end-to-end compiler: Rust runtime: Motoko client: WASM platform: Internet Computer protocol: motoview/1 license: MIT

MotoView is a Motoko-native, server-driven UI framework for Internet Computer (ICP) canisters. You write .mview files — template markup and Motoko code together in a single file — and MotoView compiles them to Motoko and deploys them to the IC with dfx. If you've enjoyed Blazor, Blade, or LiveView, this will feel familiar.

There is no application JavaScript, no React, no agent-js glue, and no duplicated validation. Your canister renders the page, your handlers run on-chain, and the browser stays in sync automatically.

Rendering is a query, events are updates, and the browser synchronizes through versioned UI batches.

Why MotoView

Building a frontend for an ICP canister usually means standing up a second project: a JavaScript bundler, a React app, agent-js to call the canister, and a second copy of your validation logic that has to stay in lockstep with the backend. MotoView removes that whole layer.

  • One language, one source of truth. Markup and Motoko live side by side in .mview files. Your validation runs in your handlers — there is no second copy to keep in sync.
  • No Node, no npm, no JS build tooling. This is a core design goal, not an accident. The compiler is a Rust binary, the runtime is a Motoko library, and the only browser code is a small WebAssembly client. You never run a JavaScript bundler.
  • Server-driven UI. The canister renders HTML. The first load is fully server-rendered for SEO and fast paint; after that, the browser synchronizes through versioned UI batches.
  • Events are real Motoko functions. A @click="save" dispatches to a typed Motoko handler that runs on-chain. Handler arguments are evaluated server-side and baked into the markup — you just write the handler.
  • Security in the platform. Secure forms mint a signed HMAC-SHA256 token binding the path, handler, caller principal, a nonce, an expiry, and the field-schema hash. The server re-derives the MAC and rejects mismatches, expired tokens, and replays. SHA-256 and HMAC are implemented in Motoko and verified against standard test vectors.
  • Adaptive, considerate polling. The client polls fast right after an interaction and backs off when the tab is idle or hidden, so live updates feel instant without hammering the canister.

Quick Start

1. Install the prerequisites

MotoView builds on the standard IC toolchain plus the Rust WebAssembly target.

# dfx — the DFINITY SDK
sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)"

# Rust + the wasm32 target (for the browser client)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown

# wasm-opt (from binaryen) for optimizing the client wasm
brew install binaryen   # or your platform's package manager

2. Build and install the compiler

The motoview compiler is a Rust crate. Build and install it from this repository with cargo.

cargo install --path compiler

3. Scaffold a project

motoview new my-app
cd my-app

4. Run it locally

motoview dev builds your .mview files to Motoko, deploys to a local replica with dfx, and watches for changes.

motoview dev

Then open the local URL printed by dfx in your browser. Click a button — the event hits the canister, your Motoko handler runs, a new UI batch comes back, and the DOM updates. No page reload, no JavaScript you had to write.

A counter in one file

This is src/Pages/Counter.mview from the verified counter example — markup and Motoko, together:

@page "/"
@layout MainLayout
@title "Counter"
@description "A live counter built with MotoView — rendering is a query, the +/- clicks are updates."

<section class="mv-container">
    <h1>Counter</h1>

    <p class="counter-value">Current value: <strong>@count</strong></p>

    <div class="counter-actions">
        <button class="mv-btn mv-btn-primary" @click="increment(1)">+1</button>
        <button class="mv-btn mv-btn-primary" @click="increment(5)">+5</button>
        <button class="mv-btn mv-btn-secondary" @click="decrement">-1</button>
        <button class="mv-btn mv-btn-ghost" @click="reset">Reset</button>
    </div>
</section>

@code {
    var count : Nat = 0;

    func increment(by : Nat) : async () {
        count += by;
    };

    func decrement() : async () {
        if (count > 0) { count -= 1 };
    };

    func reset() : async () {
        count := 0;
    };
}

The @click="increment(5)" handler argument is evaluated server-side at render time and baked into the markup; the WASM client forwards the handler id and its args, and the server dispatches to the typed increment function.

Verified status

The runtime, the WASM client, and the dfx pipeline are verified end to end in a real browser on a local replica. Six examples ship under examples/, each compiled from .mview and exercised live:

  • examples/counter — the canonical demo. Clicking updates state via event -> update -> batch -> DOM swap; adaptive polling picks up external changes; state persists across calls.
  • examples/contact — a secure, server-validated form. Invalid submits show persistent field errors; valid submits succeed. HMAC-SHA256 tokens + replay protection (verified against standard SHA-256/HMAC test vectors).
  • examples/crm — a CRM sales-pipeline Kanban with drag-and-drop between stages (plus ◀ ▶ move buttons), create/delete, live pipeline totals, toast notifications, and column pulse animations. The deal logic lives in src/Services/Crm.mo (real Motoko); the page holds the state. No frontend JavaScript — the drag-and-drop is the WASM client talking to on-chain handlers.
  • examples/products — a full CRUD catalog: a keyed product table, a secure server-validated create form, an edit form on a typed route (/edit/{id:Nat}), and delete — backed by an upgrade-stable Catalog service.
  • examples/blog — an SEO-first blog: per-post <title> / <meta description> / <link canonical>, server-rendered, with each post served as a @cacheable certified query and a 404 fallback for unknown slugs.
  • examples/svg-network — an interactive SVG network graph: click a node to highlight it and its neighbors (a side panel lists them), reset to clear — all server-driven, no frontend JavaScript.
cd examples/crm && dfx start --background && dfx deploy
# open the printed http://<canister-id>.localhost:4955/  — drag a card between columns

Beyond the examples, apps/bzzz is a production-scale dogfood: Bzzz, a Discord × X × Discourse-style-forum × WhatsApp super-app — 9 stateful services and 15 pages, with Internet Identity login, upgrade-stable persistence, and @cacheable certified pages. It's how the Production features below were built and verified, including on the IC mainnet boundary via the playground.

Features

  • .mview files: template markup plus Motoko in one file.
  • Directives: @page (including typed routes like @page "/orders/{id:Nat}"), @layout, @title, @description, @canonical, @meta, @authorize, @cacheable, @section/@yield, @slot, @code, @style, @theme, @if/else, @for, @switch, inline output (@count, @user.name, @(expr)), @effect (Focus / ScrollTo / Toast), and @animate.
  • Events: @click, @submit, @input, @change — dispatched to typed Motoko functions, with handler args evaluated server-side.
  • Secure forms with bind="@model.field", signed HMAC-SHA256 tokens, and replay/expiry rejection.
  • Handler-side validation (validate model { ... }) with <ValidationSummary /> and per-field errors.
  • Built-in semantic components — Button, Card, Alert, Badge, InputText / InputEmail / InputNumber / TextArea, ValidationSummary, Table, PageHeader, Grid — plus your own components in src/Components/*.mview.
  • Drag-and-drop: mark cards with data-mv-drag and drop zones with data-mv-drop / data-mv-dropval; a drop dispatches a typed Motoko handler — fully server-driven (see the CRM example).
  • Effects from handlers: call toast("Saved"), animate("#panel", "pulse"), focusOn("#email") or scrollTo(...) from @code and they ride back in the render batch.
  • Services & models: put real Motoko in src/Services/*.mo and src/Models/*.mo; the compiler imports them into the generated actor so page @code can call them.
  • The motoview/1 protocol: server-rendered first load, batch-based sync with changed / unchanged / redirect / validation-error statuses, and content-hashed batchIds so unchanged batches skip re-rendering.
  • Adaptive polling: hot (~350ms after an interaction), warm (~2.5s while visible), cold (~15s when idle), hidden (~45s), and exponential backoff when offline — with event responses returning the new batch immediately.

Production features

These shipped while building the Bzzz reference app (see below) and are verified end-to-end — locally and, where it matters, against the IC mainnet boundary on the playground.

  • Upgrade-stable persistence. A service that exposes mvStableSave() : Blob / mvStableLoad(Blob) (a Candid round-trip) gets an auto-generated stable var plus preupgrade/postupgrade hooks, so its state survives dfx deploy --mode upgrade. Opt-in, per service, idempotent. See docs/persistence.md.
  • Internet Identity login — no npm, no agent-js. A hand-written browser IC agent (Ed25519 / CBOR / request-id / Candid, pure JS) makes one authenticated call; the runtime mints an httpOnly session cookie and resolves ctx.caller from it. Every canister serves it at /mv-auth.js (auto-injected) — drop in a <button data-mv-signin> and you have login. Includes per-principal session revocation and ii-alternative-origins.
  • Certified query rendering. Static framework assets and pages marked @cacheable are served as fast certified queries (HTTP response-certification v2) instead of upgrading to an update call. Parameterized cacheable routes are covered by a single wildcard certificate (/u/{handle}/u/<*>). Implemented from scratch in Motoko (runtime/src/CertV2.mo) and verified accepted by the mainnet boundary.
  • App components. src/Components/*.mview compile to real typed render functions — params with defaults, the default @children slot, and named @slot / @section.
  • Hardened auth & forms. raw_rand-derived HMAC secret (never derived from public input), login-CSRF nonce binding, a pending-login DoS cap, origin-pinned postMessage, and httpOnly+Secure cookies.
  • motoview check. Builds and type-checks the generated actor, mapping any moc errors back to the originating .mview (not the generated main.mo).
  • Regression test suite. make test runs the parser/codegen tests that pin the framework's behavior.

Repository layout

compiler/   Rust crate — the motoview binary (parses .mview, generates Motoko)
runtime/    Motoko library (the "motoview" mops package) — serves HTTP from the canister
client/     Rust → WebAssembly browser client + tiny hand-written JS glue (no bundler)
examples/   counter, contact (secure form), crm (Kanban), products (CRUD), blog (SEO), svg-network
apps/       bzzz (reference super-app) and site (this docs + marketing site, itself a MotoView canister)
docs/       documentation (markdown — the source the site renders)
skills/     AI agent skills for working with MotoView

A typical generated project uses: src/Pages/*.mview, src/Layouts/*.mview, src/Components/*.mview, src/Services/*.mo, src/Models/*.mo, plus motoview.json, dfx.json, and mops.toml. The compiler emits a Motoko actor.

Using AI

MotoView ships an agent skill under skills/motoview/ so AI coding tools can scaffold pages, wire up events, and write secure forms that match the framework's real APIs. See docs/ai-tools.md for how to point your assistant at it.

Documentation

Full documentation lives in docs/ — start with docs/introduction.md and docs/quickstart.md, then dig into pages and routing, events, forms, validation, security, components, and the protocol.

Roadmap

Internet Identity login, certified query rendering, and upgrade-stable persistence have shipped (see Production features). Still planned and not yet implemented:

  • Keyed-region / granular DOM patches (instead of root swaps).
  • Role stores for @authorize role="…".
  • vetKeys-encrypted state.
  • A schema-migration story for evolving persisted service types.
  • A visual designer.

Realtime is the adaptive-polling render/event protocol — there's no separate push transport, and a canister can't open a WebSocket without an external gateway. The polling protocol is the communication layer.

License

MotoView is released under the MIT License.

About

MotoView — a Motoko-native, server-driven UI framework for the Internet Computer. Write .mview files (markup + Motoko), no frontend JavaScript. Rendering is a query, events are updates.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors