// app-main.jsx — App shell: header, upload, outline rail, view switch, export, persistence (function () { const { useState, useRef, useEffect, useCallback } = React; const { parseAgenda, SAMPLE_TEXT, PRESENTERS } = window.MeetingData; const { ItemCard, MinutesPreview } = window; const STORE_KEY = "board_minutes_v1"; const DEFAULT_META = { org: "Riverside Community Foundation", kind: "Regular Meeting", date: "2026-06-08", time: "6:00 p.m.", location: "Boardroom & Videoconference", present: "", absent: "", quorum: true, }; // ---------- persistence ---------- function load() { try { return JSON.parse(localStorage.getItem(STORE_KEY)) || null; } catch (e) { return null; } } function save(state) { try { localStorage.setItem(STORE_KEY, JSON.stringify(state)); } catch (e) {} } // ---------- Word / minutes HTML builder ---------- function buildMinutesHTML(meta, items, title) { const esc = (s) => String(s || "").replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" }[c])); const { fmtDate, voteSentence } = window.MinutesHelpers; const present = (meta.present || "").split(/[,\n]/).map((s) => s.trim()).filter(Boolean); const absent = (meta.absent || "").split(/[,\n]/).map((s) => s.trim()).filter(Boolean); let body = ""; items.forEach((it) => { const vs = it.vote.recorded ? voteSentence(it.vote) : ""; const lvl = it.level || (it.isSub ? 1 : 0); const lead = lvl ? "    ".repeat(lvl) : ""; const num = it.number ? esc(it.number) + ". " : ""; body += '
  • ' + lead + "" + num + esc(it.title) + "" + (it.presenter ? ' ' + esc(it.presenter) + "" : "") + (it.page ? ' p. ' + esc(it.page) + "" : "") + "

    "; if (it.notes && it.notes.trim()) it.notes.trim().split(/\n+/).forEach((p) => { body += "

    " + esc(p) + "

    "; }); if (vs) body += "

    " + esc(vs) + "

    "; if (it.actions.length) { body += '

    Action items

    "; } body += "
  • "; }); return '' + esc(title || "Minutes") + '' + "

    " + esc(meta.org) + "

    " + '

    Minutes of the ' + esc(meta.kind) + " of the Board of Directors

    " + '

    ' + esc(fmtDate(meta.date)) + (meta.time ? " · " + esc(meta.time) : "") + (meta.location ? " · " + esc(meta.location) : "") + "

    " + '
    Directors present' + esc(present.join(", ") || "—") + "
    " + (absent.length ? "
    Absent" + esc(absent.join(", ")) + "
    " : "") + "
    Quorum" + (meta.quorum ? "A quorum was present." : "Not established.") + "
    " + '" + '

    _____________________________
    Secretary

    ' + ""; } function downloadWord(meta, items, title) { const html = buildMinutesHTML(meta, items, title); const blob = new Blob(["\ufeff", html], { type: "application/msword" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = (meta.org ? meta.org.replace(/[^\w]+/g, "_") : "Board") + "_Minutes.doc"; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(url), 1000); } // ================= Empty state / Upload ================= function UploadView({ onLoad }) { const [paste, setPaste] = useState(""); const [busy, setBusy] = useState(null); const [err, setErr] = useState(""); const inputRef = useRef(null); function ingest(text, name) { const parsed = parseAgenda(text); if (!parsed.items.length) { setErr("We read the file but couldn’t find any agenda items. Make sure the document lists numbered or bulleted items — or paste the agenda text below."); return; } onLoad(parsed, name); } async function handleFile(file) { if (!file) return; setErr(""); setBusy(file.name); try { const { text, method } = await window.extractAgendaText(file); const parsed = parseAgenda(text); setBusy(null); if (!parsed.items.length) { setErr("Extracted text from this " + method + " file, but found no agenda structure. Try pasting the agenda below, or use the sample to explore."); return; } onLoad(parsed, parsed.title || file.name.replace(/\.[^.]+$/, "")); } catch (e) { setBusy(null); const msg = String(e && e.message); if (msg === "not-a-docx" || msg === "docx-lib-missing") setErr("Couldn’t open that Word file. If it’s an older .doc, re-save as .docx or PDF and try again."); else if (msg === "pdf-lib-missing") setErr("The PDF reader didn’t load — check your connection and try again."); else setErr("Sorry, we couldn’t read that file (" + msg + "). Try a PDF, .docx, or paste the text below."); } } return (
    New session

    Turn an agenda into a working notes form

    Upload your board agenda and we’ll lay out a note field for every item — with one-tap motion language built in.

    {err &&
    {err}
    }
    or paste the agenda text