Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Samples

dropdown-demo

A project's LEAD, chosen from a dropdown — the sample that proves M121's `Slot(item)` renders, and that a

4 source files1 test file

The dropdown-demo sample, running.
Running, signed in, compiled from the source below.

Get it

// not one of the apps the toolchain ships: this one lives in the
// repository. Clone it, then:
$ cd demo/dropdown-demo
$ osy launch

The app

app.osy8 lines
// A project's LEAD, chosen from a dropdown — the sample that proves M121's `Slot(item)` renders, and that a
// dropdown bound to an ENTITY-typed property round-trips the selection back into the data.
app DropdownDemo {
  use Osyrin.Ui;
  model "model/**/*.osy";
  tests "tests/**/*.test.osy";
}
model/auth.osy62 lines
// Auth, on the `demo/auth-demo` shape — the smallest real login, because this demo needs WRITES and the deny-all
// endpoint gate correctly refuses an anonymous one. That is not incidental to what is being demonstrated: choosing
// a lead WRITES `Project.Lead`, so a version of this demo that opened with no login could not have proven the
// binding at all.

[Role] enum AppRole { Authenticator, Member }

entity RoleGrant {
  User Grantee;
  [Required] AppRole Level;
  security { }
}

policy IsAuthenticator => RoleGrant.Any(g => g.Grantee == user && g.Level == AppRole.Authenticator);

[Principal] entity User {
  [MaxLength(200), Unique] string Email;
  [MaxLength(200)] string PasswordHash;
  [MaxLength(80)]  string Name;
  [MaxLength(120)] string Title;
  [MaxLength(4)]   string Initials;
  security {
    deny read PasswordHash when !IsAuthenticator;
    allow read when IsAuthenticator;
    allow create when IsAuthenticator;
    allow update when IsAuthenticator;
    allow read when IsAuthenticated;
  }
}

[AuthMethod]
string Signup(string email, string password) {
  var u = new User {
    Email = email, PasswordHash = Security.HashPassword(password),
    Name = email, Title = "Founder", Initials = "ME",
  };

  if (!User.Any(x => x.Email == "ada@apollo.test")) {
    new User { Email = "ada@apollo.test",       Name = "Ada Lovelace",      Title = "Principal engineer",  Initials = "AL", PasswordHash = Security.HashPassword("x") };
    new User { Email = "grace@apollo.test",     Name = "Grace Hopper",      Title = "Engineering manager", Initials = "GH", PasswordHash = Security.HashPassword("x") };
    new User { Email = "alan@apollo.test",      Name = "Alan Turing",       Title = "Research lead",       Initials = "AT", PasswordHash = Security.HashPassword("x") };
    new User { Email = "katherine@apollo.test", Name = "Katherine Johnson", Title = "Staff engineer",      Initials = "KJ", PasswordHash = Security.HashPassword("x") };
  }

  return Security.IssueJwt(u.Id, u.Email);
}

[AuthMethod]
string Login(string email, string password) {
  var u = User.Where(x => x.Email == email).FirstOrDefault();
  if (u == null) { Security.VerifyPassword(password); return ""; }
  if (Security.VerifyPassword(password, u.PasswordHash)) { return Security.IssueJwt(u.Id, u.Email); }
  return "";
}

app.AuthBootstrap = new AuthBootstrap {
  Role      = AppRole.Authenticator,
  Login     = Login,
  Signup    = Signup,
  LoginPage = LoginPage,
};
model/home.osy103 lines
// THE POINT OF THIS DEMO, in one line: `Dropdown(value: p.Lead, options: people)` — a generic dropdown over
// ENTITIES, whose caller decides what a row looks like, bound two-way to an entity-typed property.

using Osyrin.Ui;

theme Studio {
  Colors {
    Bg        = Modes.Of(light: "#F6F7F9", dark: "#0E1116");
    OnBg      = Modes.Of(light: "#16181D", dark: "#E6EAF0");
    Border    = Modes.Of(light: "#DDE1E6", dark: "#2A2F37");
    BorderStrong = Modes.Of(light: "#C3C9D0", dark: "#3A414D");
    Surface   = Modes.Of(light: "#FFFFFF", dark: "#171B21");
    OnSurface = Modes.Of(light: "#1A1F26", dark: "#E6EAF0");
    Muted     = Modes.Of(light: "#EFF1F5", dark: "#232833");
    Subtle    = Modes.Of(light: "#6B7280", dark: "#8A94A6");
    Accent    = "#4F46E5";
    OnAccent  = "#FFFFFF";
  }
  Radius { Control = "8px"; Card = "14px"; Pill = "999px"; }
  ZIndex { Overlay = "50"; }
  FontSize { Caption = "13px"; Body = "15px"; Section = "17px"; Display = "28px"; }
  FontWeight { Medium = "600"; }
  Length { Avatar = "28px"; Field = "300px"; }
}

entity Project {
  [MaxLength(80)] string Name;
  User? Lead;
  security { allow read, create, update when IsAuthenticated; }
}

[Composable] component Avatar(string initials) {
  render {
    Box(w: Length.Avatar, h: Length.Avatar, rounded: Radius.Pill, bg: Colors.Accent, color: Colors.OnAccent) {
      Row(align: Align.Center, justify: Justify.Center, h: "100%") {
        Text(initials, fontSize: FontSize.Caption, fontWeight: FontWeight.Medium);
      }
    }
  }
}

[Page("/")]
component Home() {
  live var people = User.OrderBy(u => u.Name);
  live var project = Project.FirstOrDefault();

  on mount { SeedDemo(); }

  action Save() { UnitOfWork.Commit(); }

  render {
    Stack(gap: 6, p: 8, minH: "100vh", bg: Colors.Bg, color: Colors.OnBg) {
      if (project == null) {
        Text("Seeding…", color: Colors.Subtle);
      } else {
        Stack(gap: 1) {
          Text(project.Name, fontSize: FontSize.Display, fontWeight: FontWeight.Medium);
          Row(gap: 2, align: Align.Center) {
            Text("Lead:", color: Colors.Subtle, fontSize: FontSize.Body);
            if (project.Lead == null) { Text("nobody yet", color: Colors.Subtle, fontSize: FontSize.Body); }
            else { Text(project.Lead.Name, fontSize: FontSize.Body, fontWeight: FontWeight.Medium); }
          }
        }

        Stack(gap: 2) {
          Text("Project lead", color: Colors.Subtle, fontSize: FontSize.Caption, fontWeight: FontWeight.Medium);
          Dropdown(value: project.Lead, options: people, placeholder: "Choose a lead…", label: "Project lead") { d =>
            Avatar(d.Initials);
            Stack(gap: 0) {
              Text(d.Name, fontSize: FontSize.Body);
              Text(d.Title, fontSize: FontSize.Caption, color: Colors.Subtle);
            }
          }
          Row(w: Length.Field) { Button("Save", onPress: Save); }
        }
      }
    }
  }
}

[Page("/login")] [AllowAnonymous] [Render(CSR)]
component LoginPage() {
  string email = "you@apollo.test";
  string password = "demo-password";
  action DoLogin()  { Session.SignIn(Login(email, password)); }
  action DoSignup() { Session.SignIn(Signup(email, password)); }

  render {
    Stack(gap: 4, p: 8, minH: "100vh", bg: Colors.Bg, color: Colors.OnBg) {
      Text("Dropdown demo", fontSize: FontSize.Display, fontWeight: FontWeight.Medium);
      Text("Sign up with anything — it is a local throwaway app.", color: Colors.Subtle, fontSize: FontSize.Body);
      Stack(gap: 2, w: Length.Field) {
        Input(value: email, label: "Email", placeholder: "you@apollo.test");
        Input(value: password, label: "Password", type: "password", placeholder: "password");
        Row(gap: 2) {
          Button("Create account", onPress: DoSignup);
          Button("Sign in", onPress: DoLogin);
        }
      }
    }
  }
}
model/seed.osy8 lines
// The project to lead. Written by a SIGNED-IN member on first visit — `Project` grants create to any authenticated
// caller, which is exactly who this is.
void SeedDemo() {
  if (Project.Any()) { return; }
  new Project { Name = "Apollo" };
  UnitOfWork.Commit();
}