streaming-ui-script · calendar ← Back to live examples
Live demo · agenda + scheduling

A working calendar & scheduler, generated from one program

Google Calendar-style agenda with a DatePicker anchor, view toggles (agenda / week / availability), category chips, a Timeline agenda for the focused day, a busy-hours ProgressRing, and a detail Sheet for every event. Switching day, category, or view is one @Set — no JS required.

Live preview

Pick a date, filter by category, or click any agenda row to open the event sheet (RSVP, add attendees, reschedule). The "Book focus" follow-up demonstrates how the LLM can chain new events into the day with a single follow-up.

$date = "2026-05-13"
$view = "agenda"
$category = "all"
$selected = ""
$rsvp = "yes"

events = Query("agenda_for", {date: $date, category: $category}, {
  day: "",
  busyMinutes: 0,
  totalMinutes: 480,
  busyPercent: 0,
  freeBlocks: [],
  rows: [],
  weekStats: {meetings: 0, focus: 0, deepWork: "0h", attendees: 0}
})

eventCount = @Count(events.rows)
selected = @First(@Filter(events.rows, "id", "==", $selected))
selectedExists = @Count(@Filter(events.rows, "id", "==", $selected))

header = PageHeader(
  "Calendar",
  events.day + " · " + eventCount + " events scheduled",
  Breadcrumb([BreadcrumbItem("Workspace", "#"), BreadcrumbItem("Calendar")]),
  [
    Button("Today",
           Action([@Set($date, "2026-05-13")]),
           "ghost"),
    Button("Create event",
           Action([@ToAssistant("Open the create-event form")]),
           "primary")
  ],
  Badge("Synced just now", "success")
)

dateBar = Card([
  Stack([
    Stack([
      DatePicker("anchor", $date, "Focused day", "2026-05-01", "2026-05-31"),
      Buttons([
        Button("◀",  Action([@ToAssistant("Go to previous day")]),  "ghost", "button", "small"),
        Button("▶",  Action([@ToAssistant("Go to next day")]),      "ghost", "button", "small")
      ])
    ], "row", "s", "end"),
    Spacer(),
    ToggleGroup("view", [
      {value: "agenda", label: "Agenda",       icon: "list-ul"},
      {value: "week",   label: "Week",          icon: "calendar-week"},
      {value: "free",   label: "Find time",     icon: "magnifying-glass"}
    ], $view),
    Spacer(),
    ToggleGroup("category", [
      {value: "all",      label: "All",       icon: "circle"},
      {value: "meetings", label: "Meetings",  icon: "users"},
      {value: "focus",    label: "Focus",     icon: "headphones"},
      {value: "personal", label: "Personal",  icon: "mug-hot"},
      {value: "team",     label: "Team",      icon: "building-user"}
    ], $category)
  ], "row", "m", "center")
])

busyRing = Card([
  SectionHeader("Day load", "How packed is today?"),
  ProgressRing(events.busyPercent, 100, "" + events.busyPercent + "%", "Busy", events.busyPercent > 75 ? "danger" : (events.busyPercent > 50 ? "warning" : "success"), "lg"),
  Stats([
    {label: "Busy",   value: "" + events.busyMinutes + "m",                 tone: events.busyPercent > 75 ? "danger" : "primary"},
    {label: "Free",   value: "" + (events.totalMinutes - events.busyMinutes) + "m", tone: "success"},
    {label: "Meetings", value: "" + @Count(@Filter(events.rows, "category", "==", "meetings")), tone: "info"},
    {label: "Focus",  value: "" + @Count(@Filter(events.rows, "category", "==", "focus")),     tone: "default"}
  ])
])

agendaTimeline = Timeline(
  @Each(events.rows, "e",
    TimelineItem(
      e.time + " · " + e.title,
      e.duration + " · " + e.location,
      e.summary,
      e.icon,
      e.tone
    )
  )
)

agendaList = Stack(@Each(events.rows, "e",
  Notification(
    e.time + " · " + e.title,
    e.location + " · " + e.duration + " · " + e.attendeeSummary,
    e.relative,
    e.icon,
    null,
    e.tone,
    e.unread,
    [
      Button("Open",
             Action([@Set($selected, e.id)]),
             "secondary",
             "button",
             "small"),
      Button("Join",
             Action([@OpenUrl(e.joinUrl)]),
             "primary",
             "button",
             "small")
    ]
  )
), "column", "s")

emptyAgenda = EmptyState(
  "Nothing scheduled",
  "You have the whole day free. Block focus time or invite the team.",
  "mug-hot",
  Button("Block 2h of focus",
         Action([@ToAssistant("Block 2 hours of focus time on " + $date)]),
         "primary")
)

freeBlocks = Stack(
  @Each(events.freeBlocks, "b",
    Stack([
      StatusDot(b.label + " · " + b.duration, "success"),
      Spacer(),
      Tag("Available", "circle-check", "sm", "success"),
      Button("Book",
             Action([@ToAssistant("Book " + b.label + " for focus")]),
             "secondary",
             "button",
             "small")
    ], "row", "s", "center")
  ),
  "column", "s"
)

weekCard = Card([
  SectionHeader("Week at a glance", "Mon–Fri overview", null, Tag("Week 20", null, "sm", "info")),
  MetricGrid([
    StatCard("Meetings",       "" + events.weekStats.meetings,                     "down", "-3 vs last week", "users"),
    StatCard("Focus blocks",   "" + events.weekStats.focus,                        "up",   "+2",              "headphones"),
    StatCard("Deep work",      events.weekStats.deepWork,                           "up",   "longest in 3 wks","clock"),
    StatCard("Total attendees","" + events.weekStats.attendees,                     "flat", "across meetings", "user-group")
  ])
])

freePane = Card([
  SectionHeader("Free time", "30+ minute blocks on " + events.day),
  @Count(events.freeBlocks) == 0 ? EmptyState("No free slots today", "Try tomorrow or shorten a meeting.", "circle-info", null) : freeBlocks
])

mainBody = $view == "agenda" ? (eventCount == 0 ? emptyAgenda : agendaList) : ($view == "free" ? freePane : weekCard)

agendaCard = Card([
  SectionHeader("Agenda", events.day, null, Tag("" + eventCount + " events", null, "sm", "primary")),
  mainBody,
  Separator("horizontal", true),
  SectionHeader("Day timeline", "Sequential view of every event"),
  eventCount == 0 ? TextContent("No events to plot.", "small", "muted") : agendaTimeline
])

quickPicker = Card([
  SectionHeader("Jump to date", "Quick picks for this week"),
  Buttons([
    Button("Mon · 12 May", Action([@Set($date, "2026-05-12")]), $date == "2026-05-12" ? "primary" : "ghost", "button", "small"),
    Button("Tue · 13 May", Action([@Set($date, "2026-05-13")]), $date == "2026-05-13" ? "primary" : "ghost", "button", "small"),
    Button("Wed · 14 May", Action([@Set($date, "2026-05-14")]), $date == "2026-05-14" ? "primary" : "ghost", "button", "small"),
    Button("Thu · 15 May", Action([@Set($date, "2026-05-15")]), $date == "2026-05-15" ? "primary" : "ghost", "button", "small"),
    Button("Fri · 16 May", Action([@Set($date, "2026-05-16")]), $date == "2026-05-16" ? "primary" : "ghost", "button", "small")
  ])
])

myCalendars = Card([
  SectionHeader("My calendars"),
  List([
    ListItem("Personal",    "Default · You",                    "circle"),
    ListItem("Team standups","From Naomi Rivers",                "user-group"),
    ListItem("Releases",    "From engineering@acme.com",         "rocket"),
    ListItem("Focus time",  "Auto-blocked by Reclaim",           "shield-halved")
  ])
])

sidePane = Stack([busyRing, quickPicker, myCalendars], "column", "m")

splitLayout = SplitView([agendaCard], [sidePane], "1.6fr")

detailEmpty = EmptyState(
  "No event selected",
  "Open an event on the left to see attendees, location, and the meeting agenda.",
  "calendar-day",
  null
)

detailLoaded = Stack([
  PageHeader(
    selected.title,
    selected.time + " · " + selected.duration + " · " + selected.location,
    null,
    null,
    Tag(selected.statusLabel, null, "sm", selected.tone)
  ),
  DescriptionList([
    DescriptionItem("Date",     events.day,        "calendar"),
    DescriptionItem("Time",     selected.time + " (" + selected.duration + ")", "clock"),
    DescriptionItem("Location", selected.location, "location-dot"),
    DescriptionItem("Host",     selected.host,     "user-tie"),
    DescriptionItem("Calendar", selected.calendar, "tag")
  ]),
  Separator("horizontal", true),
  SectionHeader("Agenda", "Topics planned for this meeting"),
  List(@Each(selected.agenda, "topic", ListItem(topic, null, "circle-check"))),
  Separator("horizontal", true),
  SectionHeader("Attendees", "" + @Count(selected.attendees) + " invited"),
  Stack(@Each(selected.attendees, "a",
    PersonChip(a.name, a.role, a.avatar, "md", a.online ? "online" : null)
  ), "column", "s"),
  Separator("horizontal", true),
  SectionHeader("Your RSVP"),
  Radio("rsvp", [
    SelectItem("yes",    "Yes — I'll be there"),
    SelectItem("maybe",  "Maybe"),
    SelectItem("no",     "Decline")
  ], $rsvp),
  Stack([
    Spacer(),
    Buttons([
      Button("Reschedule",
             Action([@ToAssistant("Reschedule " + selected.title)]),
             "secondary"),
      Button("Send RSVP",
             Action([@ToAssistant("RSVP " + $rsvp + " for " + selected.title)]),
             "primary")
    ])
  ], "row", "m", "center")
], "column", "m")

eventSheet = Sheet(
  "Event detail",
  $selected != "",
  [selectedExists == 0 ? detailEmpty : detailLoaded],
  "right",
  [Buttons([
    Button("Close", Action([@Set($selected, "")]), "ghost"),
    Button("Join now",
           Action([@OpenUrl("https://meet.example.com/" + $selected)]),
           "primary")
  ])]
)

dayStats = Stats([
  {label: events.day, value: "" + eventCount + " events", hint: "today",          tone: "primary"},
  {label: "Busy",     value: "" + events.busyMinutes + " min", hint: "" + events.busyPercent + "% of day", tone: events.busyPercent > 75 ? "danger" : "info"},
  {label: "Free",     value: "" + (events.totalMinutes - events.busyMinutes) + " min", hint: "longest block",  tone: "success"},
  {label: "Focus",    value: "" + @Count(@Filter(events.rows, "category", "==", "focus")), hint: "blocks scheduled", tone: "default"}
])

followUps = FollowUpBlock([
  FollowUpItem("Find 2h of focus on Thursday"),
  FollowUpItem("Reschedule \"Roadmap review\""),
  FollowUpItem("Send agenda to next standup")
], "Quick actions")

root = Stack([header, dateBar, dayStats, splitLayout, eventSheet, followUps], "column", "l")

The host's single agenda tool

agenda_for({date, category}) returns everything the UI needs for the focused day: rows, busy minutes, free blocks, and a week-level summary. The DatePicker, view toggle, and category toggle all pass bare $variables so the query re-runs automatically when any of them changes.

el.setTools({
  agenda_for: async ({ date, category }) => {
    await sleep(220);
    return buildAgenda(date, category);
  },
});