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

Samples

markdown-demo

Markdown demo — the built-in markdown control, running against real sectioned storage.

5 source files1 test file

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

Get it

$ osy init markdown-demo
$ osy launch

The app

app.osy8 lines
// Markdown demo — the built-in markdown control, running against real sectioned storage.
app MarkdownDemo {
  use Osyrin.Markdown@1;

  model "model/**/*.osy";
  tests "tests/**/*.test.osy";
}
model/auth.osy44 lines
// Minimal auth, because the DATA path never inherits anonymity.
[Role] enum AppRole { Authenticator }

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;
  Markdown Notes;
  security {
    deny read PasswordHash when !IsAuthenticator;
    allow read when IsAnonymous || IsAuthenticated;
    allow create when IsAuthenticator;
    allow update when IsAuthenticator;
  }
}

[AuthMethod]
string Signup(string email, string password) {
  var u = new User { Email = email, PasswordHash = Security.HashPassword(password) };
  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/theme.osy32 lines
// markdown-demo — ITS OWN LOOK, and deliberately so: a prose tool reads in a serif.
//
// ⚑ Not the shared demo theme (`demo/_shared/theme.osy`), and `DemoSharedThemeTests` records why.
//   The demos are not meant to look uniform — several of them exist to show that a different
//   palette and a different face are a few lines of `theme`.
//
// ⚠ This restates the ORIGINAL intent with the mechanism corrected. The original spelled it
//   `Fonts { Body / Heading / Ui }` — a group name no control reads — so the Hedvig this app
//   shipped and pinned never reached a single control. `Sans` shadows the KIT's own token, which
//   is what makes a face apply without touching a page.

theme Doc {
  Colors {
    Bg        = Modes.Of(light: "#FAFAF8", dark: "#0E1116");   // paper, very slightly warm
    OnBg      = Modes.Of(light: "#16181D", dark: "#E6EAF0");
    Border    = Modes.Of(light: "#E6E4DE", dark: "#2A2F37");
    Muted     = Modes.Of(light: "#F0EEE8", dark: "#232833");
    Surface   = Modes.Of(light: "#FFFFFF", dark: "#171B21");
    OnSurface = Modes.Of(light: "#1A1F26", dark: "#E6EAF0");
    Primary   = "#4F46E5";
    OnPrimary = "#FFFFFF";
    Danger    = "#DC2626";
  }

  Radius { Sm = "6px"; Md = "10px"; Lg = "16px"; }

  Font {
    Sans = "\"Hedvig Letters Serif\", ui-serif, Charter, Georgia, Cambria, serif";
    Mono = "ui-monospace, SFMono-Regular, Menlo, monospace";
  }
}
model/pages/editor.osy68 lines
// The one page: the markdown control over a real `Markdown` property.

// `MarkdownEditor` and `MenuEntry` come from the kit the manifest opted into. An ordinary import, exactly like
// `using Osyrin.Ui;` — the app declares the DEPENDENCY once in `app.osy` and imports the names where it uses them.
using Osyrin.Markdown;

[Page("/")]
[Render(CSR)]
[Title("Markdown")]
component EditorPage() {
  live var me = User.FirstOrDefault();

  bool finding = false;
  string query = "";
  string replacement = "";
  int hit = 0;
  int matches = 0;

  action Find(string selection) {
    finding = true;
    if (selection != "") { query = selection; }
  }
  action Matches(int current, int total) { hit = current; matches = total; }
  action ShowFind() { finding = true; }
  action HideFind() { finding = false; query = ""; }

  string note = "";
  action Archive() { note = "archived"; }

  render {
    MarkdownEditor(
      ownerType: "User",
      ownerId: me.Id,
      property: "Notes",
      face: editorial,
      outline: true,
      findQuery: query,
      replaceWith: replacement,
      findRequested: Find,
      matchesChanged: Matches,
      extraItems: [
        new MenuEntry { Label = "Archive this doc", Run = Archive },
        new MenuEntry { Label = "Delete block", Run = MarkdownEditor.DeleteBlock },
        new MenuEntry { Label = "Make it a heading", Run = () => MarkdownEditor.TurnIntoHeading(2) }
      ]
    ) {
      slot Toolbar { c =>
        Row(gap: 1) {
          if (!finding) {
            Osyrin.Button("Find", onClick: ShowFind);
          } else {
            Input(value: query, label: "Find", placeholder: "Find", onEnter: c.FindNext);
            Text(matches == 0 ? (query == "" ? "" : "none") : hit + "/" + matches);
            Osyrin.Button("‹", onClick: c.FindPrev);
            Osyrin.Button("›", onClick: c.FindNext);
            Input(value: replacement, label: "Replace with", placeholder: "Replace with");
            Osyrin.Button("Replace", onClick: c.ReplaceOne);
            Osyrin.Button("All", onClick: c.ReplaceAll);
            Osyrin.Button("✕", onClick: c.ClearFind);
            Osyrin.Button("Close", onClick: HideFind);
          }
          Text(note);
        }
      }
    }
  }
}
model/pages/login.osy24 lines
// Sign-in, prefilled. This is a demo whose point is the editor, not the credential flow, so the fields carry
// working values and signing in is one click. `Signup` doubles as "create it if it isn't there yet".
[Page("/login")]
[AllowAnonymous]
[Render(CSR)]
[Title("Sign in")]
component LoginPage() {
  string email = "demo@local";
  string password = "demo1234";

  action SignIn() { Session.SignIn(Login(email, password)); }
  action Register() { Session.SignIn(Signup(email, password)); }

  render {
    Stack(gap: 3) {
      Text("Markdown demo");
      Input(value: email, label: "Email", placeholder: "email");
      Input(value: password, label: "Password", type: "password", placeholder: "password");
      Osyrin.Button("Sign in", onClick: SignIn);
      Osyrin.Button("Create this account", onClick: Register);
    }
  }
}