---
name: mmo-client
description: Create or edit ".jhs" files (JavaScript+HTML+Style in one file) for MMO's client-side UI framework (mmo.client, namespace MMO\client / JS namespace MMO.elements.*), i.e. write custom elements (<mmo-xxx>) or standalone views (<view id="...">) using the View/Template reactive-binding runtime and the Simple CSS (.scs) design system — tokens like $color.brand, lib::/controls:: recipes (the old theme.* Sass mixins are gone). Use whenever a file has the .jhs extension, lives under a project's src/customElements/ or theme/jhs/ folder, is a project's theme stylesheet (.scs), or the user asks to create/edit an MMO custom element, an MMO view/template, the theme's styles, or mentions "jhs", "scs", "MMO.elements.View", "MMO.elements.CustomElement/CustomShadowElement", or the mmo.client package. This is the client-side sibling of the `mmoframework` skill (mmo.core, PHP) — use that one for backend code in the same project.
---
# mmo.client / JHS
`mmo.client` is Marc Mouret's client-side UI library (package `mmo.client`, PHP namespace `MMO\client`, JS namespace `MMO.elements.*` / `MMO.core.*`). It defines Web Components and templates from a single custom file format, **`.jhs`** ("JavaScript, HTML, Style"): one file holds the markup, the element's JS class, and its styles — Simple CSS (`.scs`), the language the whole design system is written in. A PHP compiler (`MMO\client\JHSCompiler`) turns each `.jhs` file into a `.js` file that is then run through Google Closure. Nothing about `.jhs` syntax is standard HTML/JS — it has its own small template DSL (reactive bindings, loops, conditionals) layered on top.
This skill was built by reading the compiler source (`src/JHSCompiler.php`, `src/jhs/*.php`), the runtime (`src/js/core/Template.js`, `src/js/elements/View.js`, `src/js/elements/CustomElement.js` and friends), the design system (`src/styles/*.scs`, `src/Theme.php`), and real `.jhs` files in this repo — not from the (partially stale) `docs/JHS.md`. Where they disagree, trust this skill and the source.
The public MCP server `https://mcp.mmoframework.fr/mcp.json` (`claude mcp add --transport http mmoframework https://mcp.mmoframework.fr/mcp.json`) serves mmo.client's documentation and API from any machine: when it is connected, `search_documentation` for an element, a `.jhs` construct or a `.scs` token before guessing.
## Before you write anything
1. **Decide: custom element or view?**
- A **custom element** (`<mmo-xxx>` root tag) is a real Web Component with a shadow DOM, its own attributes/lifecycle, registered globally via `customElements.define`. Use it for anything reusable and self-contained (inputs, widgets, layout pieces). Lives under `src/customElements/{elements,inputs,layouts,modules,tools}/mmo-xxx.jhs` in this repo.
- A **view** (`<view id="...">` root tag) is just a named, reusable template fragment bound to a plain data `Map` — no shadow DOM, no custom tag, no lifecycle. Use it for a chunk of markup you want to render into an existing element (`src/customElements/views/*.jhs` in this repo, PascalCase filenames).
2. **Read [jhs-syntax.md](https://mcp.mmoframework.fr/search/read?uri=mmoframework-skill%3A%2F%2Fmmo.client%2Fmmo-client%2Freferences%2Fjhs-syntax.md) first** — the template DSL (`{path}` / `{=js}` expressions, `<for>`, `<if>/<elseif>/<else>`, special attributes) is unlike anything in mainstream frameworks and every mistake here is a compile-time or silent-runtime failure.
3. **Pick a base class** for the `<script>` class — see [custom-elements.md](https://mcp.mmoframework.fr/search/read?uri=mmoframework-skill%3A%2F%2Fmmo.client%2Fmmo-client%2Freferences%2Fcustom-elements.md#base-classes). `CustomShadowElement` covers ~all cases; `Widget`/`CustomAPIElement` add server-data plumbing.
4. **Styling is Simple CSS and always goes through the design tokens (`$color.brand`, `$space.md`, `$radius.sm`) and the `lib::`/`controls::` recipes**, never raw hex colors or magic-number spacing — see [styling.md](https://mcp.mmoframework.fr/search/read?uri=mmoframework-skill%3A%2F%2Fmmo.client%2Fmmo-client%2Freferences%2Fstyling.md). A plain `<style>` is already `.scs` and the compiler prepends the prelude for you: `@import "shadow.scs";` in an element, `@import "controls.scs";` in a view — which emits no rule, since a view rendered in the document adds its css to the whole page. The sass design system (`theme.*` mixins) is archived: don't write sass.
5. **Look at a real, similar element** in `src/customElements/**/*.jhs` before writing one from scratch — [examples.md](https://mcp.mmoframework.fr/search/read?uri=mmoframework-skill%3A%2F%2Fmmo.client%2Fmmo-client%2Freferences%2Fexamples.md) walks through three (simple attribute element, element with `<for>`/`<if>`, standalone view).
## Reference map
| Reference | Load it when you're... |
|---|---|
| [jhs-syntax.md](https://mcp.mmoframework.fr/search/read?uri=mmoframework-skill%3A%2F%2Fmmo.client%2Fmmo-client%2Freferences%2Fjhs-syntax.md) | writing markup: expressions, `<for>`, `<if>`, `<view>`, `<fn>`, special attributes (`value`, `set-xxx`, `bind-xxx`, `on*`, `jhs-compile`) |
| [custom-elements.md](https://mcp.mmoframework.fr/search/read?uri=mmoframework-skill%3A%2F%2Fmmo.client%2Fmmo-client%2Freferences%2Fcustom-elements.md) | writing the `<script>` class: base classes, lifecycle, attributes↔properties, `documentation()` JSDoc tags |
| [views-and-data.md](https://mcp.mmoframework.fr/search/read?uri=mmoframework-skill%3A%2F%2Fmmo.client%2Fmmo-client%2Freferences%2Fviews-and-data.md) | rendering a view from JS, or working with `MMO.core.Map` / `MMO.elements.View`'s reactive `get/set/watch/loop` API |
| [styling.md](https://mcp.mmoframework.fr/search/read?uri=mmoframework-skill%3A%2F%2Fmmo.client%2Fmmo-client%2Freferences%2Fstyling.md) | writing `<style>` or a project's `.scs` stylesheet: tokens, `lib::`/`controls::` recipes, shadow-DOM vs light-DOM context, `lang="sass"`, reskinning |
| [examples.md](https://mcp.mmoframework.fr/search/read?uri=mmoframework-skill%3A%2F%2Fmmo.client%2Fmmo-client%2Freferences%2Fexamples.md) | you want a complete, working file to copy from |
## The things that matter most
1. **The root tag name IS the contract.** For a custom element it must start with `mmo-` and contain a hyphen (`<mmo-foo>`), and its `<script>` must define `class Foo { ... }` (PascalCase, `mmo-` prefix stripped) or `class Foo extends SomeBase { ... }` — the compiler finds this class by regex and rewrites `class Foo` into `class MMO_Foo_JHS extends CustomShadowElement(HTMLElement) { ...your body... }`, auto-injecting a generated `render($view)` from your markup and a generated `documentation()` from your JSDoc. Get the class name wrong and compilation fails with "cannot find class". See [custom-elements.md](https://mcp.mmoframework.fr/search/read?uri=mmoframework-skill%3A%2F%2Fmmo.client%2Fmmo-client%2Freferences%2Fcustom-elements.md).
2. **Root-tag attributes become `defaultAttributes`/`observedAttributes` automatically** — `<mmo-radio readonly value required>` makes `readonly`, `value`, `required` observed attributes with no explicit `static get observedAttributes()` needed. An attribute written bare (no `="..."`) defaults to a boolean-ish `null` default; write a getter/setter pair (`get value()`/`set value(v)`) to control how the raw attribute string is coerced and stored in `this.view`.
3. **`{path}` is a reactive one-way bind to the data Map; `{=js}` is inline JS with those paths as free variables; `value=` is the one special attribute that's two-way by default** (input/select/textarea, and `set-xxx` on another `mmo-*` element). Nothing else is two-way unless you use `bind-text`/`bind-html` (binds on the element's `blur` event) or wire it yourself in the `<script>`.
4. **Raw HTML insertion is a real XSS sink — and `{path}` in text content is one.** The compiler's text default is html, so `{path}` and `{=...}` in text content compile to `.$html()`/`.$htmlFn()`, not `$text`: like `bind-html`, `jhs-compile="html"`, or `<script>` code that `this.view.set()`s a field bound that way, the value ends up in `element.innerHTML`, unescaped. Only ever let content you control (static template text, developer-written config) or sanitized HTML reach it — never raw user/API text. For anything untrusted use `bind-text="path"`, `set-textContent="{path}"`, or `textContent`/`.text()`/`$view.$text()` from `<script>`/`<fn>`, which build text nodes — [jhs-syntax.md](https://mcp.mmoframework.fr/search/read?uri=mmoframework-skill%3A%2F%2Fmmo.client%2Fmmo-client%2Freferences%2Fjhs-syntax.md#expressions-path-and-js) has what each compiles to and its caveats. The DOM helpers are the other way round, text by default: a string child of `MMO.core.dom.createElement()`/`appendChildren()` is a text node, and so is what `MMO.app.notify()`/`alert()`/`confirm()`/`ask()`/`error()`/`showModal()`/`showPanel()` show — only markup you wrote goes through `MMO.core.dom.html('…')`. mmo.client's `docs/SECURITY_REVIEW.md` §1–§3 traces these sinks and proposes the compiler fix.
5. **Don't hand-write what the compiler derives.** Don't write `static get observedAttributes()` if `defaultAttributes`/bare root-tag attributes already cover it; don't write a `render()` method (the compiler generates one from your markup — if you also define one in `<script>`, yours is silently overwritten since the compiler's `render($view){...}` is inserted as a class member alongside your injected body, and JS uses last-definition-wins for duplicate method names — so never name a method `render` in the `<script>` block).
## Compiling / testing a change
There's no dev server to "run" — a `.jhs` file only matters once compiled. From PHP (see `src/JHSCompiler.php`):
```php
\MMO\client\JHSCompiler::Shared()->compile($outputJsFile, ['path/to/my-element.jhs']);
// or, to compile a string and see the generated JS directly (useful for debugging a syntax error):
\MMO\client\JHSCompiler::Shared()->compileText($jhsSourceString, debug: true);
```
`compileText` with `debug: true` gives readable, indented output — use it to sanity-check what your markup/loop/if actually compiles to before wiring it into the real build; the element's css is the string in its `.add('style', null, '…')` call. Both methods also take `$sassOpts` and `$scsOpts` (the latter merged over `JHSCompiler::$scsOptions`); an element styled in `.scs` needs neither. A style error — Simple CSS (`SCSCompiler::renderError`) or a `lang="sass"` block (`eSass::getErrorCss`) — is caught and rendered as visible error CSS in the element rather than failing the whole compile — don't mistake "the page loaded" for "the styles compiled cleanly."
To actually see a compiled element/page render, **don't start your own `php -S`** — apache serves every
project under `~/dev/src` on localhost, at its path below it (`~/dev/src/mcp.mmoframework` is
`http://localhost/mcp.mmoframework/`), so the demo app is live at `http://localhost/MMO/master/libs/client` (maps to
`libs/client/demo/www`); the same pattern serves the other libraries too (`.../libs/core`, `.../libs/doc`,
etc.). Navigate/curl that directly. A project behind a login (mmo.oauth) is opened with the link `php admin.php agent-login --user=<username> --url=<site url> -m` prints, never by typing a password: see the `mmo-agent-login` skill. A self-started `php -S` hits an unrelated, pre-existing bootstrap failure
on his machine (`Undefined constant "MMO\core\shell\STDOUT"` in `libs/core/src/functions/shell.php:1013`) —
reproducible even on an unmodified page, so don't try to work around it per-task. For one-off CLI scripts
(not HTML output), address them by full absolute path rooted at
`/home/mmouret/dev/src/MMO/master/libs/<library>/...`, e.g.
`php /home/mmouret/dev/src/MMO/master/libs/client/demo/www/index.php`.