Riot’s Jolly Roger

The flag and surface system for RiotML: one voice across web, CLI, docs, packages, diagnostics, and releases.

Why Riot?

There are many OCaml stacks. This one is mine.

I wanted an ML-family language and ecosystem that felt beautiful and practical: actor-based, multi-core ready, type safe, and delightful to use end to end. Something built for shipping.

Riot is my take on what an ML stack should feel like in 2026: opinionated unified tooling, a modern packaging story with built-in docs, actor-oriented by default, a standard library for 80% of everything you'll ever need, and one flag over the whole thing.

Riot is about building software that works, shipping it quickly, making it beautiful when it proves itself useful, and enjoying the work along the way.

It is the most fun I've had programming in my entire career.

Jolly Roger is Riot’s design system, in many ways our very own pirate flag, and it describes the values this stack, and all the supporting systems around it, lives and dies by.

01

Optimize for Developer Joy

Thesis

I get joy out of building good software. Especially when it works, but even more so when it looks beautiful and runs fast.

For design

Every interaction with Riot should make information clear, feedback actionable, and iteration fast. Put commands near claims, examples near explanations, and recovery paths near failures.

Rejects

Joy does not mean everything is soft, friendly, or simplified until it becomes useless. Riot can be sharp. It can be loud. It can be opinionated. It can say no.

Design test

Does this surface help a developer feel unblocked, capable, and eager to continue?

02

Design as One Piece

Thesis

Riot is one piece, not many. The compiler, runtime, actor model, package manager, docs generator, registry, test runner, formatter, diagnostics, install scripts, standard library, and website should feel like parts of the same whole.

For design

Make the preferred path obvious. A golden path is not a prison; it is the route designed, tested, documented, and supported end to end. The design system should make those paths visible: install this, run this, add this package, read these docs, fix this error, publish this way.

Rejects

The core model is not up for committee redesign. Riot does not need formatter knobs, old-tool compatibility as identity, or every surface assembled from unrelated options.

Design test

Does this make the stack feel more coherent, or does it fracture Riot into a toolkit of unrelated choices?

03

Build for Shipping

Thesis

Riot exists so you can ship real software. The design system should help people understand, install, try, build, test, publish, debug, and keep going.

For design

The Riot path is: make it work, learn whether it is the right thing, make it beautiful, make it fast where it needs to be fast, and keep shipping.

Rejects

Do not reward performative complexity, academic posturing, elaborate setup rituals, or pages that brand and decorate without moving the builder forward.

Design test

Does this page help someone take the next shipping step?

04

Human-led, agent-ready

Thesis

Riot is designed by humans. Taste matters. Authority matters. Judgment matters.

For design

Use stable anchors, stable headings, stable field labels, package names, source paths, version numbers, error codes, copyable commands, structured metadata, explicit next steps, and short examples that agents can reuse safely.

Rejects

AI-friendly does not mean vague chat UX, autopilot magic, generated slop, or letting the AI own the taste. The captain is human.

Design test

Could a human understand this quickly, and could an agent extract the important fields without guessing?

05

Trust Builders With Power

Thesis

Riot trusts you with powerful tools. The stack should be approachable, but it should not be timid.

For design

Make power legible: show code, signatures, generated artifacts, runtime behavior, unsafe boundaries, platform caveats, what the tool did, and how to go deeper.

Rejects

Do not hide powerful concepts behind soft language. Power tools need visible edges.

Design test

Does this surface make Riot’s power understandable, or does it hide the mechanism behind vague reassurance?

Voice and writing

Direct, useful, joyful.

Riot’s Jolly Roger sounds direct, opinionated, useful, technical, and joyful. It does not sound corporate, academic, generic, or clever when a clear label helps more.

Direct

Say the thing.

Do not wrap every claim in hedging language. Do not make the reader decode vague platform words. Do not hide the action behind narrative.

Useful

Every sentence should help.

A page can be joyful, opinionated, or expressive, but it still has to help the reader install Riot, find a package, fix an error, understand a module, compare options, or keep moving.

Joyful

Joy means momentum.

Joy does not mean childish. The writing should make the reader feel that Riot is on their side: beautiful code, fast feedback, good defaults, visible power, and the pleasure of building useful software.

Errors

Clear, structured, recoverable.

  • Name the problem before explaining the system.
  • Show package, source, requested thing, expected or available thing, and why it happened.
  • End with one concrete command, edit, or docs link.
  • Keep the diagnostic readable as plain text.

Agents

Stable labels. No guessing.

Agent-ready voice is still human voice. It just refuses to hide important state in loose prose. When a surface describes a package, command, file, error, or generated report, name the stable facts directly so a person can scan them and a tool can parse them.

Prefer labels such as package, source, wanted, found, and fix when those facts matter. Make examples reusable without hidden setup. Avoid copy that forces the reader to infer the package, file, requested symbol, or next command.

Example

Vague copy names no object, no source, and no recovery path. It leaves both humans and agents guessing.

Vague
Something went wrong while resolving dependencies.

Better copy names the package, explains the failure, and gives the next command.

Better
Riot could not resolve Krasny because riot-cli does not depend on the package that exposes it.

Run riot add krasny.

When the output is meant for tools too, expose the same facts as stable fields.

Preferred structure
package: riot-cli
source:  src/publish.ri
wanted:  Krasny
fix:     riot add krasny

Foundations

Visible decisions, not decoration.

These are the tangibles: mark, color, typography, spacing, edges, density, layout, code, and terminal output. They follow the beliefs. They are not arbitrary styling choices.

/01 · Mark and flag

A black flag with a white λ.

The Jolly Roger is the flag of Riot: sharp, simple, reproducible, and more like a symbol stamped on a tool than a mascot illustration.

Use as identityLet the mark sign a surface. Do not use it as repeated decoration.
High contrastPrefer simple placements where the mark survives at small sizes.
No mascotThe flag is enough. Do not over-illustrate the metaphor.
Never competeThe mark should not fight code, docs, or diagnostics for attention.

/02 · Color

Cream, coal, ink, and one red.

Riot Red is identity and action. Paper is reading. Coal is terminal and gravity. Ink is text. Mint, amber, and blue are functional colors.

Color clarifies what kind of information the reader is looking at. Paper carries reading, ink carries structure, coal carries code and terminal gravity, and Riot Red marks identity or action.

Paper #FFF8ED · Default reading surface
Paper-2 #F5ECDC · Recessed surface
Ink #151317 · Text, borders, structure
Coal #0E0D10 · Terminal and code gravity
Riot #F5334D · Flag, identity, primary action
Mint #24C08D · Success state
Amber #F0B429 · Warning state
Blue #2777FF · References and links
PrimaryRiot Red, paper, ink, and coal define identity, reading, structure, and terminal gravity.
SecondaryMint, amber, and blue are functional state colors for success, caution, and references.
Deliberate redUse Riot Red for identity, action, danger, and real emphasis.
Never color aloneState must also have labels, text, or structure.

/03 · Typography

Technical, authored, readable.

Use Martian Mono for headings, navigation, labels, and data chrome; a readable sans for explanations; and a regular mono for code and terminal output.

Typography has three jobs. Display type gives Riot its authored voice. Body type makes longer explanations comfortable. Code type keeps commands, paths, signatures, and numeric metadata precise.

DisplayMartian Mono · 800
Hoist the λ

Programming ecosystem. Headings, navigation, labels, data chrome.

BodyAtkinson Hyperlegible · 400

Long explanations should be comfortable enough to read without making dense technical pages feel padded.

Human explanation. Long docs prose. Editorial surfaces.

CodeMonospace stack · 500
$ riot add httpaf
→ resolved 12 packages in 84ms

Code is product UI. Commands, paths, signatures, table numerics.

SizesUse the token scale for headings, labels, body text, compact metadata, and code.
RuleDo not use display mono for long code snippets.

/04 · Spacing

5px base unit. Memorable scale.

Use 5px increments for spacing and component rhythm. The common scale is 5, 10, 15, 20, 25, 30, 40, 50.

The 5px unit is small enough to tune dense interfaces without inventing one-off values, and large enough to stay memorable.

It keeps Riot surfaces rhythmic: rows, gutters, controls, and examples snap to the same cadence, so dense pages feel authored instead of arbitrary.

Typography can still be optical. The grid does its work in spacing, sizing, rhythm, and repeated structure.

u1 5px
u2 10px
u3 15px
u4 20px
u5 25px
u6 30px
u8 40px
u10 50px
UnitThe base spacing unit is 5px.
SizesControls, rows, icons, and marks follow named size tokens.
SpacersThe common rhythm is 5, 10, 15, 20, 25, 30, 40, and 50.
BordersUse 1px borders. Borders do not need to become grid units.

/05 · Edges

Sharp, not inflated.

Square edges by default. Small radius only when hard edges create visual artifacts. Prefer borders and offset shadows over soft floating cards.

square

Default geometry for docs, tables, panels, and command surfaces.

2px

Small radius is an exception for controls and terminal shells.

offset

Hard offset shadows create emphasis without making surfaces float softly.

heavy

Heavy emphasis belongs to major identity moments, not every card.

DefaultUse square geometry for cards, tables, docs shells, and command blocks.
ExceptionStatus tags and terminal shells may use small radii.

/06 · Density

Clean compression.

Riot surfaces carry packages, modules, types, versions, dependencies, diagnostics, reports, and docs. Density is a feature when hierarchy is clear.

Use tablesComparison belongs in rows, not decorative cards.
Use sidebarsLong references need orientation and anchors.
Keep metadata closeVersions, dates, owners, and docs status sit near the thing they describe.
Hierarchy firstReach for labels, rules, alignment, and type before adding empty air.

/07 · Layout

Chapter, body, anchors.

Use the shell to orient the reader, chapters to create long-page structure, and compact metadata to keep decisions local.

ShellPage frame plus flexible main column.
ContentReadable width with tokenized gutters.
ChapterMarker column plus body column.
SectionTokenized vertical padding and rule.

/08 · Code and terminal

Code is product UI.

Commands, output, signatures, diagnostics, and copy affordances are first-class surfaces.

The terminal is not a screenshot. It is where install, build, test, publish, debug, and recovery happen. Code and output need the same care as navigation and prose.

Terminal
λ riot fmt --check
ok 128 files formatted
json: {"event":"fmt.checked","files":128}
riot add krasny http postgres

Components

Recurring communication patterns.

Components are not a generic UI kit. They are repeated Riot communication patterns with a purpose, anatomy, rules, examples, and anti-examples.

Command block

Show executable commands and their expected output.

Use command blocks for installation, CLI workflows, package commands, fixes, and output that confirms success or explains failure. Avoid fake terminal theater.

Command block
λ riot publish --dry-run
ok package checked
package: krasny
version: 0.2.1
next: run riot publish

Copy strip

Make one command easy to copy.

Use copy strips near explanations. One command per strip. Keep the affordance close to the decision it supports.

curl -sSL https://get.riot.ml | sh
riot add krasny http postgres

Diagnostic block

Help a blocked user recover.

A diagnostic names the problem first, shows source context, uses stable labels, explains why, and ends with a fix.

Diagnostic block
error[R0087]: Type mismatch in fold accumulator

package: krasny
source:  src/main.ri:42:13
wanted:  int
found:   string

42 │ let total = items |> List.fold (+) "zero"^^^^^^
why:
  the seed must match the accumulator type

fix:
  change "zero" to 0

Tables

Let users scan and compare dense structured information.

Use tables when comparison is the task: package lists, dependencies, benchmarks, compatibility matrices, release risk, and generated API inventories.

Package metadata comparison
Package Version Docs Owner Updated
std0.6.0readycoretoday
http0.7.1readynet2d
krasny0.2.1partialweb1d
Benchmark comparison
Run Mean P95 Delta
baseline42 ms68 ms0%
current37 ms54 ms-12%

API signature row

Make generated docs scannable.

map val map : ('a -> 'b) -> ('a, 'e) t -> ('b, 'e) t Transform the success channel.
bind val bind : ('a -> ('b, 'e) t) -> ('a, 'e) t -> ('b, 'e) t Chain a fallible step.
map_error val map_error : ('e -> 'f) -> ('a, 'e) t -> ('a, 'f) t Transform the failure channel.

Callouts

Interrupt only when the information changes what the reader should do.

Release note

Help users understand change and risk.

v0.6.0

Package docs now build with stable anchors.

Breaking changes come first. Added, changed, fixed, removed, and migration notes stay grouped so readers can decide what matters.

  • Added generated module search to docs pages.
  • Changed package rows to expose docs freshness.
  • Fixed stale links from registry pages to generated docs.

Buttons

Direct, compact, imperative actions.

Button variant rules
Variant Use for Example
Primary One main action on the surface.
Default A secondary action with normal weight.
Dark Technical or terminal-adjacent actions.
Ghost A quiet command-shaped action.

Badges

Small status and metadata markers.

Badge variant rules
Variant Use for Example
Default Neutral categories and tags. stdlib
Solid Strong version or status metadata. v0.6.0
Riot Identity, risk, or attention. unstable
Mint Readiness, success, passing checks. stable
Amber Caution, beta state, review needed. beta

Cards

Use cards for one bounded object. Do not use cards to replace comparison tables.

Docs report

One object, one status, one next action. Cards frame a thing; they should not hide data that wants rows.

Build surface review

Use status cards for repeated summaries, settings groups, and focused previews.

Forms

Inputs are compact, labeled, and bordered in ink.

Fields keep labels close to values so compact forms still scan cleanly.

Version values stay mono and exact.

Groups are named by the decision they support, not by visual placement.

In the wild

Real surfaces governed by the system.

Each application names the surface, the reader's task, the required information, the traps to avoid, and the components that carry it.

https://riot.ml
PurposeMake people think: this might be the ML stack I wanted.
Must showThe stack, a real command, real code, and the path to install, learn, and ship.
AvoidStartup SaaS air, vague positioning, and technical proof buried below chrome.
Primary componentsCommand block, copy strip, buttons, cards, callouts.

riot.ml

Many OCaml stacks.
/ One flies a flag.

The Riot website makes the stack legible fast. Show the command, the stack, and the path to install, learn, and ship.

std@0.6.0

Standard library surface for system work.

published today
kernel@0.0.18

Core abstractions shared across Riot.

updated 6h ago
https://pkgs.ml
PurposeHelp users find, compare, evaluate, and install packages.
Must showName, version, docs status, freshness, owner, install command, and compatibility context.
AvoidDecorative package cards when comparison is the task.
Primary componentsTables, badges, copy strip, forms, callouts.
pkgs.ml
1,247 packages
Total packages1,247
Downloads8.4M
Publishers309
Median publish1.8s
Registry package comparison
Package Version Description Category Downloads Updated
std 0.4.0 Standard library. Datatypes, collections, IO, time, formats. core 1,247,890 today
httpaf 0.7.1 Fast, low-allocation HTTP/1.1 server and client. net 840,221 2d
kernel 0.0.18 Core abstractions shared across the Riot ecosystem. core 620,498 6h
krasny 0.2.1-rc Web framework on top of httpaf and the actor runtime. web 98,114 1d
https://docs.pkgs.ml/p/kernel/0.0.18/Kernel/Result
PurposeHelp readers understand generated API documentation without losing orientation.
Must showPackage, version, module, breadcrumbs, search, local navigation, signatures, examples, and stability markers.
AvoidDecorative chrome around generated reference material.
Primary componentsModule index, API signature rows, code blocks, callouts, badges.
Riot Docs kernel / Kernel / Result v0.0.18

module · Kernel.Result

Result

Errors are values. Results carry them through pipelines, actor boundaries, and logs.

Signature
type ('a, 'e) t = Ok of 'a | Error of 'e
val map : ('a -> 'b) -> ('a, 'e) t -> ('b, 'e) t
terminal surfaces
PurposeTell the user what happened and what to do next in plain text.
Must showStatus, package, version, source when relevant, diagnostic IDs, and copyable next commands.
AvoidNoisy success output, fake terminal theater, and prose that breaks scripts.
Primary componentsCommand block, diagnostic block, copy strip, badges.

CLI

Plain text still carries the brand.

Prompts, aligned labels, diagnostic IDs, and fix commands work without browser chrome.

CLI output
λ riot publish --dry-run
ok package checked
package: krasny
version: 0.2.1
next: run riot publish
https://pkgs.ml/not-found
PurposeTurn a dead end into a recovery path.
Must showProblem, context, possible cause, fix, and fallback.
AvoidCute dead ends, jokes instead of recovery, and errors with no next action.
Primary componentsDiagnostic block, callout, copy strip, buttons.

Error pages

Useful dead ends.

Error pages are diagnostics in web form. Explain what failed, whether it is local or remote, and what the user can do next.

Anatomy
problem: package not found
context: registry search
possible cause: misspelled package name
fix: search for krasny
fallback: publish a new package