{
  "name": "Weekday morning brief",
  "version": 1,
  "nodes": [
    {
      "localID": 0,
      "typeID": "trigger.schedule",
      "customName": "Schedule",
      "isDisabled": false,
      "x": 60.0,
      "y": 110.0,
      "config": {
        "mode": "calendar",
        "weekdays": "mon,tue,wed,thu,fri",
        "time": "08:30",
        "customArgs": {}
      }
    },
    {
      "localID": 1,
      "typeID": "action.script",
      "customName": "Today's date",
      "isDisabled": false,
      "x": 380.0,
      "y": 110.0,
      "config": {
        "shell": "/bin/bash",
        "script": "date '+%A, %B %-d, %Y'",
        "outputSchema": ""
      }
    },
    {
      "localID": 2,
      "typeID": "action.apple_calendar_list_events",
      "customName": "List Calendar Events",
      "isDisabled": false,
      "x": 700.0,
      "y": 110.0,
      "config": {
        "calendarName": "",
        "range": "today",
        "hours": 8,
        "days": 7,
        "includeAllDay": true,
        "maxEvents": 50
      }
    },
    {
      "localID": 3,
      "typeID": "action.apple_reminders",
      "customName": "Apple Reminders",
      "isDisabled": false,
      "x": 1020.0,
      "y": 110.0,
      "config": {
        "operation": "list",
        "list": "Reminders",
        "completed": false,
        "limit": 25
      }
    },
    {
      "localID": 4,
      "typeID": "ai.prompt",
      "customName": "Plan the day",
      "isDisabled": false,
      "x": 1340.0,
      "y": 110.0,
      "config": {
        "prompt": "Today is {{output}}.\n\nMy calendar for today:\n{{eventsText}}\n\nMy open reminders, as JSON (dueDate is empty when a reminder has no due date):\n{{results}}\n\nWrite my plan for today in under 120 words.\n1. The first fixed commitment and when it starts.\n2. The reminders that are due today or overdue, oldest first. Skip the rest.\n3. Any gap of an hour or more between events, and which reminder fits in it.\nPlain text, one item per line, no Markdown.",
        "systemPrompt": "You write short, factual morning plans. Use only the events and reminders you are given. Never invent a meeting, a task or a time.",
        "imagePath": "",
        "provider": "",
        "model": "",
        "temperature": 0.3,
        "maxTokens": 0,
        "outputSchema": ""
      }
    },
    {
      "localID": 5,
      "typeID": "transformer.html_template",
      "customName": "HTML Template",
      "isDisabled": false,
      "x": 1660.0,
      "y": 110.0,
      "config": {
        "outputKey": "html",
        "html": "<style>\n  :root { color-scheme: dark; }\n  * { box-sizing: border-box; }\n  body { margin: 0; background: transparent; font: 13px/1.45 -apple-system, BlinkMacSystemFont, \"SF Pro Text\", sans-serif; color: #efe9f7; -webkit-font-smoothing: antialiased; }\n  .brief { width: 820px; border-radius: 22px; overflow: hidden; background: #141019; border: 1px solid rgba(255,255,255,.08); }\n  .sky { position: relative; overflow: hidden; padding: 26px 30px 24px; background:\n      radial-gradient(120% 140% at 88% 120%, rgba(255,160,90,.55), rgba(255,122,89,.25) 38%, transparent 62%),\n      radial-gradient(90% 120% at 10% -20%, rgba(139,108,246,.45), transparent 60%),\n      linear-gradient(180deg, #1d1530, #1a1422); }\n  .sun { position: absolute; right: 64px; bottom: -118px; width: 210px; height: 210px; border-radius: 50%;\n      background: radial-gradient(circle, #ffd58a 0%, #ffab5e 45%, rgba(255,122,89,0) 70%); opacity: .9; }\n  .day { font-size: 13px; letter-spacing: .02em; color: #d8c9f2; }\n  .date { font: 700 38px/1.05 -apple-system, \"SF Pro Display\", sans-serif; letter-spacing: -.02em; margin: 2px 0 10px; }\n  .lede { position: relative; max-width: 470px; color: #e9dff7; font-size: 14px; }\n  .stats { position: absolute; right: 30px; top: 26px; display: flex; gap: 10px; }\n  .stat { background: rgba(20,16,25,.55); border: 1px solid rgba(255,255,255,.1); border-radius: 12px; padding: 8px 12px; min-width: 78px; backdrop-filter: blur(6px); }\n  .stat b { display: block; font: 700 20px/1.1 -apple-system, \"SF Pro Display\", sans-serif; font-variant-numeric: tabular-nums; }\n  .stat span { color: #b9abd3; font-size: 11px; }\n  .main { display: grid; grid-template-columns: 1.25fr 1fr; gap: 22px; padding: 22px 30px 8px; }\n  h2 { font: 600 12px/1 -apple-system, sans-serif; color: #a595c4; margin: 0 0 12px; letter-spacing: .01em; }\n  .tl { position: relative; }\n  .hour { position: absolute; left: 0; right: 0; height: 0; border-top: 1px solid rgba(255,255,255,.06); }\n  .hour i { position: absolute; left: 0; top: -8px; font-style: normal; font-size: 11px; color: #7f7396; font-variant-numeric: tabular-nums; }\n  .ev, .gap { position: absolute; left: 50px; right: 0; border-radius: 10px; padding: 6px 10px; overflow: hidden; }\n  .ev.short { padding: 3px 10px; display: flex; gap: 8px; align-items: baseline; } .ev.short b { font-size: 12px; }\n  .ev { background: linear-gradient(135deg, rgba(139,108,246,.34), rgba(139,108,246,.18)); border: 1px solid rgba(160,135,255,.45); }\n  .ev.first { background: linear-gradient(135deg, rgba(255,171,94,.38), rgba(255,122,89,.2)); border-color: rgba(255,171,94,.6); }\n  .ev b { display: block; font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n  .ev small, .gap small { color: #cbbfe3; font-size: 11px; }\n  .gap { border: 1px dashed rgba(122,220,170,.55); background: rgba(90,200,150,.08); }\n  .gap b { display: block; color: #9fe8c4; font-size: 11px; font-weight: 600; }\n  .gap p { margin: 1px 0 0; font-size: 12px; color: #e3f7ed; }\n  .card { background: #1b1622; border: 1px solid rgba(255,255,255,.07); border-radius: 14px; padding: 14px 16px; margin-bottom: 14px; }\n  .plan { list-style: none; margin: 0; padding: 0; }\n  .plan li { display: grid; grid-template-columns: auto 1fr; gap: 8px; align-items: baseline; padding: 6px 0; border-top: 1px solid rgba(255,255,255,.06); }\n  .plan li:first-child { border-top: 0; padding-top: 0; }\n  .tag { font-size: 10.5px; font-weight: 600; padding: 2px 7px; border-radius: 999px; background: rgba(255,255,255,.08); color: #d9cdef; white-space: nowrap; }\n  .tag.hot { background: rgba(255,122,89,.18); color: #ffb29c; }\n  .tag.warm { background: rgba(255,190,90,.16); color: #ffd28f; }\n  .rem { display: flex; align-items: center; gap: 10px; padding: 7px 0; border-top: 1px solid rgba(255,255,255,.06); }\n  .rem:first-of-type { border-top: 0; padding-top: 0; }\n  .dot { width: 14px; height: 14px; border-radius: 50%; border: 1.5px solid #6d6284; flex: none; }\n  .rem .t { flex: 1; }\n  .pill { font-size: 10.5px; font-weight: 600; padding: 2px 8px; border-radius: 999px; }\n  .pill.over { background: rgba(255,122,89,.2); color: #ffb29c; }\n  .pill.today { background: rgba(255,190,90,.18); color: #ffd28f; }\n  .pill.later { background: rgba(255,255,255,.07); color: #a99cc2; }\n  .foot { display: flex; align-items: center; justify-content: space-between; padding: 6px 30px 22px; color: #7f7396; font-size: 11.5px; }\n  button { font: 600 13px -apple-system, sans-serif; padding: 9px 22px; border: 0; border-radius: 999px; color: #1b1026; cursor: pointer;\n      background: linear-gradient(135deg, #ffd58a, #ff9a6b); box-shadow: 0 6px 18px rgba(255,140,90,.25); }\n  .fallback { white-space: pre-wrap; }\n</style>\n<script type=\"application/json\" id=\"brief-data\">{{payload}}</script>\n<div class=\"brief\">\n  <div class=\"sky\">\n    <div class=\"sun\"></div>\n    <div class=\"day\" id=\"day\"></div>\n    <div class=\"date\" id=\"date\">{{output}}</div>\n    <div class=\"lede\" id=\"lede\"></div>\n    <div class=\"stats\" id=\"stats\"></div>\n  </div>\n  <div class=\"main\">\n    <section>\n      <h2>Your day</h2>\n      <div class=\"tl\" id=\"tl\"></div>\n    </section>\n    <section>\n      <div class=\"card\"><h2>The plan</h2><ul class=\"plan\" id=\"plan\"><li class=\"fallback\">{{result}}</li></ul></div>\n      <div class=\"card\"><h2>Open reminders</h2><div id=\"rems\"></div></div>\n    </section>\n  </div>\n  <div class=\"foot\"><span>Built from today's calendar and your open reminders.</span><button onclick=\"watchflows.emit({ read: true })\">Got it</button></div>\n</div>\n<script>\n(function () {\n  var d = {};\n  try { d = JSON.parse(document.getElementById('brief-data').textContent); } catch (e) { return; }\n  var events = Array.isArray(d.events) ? d.events.filter(function (e) { return !e.isAllDay; }) : [];\n  var rems = Array.isArray(d.results) ? d.results : [];\n  var plan = String(d.result || '').split('\\n').map(function (s) { return s.trim(); }).filter(Boolean);\n  var today = new Date(); today.setHours(0, 0, 0, 0);\n  var el = function (tag, cls, text) { var n = document.createElement(tag); if (cls) n.className = cls; if (text != null) n.textContent = text; return n; };\n  var hm = function (dt) { return dt.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); };\n  var mins = function (dt) { return dt.getHours() * 60 + dt.getMinutes(); };\n\n  // Header: the day and date, a one-line summary, three numbers.\n  document.getElementById('day').textContent = new Date().toLocaleDateString([], { weekday: 'long' });\n  document.getElementById('date').textContent = new Date().toLocaleDateString([], { month: 'long', day: 'numeric' });\n  var ev = events.map(function (e) { return { t: e.title, s: new Date(e.startISO), e: new Date(e.endISO), loc: e.location || '' }; })\n                 .sort(function (a, b) { return a.s - b.s; });\n  var start = Math.min(9 * 60, ev.length ? Math.floor(mins(ev[0].s) / 60) * 60 : 9 * 60);\n  var end = Math.max(18 * 60, ev.length ? Math.ceil(mins(ev[ev.length - 1].e) / 60) * 60 : 18 * 60);\n  var busy = ev.reduce(function (n, x) { return n + (mins(x.e) - mins(x.s)); }, 0);\n  var free = (end - start) - busy, fh = Math.floor(free / 60), fm = free % 60;\n  var gaps = plan.filter(function (l) { return /^gap\\b/i.test(l); });\n  document.getElementById('lede').textContent = 'Good morning. ' + (ev.length ? 'First up: ' + ev[0].t + ' at ' + hm(ev[0].s) + '. ' : 'Nothing on the calendar. ') +\n    (gaps.length ? gaps.length + ' open ' + (gaps.length === 1 ? 'window' : 'windows') + ' to get things done.' : '');\n  [[ev.length, 'events'], [rems.length, 'reminders'], [fh + 'h' + (fm ? ' ' + fm + 'm' : ''), 'free']].forEach(function (s) {\n    var n = el('div', 'stat'); n.appendChild(el('b', null, String(s[0]))); n.appendChild(el('span', null, s[1])); document.getElementById('stats').appendChild(n);\n  });\n\n  // Timeline: hours, the events where they fall, and the model's gap suggestions in the free slots.\n  var px = 0.92, tl = document.getElementById('tl'); tl.style.height = ((end - start) * px + 8) + 'px';\n  for (var h = start; h <= end; h += 60) {\n    var r = el('div', 'hour'); r.style.top = ((h - start) * px + 4) + 'px';\n    var lab = new Date(today); lab.setMinutes(h); r.appendChild(el('i', null, lab.toLocaleTimeString([], { hour: 'numeric' }))); tl.appendChild(r);\n  }\n  var place = function (n, a, b) { n.style.top = ((a - start) * px + 5) + 'px'; n.style.height = Math.max(24, (b - a) * px - 4) + 'px'; tl.appendChild(n); };\n  ev.forEach(function (x, i) {\n    var short = (mins(x.e) - mins(x.s)) <= 45;\n    var n = el('div', 'ev' + (i === 0 ? ' first' : '') + (short ? ' short' : '')); n.appendChild(el('b', null, x.t));\n    n.appendChild(el('small', null, hm(x.s) + ' \u2013 ' + hm(x.e) + (x.loc ? ' \u00b7 ' + x.loc : ''))); place(n, mins(x.s), mins(x.e));\n  });\n  gaps.forEach(function (g) {\n    var m = g.match(/(\\d{1,2})[:.](\\d{2})\\D+?(\\d{1,2})[:.](\\d{2})/); if (!m) return;\n    var a = +m[1] * 60 + +m[2], b = +m[3] * 60 + +m[4];\n    var what = g.replace(/^gap\\s+\\d{1,2}[:.]\\d{2}\\s*(?:to|-|\u2013)\\s*\\d{1,2}[:.]\\d{2}\\s*(?:\\([^)]*\\))?\\s*:?\\s*/i, '');\n    var n = el('div', 'gap'); n.appendChild(el('b', null, 'Open ' + hm(new Date(today.getTime() + a * 6e4)) + ' \u2013 ' + hm(new Date(today.getTime() + b * 6e4))));\n    n.appendChild(el('p', null, what.charAt(0).toUpperCase() + what.slice(1))); place(n, a, b);\n  });\n\n  // The plan: everything but the gaps (those are on the timeline), with its label as a tag.\n  var ul = document.getElementById('plan'), rest = plan.filter(function (l) { return !/^gap\\b/i.test(l); });\n  if (rest.length) {\n    ul.innerHTML = '';\n    rest.forEach(function (l) {\n      var m = l.match(/^([A-Za-z][A-Za-z ]{2,24}):\\s*(.+)$/), li = el('li');\n      var label = m ? m[1] : 'Note', cls = /overdue/i.test(label) ? 'tag hot' : /due|first/i.test(label) ? 'tag warm' : 'tag';\n      li.appendChild(el('span', cls, label)); li.appendChild(el('span', null, m ? m[2] : l)); ul.appendChild(li);\n    });\n  }\n\n  // Reminders, with where each stands against today.\n  var months = 'january february march april may june july august september october november december'.split(' ');\n  var box = document.getElementById('rems');\n  rems.forEach(function (r) {\n    var due = null, m = String(r.dueDate || '').match(/([A-Za-z]+) (\\d{1,2}), (\\d{4})/);\n    if (m && months.indexOf(m[1].toLowerCase()) >= 0) due = new Date(+m[3], months.indexOf(m[1].toLowerCase()), +m[2]);\n    var st = !due ? ['later', 'Someday'] : due < today ? ['over', 'Overdue'] : due.getTime() === today.getTime() ? ['today', 'Today'] : ['later', due.toLocaleDateString([], { month: 'short', day: 'numeric' })];\n    var row = el('div', 'rem'); row.appendChild(el('span', 'dot')); row.appendChild(el('span', 't', r.title)); row.appendChild(el('span', 'pill ' + st[0], st[1])); box.appendChild(row);\n  });\n})();\n</script>\n"
      }
    },
    {
      "localID": 6,
      "typeID": "action.html_view",
      "customName": "HTML View",
      "isDisabled": false,
      "x": 1980.0,
      "y": 110.0,
      "config": {
        "html": "{{html}}",
        "title": "Morning brief",
        "windowMode": "window",
        "chrome": "none",
        "width": 820,
        "height": 800,
        "closeIsDismiss": true,
        "timeoutMinutes": 240
      }
    },
    {
      "localID": 7,
      "typeID": "action.notification",
      "customName": "Brief failed",
      "isDisabled": false,
      "x": 1660.0,
      "y": 330.0,
      "config": {
        "title": "Morning brief",
        "body": "The plan could not be written: {{error}}",
        "actionURL": "",
        "forceDisplay": false
      }
    },
    {
      "localID": 8,
      "typeID": "note",
      "customName": "Gather",
      "isDisabled": false,
      "x": 20.0,
      "y": 50.0,
      "config": {
        "backdrop": true,
        "color": "#64B5F6",
        "text": "Gather",
        "_width": 1260.0,
        "_height": 180.0
      }
    },
    {
      "localID": 9,
      "typeID": "note",
      "customName": "Think",
      "isDisabled": false,
      "x": 1300.0,
      "y": 50.0,
      "config": {
        "backdrop": true,
        "color": "#BA68C8",
        "text": "Think",
        "_width": 300.0,
        "_height": 180.0
      }
    },
    {
      "localID": 10,
      "typeID": "note",
      "customName": "Show",
      "isDisabled": false,
      "x": 1620.0,
      "y": 50.0,
      "config": {
        "backdrop": true,
        "color": "#81C784",
        "text": "Show",
        "_width": 620.0,
        "_height": 400.0
      }
    }
  ],
  "connections": [
    {
      "sourceLocalID": 0,
      "sourcePortID": "outlet_output",
      "targetLocalID": 1,
      "targetPortID": "inlet_input"
    },
    {
      "sourceLocalID": 1,
      "sourcePortID": "outlet_output",
      "targetLocalID": 2,
      "targetPortID": "inlet_input"
    },
    {
      "sourceLocalID": 2,
      "sourcePortID": "outlet_output",
      "targetLocalID": 3,
      "targetPortID": "inlet_input"
    },
    {
      "sourceLocalID": 3,
      "sourcePortID": "outlet_output",
      "targetLocalID": 4,
      "targetPortID": "inlet_input"
    },
    {
      "sourceLocalID": 4,
      "sourcePortID": "outlet_result",
      "targetLocalID": 5,
      "targetPortID": "inlet_input"
    },
    {
      "sourceLocalID": 5,
      "sourcePortID": "outlet_output",
      "targetLocalID": 6,
      "targetPortID": "inlet_input"
    },
    {
      "sourceLocalID": 4,
      "sourcePortID": "outlet_failure",
      "targetLocalID": 7,
      "targetPortID": "inlet_input"
    }
  ],
  "background": {
    "type": "image",
    "ref": {
      "type": "bundled",
      "id": "dusk"
    },
    "fit": "cover"
  }
}
