---
title: The same counter on a phone
description: Two hand-written proofs drive a counter on iOS UIKit and macOS AppKit through JavaScriptCore, from a graph-shaped artifact instead of the DOM.
sidebar: { label: Native targets, badge: Experimental }
---

Last page, the graph and its symbols turned out to be data plus small functions. Does a browser have to run them?

The Markless README states the goal: one web-like component model for web and native apps. Today, two small proofs test that idea. Neither one is a product.

:::warning[Experimental: hand-written proofs only]
The proofs live in `poc/fixtures/proofs/` in the Markless repo. A person wrote each `artifact.json` by hand. No Markless package emits this format. The compiler has no native output, and `@markless/web` is the only rendering target package.
:::

## The problem

The web build ends in DOM operations, such as `setText`. A phone has no DOM. A native button needs its own control, its own event, and its own way to set a title.

So the question is narrow. Can a graph-shaped description of a counter drive real native controls, with the handler still written in JavaScript?

## The mental model

Think of sheet music. That is an analogy. The score says which notes play and when. Each instrument plays the same score in its own way.

Here the score is the artifact. It lists state cells, host nodes, event records, text bindings, and symbol bodies. A small Swift runtime is the instrument. It builds UIKit or AppKit controls from the host nodes.

Press **Count 0**. Then pick another host and press it again.

<UnderTargetsFigure />

Notice that `state:count`, the handler, and the text binding stay the same. Only the control, its event name, and the title write change.

## The proof

The iOS proof starts from this component. The macOS proof differs only in its heading.

```tsrx App.tsrx
import { state } from '@markless/core';

export function Counter() @{
  let count = state(0);

  <main>
    <h1>Markless iOS Proof</h1>
    <button onClick={() => count++}>Count {count}</button>
  </main>
}
```

The hand-written artifact maps the authored `onClick` to a semantic `activate` event. Then it names the host event. On iOS that is `touchUpInside`. On macOS it is `action`.

```json artifact.json
{
  "node": "host:button",
  "authoredEvent": "onClick",
  "semanticEvent": "activate",
  "nativeEvent": "touchUpInside",
  "symbolId": "symbol:counter.increment"
}
```

The symbol body is also hand-written JavaScript:

```js
graph["state:count"] = graph["state:count"] + 1;
```

## What the Swift runtime does

1. It creates a `JSContext` from JavaScriptCore, and a plain `graph` object with `state:count` set to `0`.
2. It turns each host node into a control. On iOS, `main` becomes a `UIStackView` and `button` becomes a `UIButton`.
3. It connects the native event to the symbol ID.
4. On a tap, it runs the symbol in JavaScriptCore.
5. Then it re-reads every text binding and sets the button title, such as `Count 1`.

The macOS proof does the same with an `NSButton` and its `action` event. In both proofs, an XCTest checks that the title changes from `Count 0` to `Count 1`.

## What the proofs do not show

The proofs are narrow on purpose. Keep these limits in mind:

- They do not use `@markless/runtime`. The graph is a plain JavaScript object.
- They update every text binding after each tap. They do not use path subscriptions.
- They do not cover styling, Android, Windows, Linux, or production packaging.
- A check script in each proof makes sure that the files name no web view or cross-platform shell.

**Misconception: Markless ships native apps today.** It does not. The proofs show that the artifact shape can drive native controls. The compiler does not produce that artifact yet.

**Next:** That is the whole engine, from compiler to native proof. Where does each part live in the source? [Repo tour →](/contributing/repo-tour)
