{
  "format": "subfork.graph/1",
  "definition": {
    "version": "0.1.0",
    "name": "Signal Garden",
    "description": "A botanical visualization of real earthquake records. Remap stem height, bloom size, and petal color; inspect each specimen or reuse the dataset. Run to refresh. No API key required.",
    "nodes": [
      {
        "node_instance_id": "feed_url",
        "node_id": "n_url_input",
        "node_version": "1.0.0",
        "title": "USGS / past day / M2.5+",
        "params": {
          "url": "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson"
        }
      },
      {
        "node_instance_id": "fetch",
        "node_id": "n_fetch_url",
        "node_version": "1.0.0",
        "title": "Fetch earthquake snapshot",
        "params": {
          "method": "GET",
          "headers": {
            "Accept": "application/geo+json, application/json"
          },
          "timeout_seconds": 30,
          "max_response_bytes": 5000000
        },
        "exposed_inputs": [
          "url"
        ],
        "inputs_visibility_configured": true,
        "exposed_outputs": [
          "body",
          "status"
        ],
        "outputs_visibility_configured": true
      },
      {
        "node_instance_id": "parse",
        "node_id": "n_json_parse",
        "node_version": "1.0.0",
        "title": "Parse GeoJSON snapshot",
        "params": {}
      },
      {
        "node_instance_id": "encode",
        "node_id": "n_json_stringify",
        "node_version": "1.0.0",
        "title": "HTML-safe dataset",
        "params": {
          "html_safe": true
        }
      },
      {
        "node_instance_id": "features",
        "node_id": "n_json_path",
        "node_version": "1.0.0",
        "title": "Earthquake features",
        "params": {
          "path": "features"
        }
      },
      {
        "node_instance_id": "metadata",
        "node_id": "n_json_path",
        "node_version": "1.0.0",
        "title": "Source metadata",
        "params": {
          "path": "metadata"
        }
      },
      {
        "node_instance_id": "rows",
        "node_id": "n_map_fields",
        "node_version": "1.0.0",
        "title": "Reusable earthquake rows",
        "params": {
          "mapping": {
            "id": "id",
            "place": "properties.place",
            "magnitude": "properties.mag",
            "time": "properties.time",
            "lon": "geometry.coordinates.0",
            "lat": "geometry.coordinates.1",
            "depth_km": "geometry.coordinates.2",
            "url": "properties.url"
          }
        }
      },
      {
        "node_instance_id": "dataset",
        "node_id": "n_json_template",
        "node_version": "1.0.0",
        "title": "Earthquake dataset",
        "params": {
          "template": {
            "schema_version": "planet-pulse/1",
            "source": "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson",
            "generated_at_ms": "{{ inputs.metadata.generated }}",
            "window": "past day",
            "minimum_magnitude": 2.5,
            "timezone": "UTC",
            "units": {
              "time": "Unix milliseconds",
              "lat": "degrees",
              "lon": "degrees",
              "depth_km": "kilometers",
              "magnitude": "reported USGS magnitude"
            },
            "rows": "{{ inputs.rows }}"
          }
        },
        "custom_inputs": {
          "metadata": "json",
          "rows": "json"
        },
        "exposed_inputs": [
          "metadata",
          "rows"
        ],
        "inputs_visibility_configured": true
      },
      {
        "node_instance_id": "view",
        "node_id": "n_html_template",
        "node_version": "1.0.0",
        "title": "Signal Garden / botanical renderer",
        "params": {
          "template": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <title>Signal Garden</title>\n    <style>\n      :root {\n        color-scheme: light;\n        --paper: #f1ecdf;\n        --ink: #253e35;\n        --muted: #647567;\n        --line: #c9cfbd;\n        --accent: #376b50;\n      }\n      * { box-sizing: border-box; }\n      body {\n        margin: 0;\n        color: var(--ink);\n        background: radial-gradient(circle at 1px 1px, #75836b14 1px, transparent 0) 0 0 / 9px 9px, var(--paper);\n        font: 15px/1.5 Georgia, serif;\n      }\n      main { max-width: 1500px; padding: 32px 36px; margin: auto; }\n      header { display: flex; justify-content: space-between; gap: 32px; align-items: end; }\n      .eyebrow, label, button, select, .caption, footer, dt, .stat { font-family: monospace; }\n      .eyebrow { font-size: 11px; letter-spacing: .2em; color: var(--accent); }\n      h1 { font-size: clamp(42px, 6vw, 80px); font-weight: 400; line-height: 1; margin: 16px 0; letter-spacing: -.04em; }\n      h1 em { color: var(--accent); }\n      p { margin: 8px 0; }\n      .caption { font-size: 11px; color: var(--muted); line-height: 1.7; }\n      .source { max-width: 350px; overflow-wrap: anywhere; }\n      .controls { display: flex; gap: 18px; flex-wrap: wrap; align-items: end; padding: 20px 0; margin-top: 22px; border-block: 1px solid var(--line); }\n      label { display: grid; gap: 6px; color: var(--muted); font-size: 11px; }\n      button, select { padding: 8px 12px; border: 1px solid #a6b6a0; background: #f9f6ed; color: var(--ink); border-radius: 3px; font-size: 12px; }\n      button { cursor: pointer; }\n      button:hover { background: #dde7d6; }\n      :focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }\n      .tally { margin-left: auto; text-align: right; }\n      .stat { font-size: 28px; }\n      .garden-scroll { overflow-x: auto; margin-top: 20px; border-bottom: 1px solid var(--line); }\n      svg { display: block; width: 100%; min-width: 720px; height: auto; }\n      .flower { cursor: pointer; outline: none; }\n      .halo { opacity: 0; transition: opacity .15s; }\n      .flower:hover .halo, .flower:focus .halo, .flower[aria-pressed=\"true\"] .halo { opacity: 1; }\n      .sway { transform-origin: 0px 0px; animation: sway 7s ease-in-out infinite alternate; }\n      @keyframes sway { from { transform: rotate(-1.3deg); } to { transform: rotate(1.3deg); } }\n      .paused .sway { animation-play-state: paused; }\n      @media (prefers-reduced-motion: reduce) { .sway { animation: none; } .halo { transition: none; } }\n      .legend { display: flex; flex-wrap: wrap; gap: 22px; padding: 12px 0; }\n      #color-scale { display: inline-block; width: 75px; height: 8px; margin-right: 8px; }\n      .below { display: grid; grid-template-columns: minmax(0, 1fr) minmax(260px, 1fr); gap: 36px; margin-top: 22px; }\n      h2 { font-size: 26px; font-weight: 400; margin: 0 0 12px; }\n      .records { max-height: 210px; overflow-y: auto; display: grid; gap: 6px; align-content: start; }\n      .records button { text-align: left; background: transparent; border-color: var(--line); }\n      .records button[aria-pressed=\"true\"] { background: #dde7d6; border-color: var(--accent); }\n      #detail { min-height: 160px; padding: 20px; background: #e2e8d8; border-left: 3px solid var(--accent); overflow-wrap: anywhere; }\n      #detail h3 { margin: 0 0 12px; font-size: 20px; font-weight: 400; }\n      dl { display: grid; grid-template-columns: 110px 1fr; gap: 6px 14px; margin: 0; }\n      dt { font-size: 11px; color: var(--muted); } dd { margin: 0; }\n      #notice, #error { margin-top: 12px; } #error { color: #9a3627; }\n      footer { font-size: 11px; color: var(--muted); margin-top: 28px; border-top: 1px solid var(--line); padding-top: 16px; }\n      @media (max-width: 700px) { main { padding: 22px 16px; } header { display: block; } .source { margin-top: 20px; } .below { grid-template-columns: 1fr; gap: 20px; } .tally { margin-left: 0; } }\n    </style>\n  </head>\n  <body>\n    <main>\n      <header>\n        <div><div class=\"eyebrow\">SUBFORK / BOTANICAL DATA STUDIES / 02</div><h1>Signal <em>Garden.</em></h1><p>Every record, a flower. The same world, seen differently.</p></div>\n        <div class=\"source caption\"><div id=\"source\">Reading dataset...</div><div id=\"snapshot\"></div></div>\n      </header>\n      <div class=\"controls\">\n        <label>Stem height<select id=\"height\" aria-label=\"Stem height field\"></select></label>\n        <label>Bloom size<select id=\"size\" aria-label=\"Bloom size field\"></select></label>\n        <label>Petal color<select id=\"color\" aria-label=\"Petal color field\"></select></label>\n        <label>Palette<select id=\"palette\"><option value=\"meadow\">Meadow</option><option value=\"sunset\">Sunset</option><option value=\"tide\">Tide</option></select></label>\n        <button id=\"motion\" type=\"button\" aria-pressed=\"false\">Pause motion</button>\n        <div class=\"tally\"><span id=\"count\" class=\"stat\">--</span> <span class=\"caption\">FLOWERS</span></div>\n      </div>\n      <p id=\"error\" role=\"alert\" hidden></p>\n      <p id=\"notice\" class=\"caption\"></p>\n      <div class=\"garden-scroll\" tabindex=\"0\" aria-label=\"Garden canvas. Scroll horizontally on small screens.\">\n        <svg id=\"garden\" viewBox=\"0 0 1200 500\" role=\"group\" aria-label=\"Interactive data garden. Each flower is also available in the record list below.\">\n          <defs><linearGradient id=\"ground\" x2=\"0\" y2=\"1\"><stop stop-color=\"#96ae7866\"/><stop offset=\"1\" stop-color=\"#96ae7800\"/></linearGradient></defs>\n          <circle cx=\"1025\" cy=\"100\" r=\"67\" fill=\"#dfb967\" opacity=\".15\"/>\n          <path d=\"M0 438 Q300 413 600 439 T1200 430 L1200 500 L0 500Z\" fill=\"url(#ground)\"/>\n          <path d=\"M20 456 Q330 431 610 457 T1180 448\" fill=\"none\" stroke=\"#9cab8b\" stroke-width=\"1\"/>\n          <g id=\"flowers\"></g>\n        </svg>\n      </div>\n      <div class=\"legend caption\"><span id=\"height-scale\"></span><span id=\"size-scale\"></span><span><i id=\"color-scale\"></i><span id=\"color-label\"></span></span></div>\n      <div class=\"below\"><section><h2>Field notes</h2><div id=\"detail\" aria-live=\"polite\">Select a flower to inspect the record behind it.</div></section><section><h2>Specimen index</h2><div id=\"records\" class=\"records\" aria-label=\"Records\"></div></section></div>\n      <footer>Run the graph again to refresh the snapshot. Growth and swaying are visual metaphors, not real-time measurements or forecasts. Layout is deterministic. Field scales use the displayed records; position and petal count are decorative.</footer>\n    </main>\n    <!-- prettier-ignore -->\n    <script id=\"dataset\" type=\"application/json\">{{ inputs.dataset_json }}</script>\n    <script>\n      'use strict';\n      const $ = id => document.getElementById(id);\n      const palettes = { meadow: ['#deb254', '#db8270', '#895f78'], sunset: ['#efc963', '#dc8353', '#a94842'], tide: ['#9cc4ae', '#5faaa4', '#3e697f'] };\n      const pretty = key => key.replaceAll('_', ' ');\n      const number = value => Number.isFinite(value) ? Number(value.toFixed(2)).toLocaleString() : '--';\n      const stamp = value => new Date(value).toISOString().replace('T', ' ').replace(/\\.\\d{3}Z$/, ' UTC');\n      function hash(text) { let value = 2166136261; for (const char of text) value = Math.imul(value ^ char.charCodeAt(0), 16777619); return value >>> 0; }\n      function element(tag, attributes = {}) {\n        const node = document.createElementNS('http://www.w3.org/2000/svg', tag);\n        for (const [key, value] of Object.entries(attributes)) node.setAttribute(key, String(value));\n        return node;\n      }\n      function prepare(dataset) {\n        if (!dataset || !Array.isArray(dataset.rows) || !Number.isFinite(dataset.generated_at_ms) || Math.abs(dataset.generated_at_ms) > 8.64e15) throw new Error('Expected a dataset with rows and a valid generated_at_ms timestamp.');\n        const valid = dataset.rows.filter(row => row && typeof row === 'object' && !Array.isArray(row));\n        const rows = valid.map((row, index) => ({ ...row, _index: index, age_hours: Number.isFinite(row.time) ? Math.max(0, (dataset.generated_at_ms - row.time) / 3600000) : null }))\n          .sort((a, b) => (Number.isFinite(b.time) ? b.time : -Infinity) - (Number.isFinite(a.time) ? a.time : -Infinity) || a._index - b._index).slice(0, 60);\n        return { rows, total: valid.length, omitted: dataset.rows.length - valid.length };\n      }\n      function extent(rows, key) {\n        const values = rows.map(row => row[key]).filter(Number.isFinite);\n        return values.length ? [Math.min(...values), Math.max(...values)] : [0, 0];\n      }\n      function fraction(value, range) {\n        if (!Number.isFinite(value)) return null;\n        return range[0] === range[1] ? .5 : Math.max(0, Math.min(1, (value - range[0]) / (range[1] - range[0])));\n      }\n      // The renderer consumes normalized rows, never provider-specific GeoJSON.\n      try {\n        const dataset = JSON.parse($('dataset').textContent);\n        const { rows, total, omitted } = prepare(dataset);\n        const numeric = [...new Set(rows.flatMap(row => Object.keys(row)))].filter(key => !['id', 'time', '_index'].includes(key) && rows.some(row => Number.isFinite(row[key]))).sort();\n        const categories = [...new Set(rows.flatMap(row => Object.keys(row)))].filter(key => !['id', 'url', 'place', 'name', 'title'].includes(key) && rows.some(row => typeof row[key] === 'string') && !numeric.includes(key)).sort();\n        const label = row => String(row.place || row.name || row.title || row.id || 'Record ' + (row._index + 1));\n        const units = key => key === 'age_hours' ? 'hours' : String(dataset.units?.[key] || '');\n        $('source').textContent = String(dataset.source || 'User-supplied dataset');\n        $('snapshot').textContent = 'Snapshot: ' + stamp(dataset.generated_at_ms) + (Date.now() - dataset.generated_at_ms > 7200000 ? ' (older than 2 hours)' : '');\n        $('count').textContent = String(rows.length);\n        $('notice').textContent = (total > 60 ? 'Showing the newest 60 of ' + total + ' records. ' : '') + (omitted ? omitted + ' invalid records omitted. ' : '') + (rows.length ? 'Hover to highlight; select or keyboard-focus a flower to read its field notes.' : 'No records in this snapshot. Run again or connect another dataset.');\n        let selected = null;\n        const links = new Map();\n        function populate(id, fields, preferred) {\n          for (const field of fields) { const option = document.createElement('option'); option.value = field; option.textContent = pretty(field); $(id).append(option); }\n          $(id).disabled = !fields.length;\n          $(id).value = fields.includes(preferred) ? preferred : fields[0] || '';\n        }\n        populate('height', numeric, 'age_hours'); populate('size', numeric, 'magnitude'); populate('color', [...numeric, ...categories], 'depth_km');\n        function inspect(row) {\n          selected = row;\n          $('detail').replaceChildren();\n          const heading = document.createElement('h3'); heading.textContent = label(row);\n          const list = document.createElement('dl');\n          const fields = [...new Set(['time', $('height').value, $('size').value, $('color').value, 'lat', 'lon'])].filter(key => key && Object.hasOwn(row, key));\n          for (const key of fields) {\n            const dt = document.createElement('dt'), dd = document.createElement('dd'); dt.textContent = pretty(key);\n            dd.textContent = key === 'time' && Number.isFinite(row.time) && Math.abs(row.time) <= 8.64e15 ? stamp(row.time) : (Number.isFinite(row[key]) ? number(row[key]) + ' ' + units(key) : row[key] == null ? 'Not reported' : String(row[key]));\n            list.append(dt, dd);\n          }\n          $('detail').append(heading, list);\n          for (const [candidate, pair] of links) for (const node of pair) node.setAttribute('aria-pressed', String(candidate === row));\n        }\n        function draw() {\n          const heightKey = $('height').value, sizeKey = $('size').value, colorKey = $('color').value;\n          const heightRange = extent(rows, heightKey), sizeRange = extent(rows, sizeKey), colorRange = extent(rows, colorKey);\n          const categorical = categories.includes(colorKey);\n          const categoryValues = [...new Set(rows.map(row => row[colorKey]).filter(value => typeof value === 'string'))].sort();\n          const colors = palettes[$('palette').value];\n          const rangeLabel = (key, range) => key ? pretty(key) + ': ' + number(range[0]) + ' to ' + number(range[1]) + ' ' + units(key) : 'uniform';\n          $('height-scale').textContent = 'Height / ' + rangeLabel(heightKey, heightRange);\n          $('size-scale').textContent = 'Bloom / ' + rangeLabel(sizeKey, sizeRange);\n          $('color-scale').style.background = 'linear-gradient(to right, ' + colors.join(',') + ')';\n          $('color-label').textContent = categorical ? pretty(colorKey) + ': ' + categoryValues.map((value, index) => value + ' (color ' + (index % 3 + 1) + ')').join(', ') : rangeLabel(colorKey, colorRange) + ' / low to high';\n          $('flowers').replaceChildren(); $('records').replaceChildren(); links.clear();\n          // Three beds keep 60 specimens bounded and each flower keyboard reachable.\n          rows.forEach((row, index) => {\n            const bed = index % 3, column = Math.floor(index / 3), columns = Math.ceil((rows.length - bed) / 3);\n            const x = 50 + (column + .5) * 1100 / Math.max(1, columns), base = 365 + bed * 43;\n            const seed = hash(String(row.id || label(row)) + ':' + row._index);\n            const height = 65 + (fraction(row[heightKey], heightRange) ?? .5) * 180;\n            const radius = 8 + (fraction(row[sizeKey], sizeRange) ?? .5) * 14;\n            const colorValue = fraction(row[colorKey], colorRange);\n            const color = categorical ? (typeof row[colorKey] === 'string' ? colors[categoryValues.indexOf(row[colorKey]) % colors.length] : '#99a394') : colorValue === null ? '#99a394' : colors[Math.min(2, Math.floor(colorValue * 3))];\n            const bend = (seed % 19) - 9;\n            const group = element('g', { class: 'flower', transform: 'translate(' + x + ',' + base + ')', tabindex: 0, role: 'button', 'aria-label': label(row), 'aria-pressed': String(row === selected) });\n            const sway = element('g', { class: 'sway', style: 'animation-delay:-' + (seed % 7) + 's;animation-duration:' + (6 + seed % 5) + 's' });\n            sway.append(element('path', { d: 'M0 0 Q' + bend + ' ' + -height / 2 + ' ' + bend + ' ' + -height, fill: 'none', stroke: '#63815b', 'stroke-width': 1.8 }));\n            for (const side of [-1, 1]) { const y = -height * (side < 0 ? .35 : .6); sway.append(element('path', { d: 'M0 ' + y + ' Q' + side * 30 + ' ' + (y - 30) + ' ' + side * 36 + ' ' + (y - 13) + ' Q' + side * 19 + ' ' + (y + 7) + ' 0 ' + y, fill: bed === 1 ? '#8ca67a' : '#718e67', opacity: .8 })); }\n            const bloom = element('g', { transform: 'translate(' + bend + ',' + -height + ')' });\n            bloom.append(element('circle', { class: 'halo', r: radius * 2 + 7, fill: 'none', stroke: '#376b50', 'stroke-width': 1.5, 'stroke-dasharray': '3 4' }));\n            const petals = 7 + seed % 3;\n            for (let petal = 0; petal < petals; petal++) bloom.append(element('ellipse', { cx: 0, cy: -radius * .9, rx: radius * .58, ry: radius, transform: 'rotate(' + petal * 360 / petals + ')', fill: color, 'fill-opacity': .83, stroke: '#ffffff66', 'stroke-width': .6 }));\n            bloom.append(element('circle', { r: radius * .43, fill: '#745b35' }), element('circle', { r: radius * .18, fill: '#f2d68a' }));\n            sway.append(bloom); group.append(sway); $('flowers').append(group);\n            group.addEventListener('click', () => inspect(row)); group.addEventListener('focus', () => inspect(row));\n            group.addEventListener('keydown', event => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); inspect(row); } });\n            const button = document.createElement('button'); button.type = 'button'; button.textContent = String(index + 1).padStart(2, '0') + ' / ' + label(row); button.setAttribute('aria-pressed', String(row === selected)); button.addEventListener('click', () => inspect(row)); $('records').append(button); links.set(row, [group, button]);\n          });\n          if (selected) inspect(selected);\n          else if (!rows.length) $('detail').textContent = 'No specimens to inspect.';\n        }\n        const reduced = window.matchMedia('(prefers-reduced-motion: reduce)');\n        let paused = reduced.matches;\n        function updateMotion() { $('garden').classList.toggle('paused', paused); $('motion').setAttribute('aria-pressed', String(paused)); $('motion').textContent = reduced.matches ? 'Reduced motion' : paused ? 'Resume motion' : 'Pause motion'; $('motion').disabled = reduced.matches; }\n        $('motion').addEventListener('click', () => { paused = !paused; updateMotion(); });\n        reduced.addEventListener('change', () => { if (reduced.matches) paused = true; updateMotion(); });\n        for (const id of ['height', 'size', 'color', 'palette']) $(id).addEventListener('change', draw);\n        updateMotion(); draw();\n      } catch (error) { $('error').hidden = false; $('error').textContent = error instanceof Error ? error.message : 'Unable to grow this dataset.'; $('source').textContent = 'Dataset unavailable'; }\n    </script>\n  </body>\n</html>\n"
        },
        "custom_inputs": {
          "dataset_json": "str"
        },
        "exposed_inputs": [
          "dataset_json"
        ],
        "inputs_visibility_configured": true
      },
      {
        "node_instance_id": "response",
        "node_id": "n_html_response",
        "node_version": "1.0.0",
        "title": "Signal Garden page",
        "params": {}
      },
      {
        "node_instance_id": "data_response",
        "node_id": "n_json_response",
        "node_version": "1.0.0",
        "title": "Reusable dataset response",
        "params": {}
      }
    ],
    "edges": [
      {
        "source_node_id": "feed_url",
        "source_output": "url",
        "target_node_id": "fetch",
        "target_input": "url"
      },
      {
        "source_node_id": "fetch",
        "source_output": "body",
        "target_node_id": "parse",
        "target_input": "text"
      },
      {
        "source_node_id": "dataset",
        "source_output": "value",
        "target_node_id": "encode",
        "target_input": "value"
      },
      {
        "source_node_id": "parse",
        "source_output": "value",
        "target_node_id": "features",
        "target_input": "value"
      },
      {
        "source_node_id": "parse",
        "source_output": "value",
        "target_node_id": "metadata",
        "target_input": "value"
      },
      {
        "source_node_id": "features",
        "source_output": "value",
        "target_node_id": "rows",
        "target_input": "value"
      },
      {
        "source_node_id": "metadata",
        "source_output": "value",
        "target_node_id": "dataset",
        "target_input": "metadata"
      },
      {
        "source_node_id": "rows",
        "source_output": "items",
        "target_node_id": "dataset",
        "target_input": "rows"
      },
      {
        "source_node_id": "encode",
        "source_output": "text",
        "target_node_id": "view",
        "target_input": "dataset_json"
      },
      {
        "source_node_id": "view",
        "source_output": "html",
        "target_node_id": "response",
        "target_input": "html"
      },
      {
        "source_node_id": "dataset",
        "source_output": "value",
        "target_node_id": "data_response",
        "target_input": "value"
      }
    ],
    "graph_outputs": {
      "html": {
        "node_instance_id": "view",
        "output_name": "html"
      },
      "response": {
        "node_instance_id": "response",
        "output_name": "response"
      },
      "dataset": {
        "node_instance_id": "dataset",
        "output_name": "value"
      },
      "data_response": {
        "node_instance_id": "data_response",
        "output_name": "response"
      },
      "rows": {
        "node_instance_id": "rows",
        "output_name": "items"
      },
      "geojson": {
        "node_instance_id": "parse",
        "output_name": "value"
      }
    },
    "metadata": {
      "template": {
        "category": "html",
        "status": "provider-backed-probe",
        "summary": "Reusable SVG garden from a normalized dataset, initially wired to the same USGS rows as Planet Pulse.",
        "requires": {
          "nodes": [
            "n_url_input",
            "n_fetch_url",
            "n_json_parse",
            "n_json_stringify",
            "n_json_path",
            "n_map_fields",
            "n_json_template",
            "n_html_template",
            "n_html_response",
            "n_json_response"
          ],
          "panels": [
            "html",
            "table",
            "json"
          ],
          "capabilities": [
            "public-http"
          ],
          "secrets": []
        }
      },
      "canvas": {
        "positions": {
          "feed_url": {
            "x": 40,
            "y": 200
          },
          "fetch": {
            "x": 320,
            "y": 200
          },
          "parse": {
            "x": 600,
            "y": 200
          },
          "features": {
            "x": 880,
            "y": 60
          },
          "metadata": {
            "x": 880,
            "y": 280
          },
          "rows": {
            "x": 1160,
            "y": 60
          },
          "dataset": {
            "x": 1440,
            "y": 180
          },
          "encode": {
            "x": 1720,
            "y": 100
          },
          "view": {
            "x": 2000,
            "y": 100
          },
          "response": {
            "x": 2280,
            "y": 100
          },
          "data_response": {
            "x": 1720,
            "y": 340
          }
        },
        "recommended_panels": [
          {
            "kind": "html",
            "binding": {
              "nodeInstanceId": "view",
              "outputName": "html"
            }
          },
          {
            "kind": "table",
            "binding": {
              "nodeInstanceId": "rows",
              "outputName": "items"
            }
          },
          {
            "kind": "json",
            "binding": {
              "nodeInstanceId": "dataset",
              "outputName": "value"
            }
          }
        ]
      }
    }
  }
}
