Stream: show and tell

Topic: browser canvas apps (and roc-ray)


view this post on Zulip Steve Howell (Sep 21 2026 at 22:21):

I have been working on creating (or porting) apps that run both in the browser as canvas apps and as native apps using roc-ray.

view this post on Zulip Steve Howell (Sep 21 2026 at 22:21):

https://roc.lynrummy.com/breakout/ is a example.

image.png

view this post on Zulip Steve Howell (Sep 21 2026 at 22:23):

Everything basically goes through this interface:

CanvasApp(model) : {
    size : { width : F64, height : F64 },
    fps : I32,
    init : model,
    advance : model, Input.Snapshot, F32 -> model,
    frame : model -> List(Shapes.Shape),
    sounds : model -> U32,
    tones : List({ freq : I32, ms : I32 }),
    title : Str,
}

see https://github.com/showell/roc-apps/blob/master/canvas_apps/lib/CanvasApp.roc for more details

view this post on Zulip Steve Howell (Sep 21 2026 at 22:28):

In order to prove the concept, I took several examples from the roc-ray repository that @Luke Boswell published. For example, the Breakout game originally came from https://github.com/lukewilliamboswell/roc-ray/tree/main/examples/breakout

It was not my goal for Luke's examples to be pulled verbatim into my repo and "just work", but I never needed any serious modification either.

It was my goal that the code, once modified, runs identically in both the browser and on roc-ray, with just different drivers for the most part. In other words, the actual app code does not need to be tweaked once you either shim it or modify it to work with the CanvasApp code.

view this post on Zulip Steve Howell (Sep 21 2026 at 22:30):

It's pretty versatile for such a small system. Canvas apps can include arcade games (I also pulled Pong and Snake from Luke's repo) or little animated shorts (Safari and Trick-or-Treat are mine) and miscellaneous other apps.

view this post on Zulip Steve Howell (Sep 21 2026 at 22:31):

You can see the web versions running at https://roc.lynrummy.com/. It's still under very active development. The code is at https://github.com/showell/roc-apps/tree/master/canvas_apps

view this post on Zulip Steve Howell (Sep 21 2026 at 22:34):

The cool thing here is that Roc doesn't really flinch when it comes to supporting two similar but different paradigms. The roc-ray paradigm uses Roc effects pretty concretely, whereas the browser side is more about using Roc staying in the pure function space and letting a thin JS layer mutate the world.

view this post on Zulip Luke Boswell (Sep 21 2026 at 22:40):

roc-ray app needs native/CanvasAppRunner, which touches the platform and so cannot live in a package

I think you can do this now, we landed https://github.com/roc-lang/roc/pull/11079

view this post on Zulip Luke Boswell (Sep 21 2026 at 22:41):

By convention the app root is typically named "main.roc" which is why all my examples have the files in a directory alongside it. That way the tooling like LSP looks up the tree until it finds main.roc and you don't need to add a flag for that

view this post on Zulip Luke Boswell (Sep 21 2026 at 22:42):

Same convention for packages and platforms too, the "root" is assumed to be main.roc unless you provide a flag

view this post on Zulip Luke Boswell (Sep 21 2026 at 22:45):

Not saying you should change it, just explaining why I structure things that way

view this post on Zulip Steve Howell (Sep 21 2026 at 22:58):

Luke Boswell said:

Same convention for packages and platforms too, the "root" is assumed to be main.roc unless you provide a flag

Ah, good to know. I relayed your feedback to Claude, and now each app directory will have a main.roc for roc-ray and a web.roc for web. That's still in progress as I write this, but it shouldn't take long.

view this post on Zulip Steve Howell (Sep 21 2026 at 23:08):

Here's an example of making the code work on the web:

# Breakout, as a page: the wasm module a browser runs. main.roc beside it
# is the same app on roc-ray.
app [Model, program] {
    pf: platform "../web/platform/main.roc",
    lib: "../lib/main.roc",
}

import lib.WasmApp
import BreakoutApp

Model : BreakoutApp.Model

program = WasmApp.program(BreakoutApp.canvas_app)

It's pretty simple. :) web.roc

view this post on Zulip Steve Howell (Sep 21 2026 at 23:12):

The landing page now has screen captures. :)

view this post on Zulip Luke Boswell (Sep 22 2026 at 00:27):

Love the screen captures... I think it's really compelling and inviting

view this post on Zulip Luke Boswell (Sep 22 2026 at 00:28):

Did you use roc-ray's builtin screen capture utilities for that?

view this post on Zulip Steve Howell (Sep 22 2026 at 12:06):

Luke Boswell said:

Did you use roc-ray's builtin screen capture utilities for that?

No, I already tooling from before using roc-ray: https://github.com/showell/roc-apps/blob/master/canvas_apps/web/mini_canvas.mjs

view this post on Zulip Steve Howell (Sep 22 2026 at 12:28):

Claude says I would need to install xvfb to use roc-ray's screen capture. It's a pretty small dependency, but I'm trying to avoid dependencies where I can.


Last updated: Sep 24 2026 at 15:59 UTC