/* guide.jsx — the in-app tutorial ("Guide" tab).

   ⚠ KEEP THIS IN SYNC WITH THE PRODUCT. Whenever a feature is added, changed or
   removed anywhere in Sinux Signals, update the matching section here in the
   same change. The "snapshots" are live mock components rendered with the real
   design system, so they restyle themselves — but the words must be maintained.

   Role gating: every section declares the minimum role that can see it.
   Viewers get the core guide; analysts also see their own-scope tools; editors
   the editor tools; admins (and the owner) see everything below their rank too.
   A couple of sections are gated by a capability rather than rank (e.g. Bug
   triage needs the owner-granted bugs cap) — those take an extra prop.
*/

const GUIDE_RANK = { viewer: 1, analyst: 2, editor: 3, admin: 4, server_owner: 5, owner: 6 };

const GUIDE_ROLE_LABEL = {
  viewer: "Everyone",
  analyst: "Analyst+",
  editor: "Editor+",
  admin: "Admin+",
  server_owner: "Server owner",
  owner: "Owner",
};

function GuideRoleChip({ min }) {
  return <span className={cx("guide-role", "r-" + min)}>{GUIDE_ROLE_LABEL[min]}</span>;
}

// "Snapshot" frame — a faux window around a live mock of the real UI, so the
// examples always match the current design instead of going stale like PNGs.
function Shot({ caption, children }) {
  return (
    <figure className="guide-shot">
      <div className="guide-shot-bar"><span /><span /><span /></div>
      <div className="guide-shot-body">{children}</div>
      {caption && <figcaption className="guide-shot-cap">{caption}</figcaption>}
    </figure>
  );
}

function GSection({ id, title, min, role, children }) {
  if ((GUIDE_RANK[role] || 0) < (GUIDE_RANK[min] || 99)) return null;
  return (
    <section id={"g-" + id} className="guide-sec">
      <h3 className="guide-sec-title">{title} <GuideRoleChip min={min} /></h3>
      {children}
    </section>
  );
}

// ── Live demo snippets ───────────────────────────────────────────────────────

const GUIDE_ACTION_DESC = {
  open:   "A new position was entered.",
  add:    "Bought more of an existing position.",
  trim:   "Sold part — banking profit, keeping runners.",
  cut:    "Stopped out / exited because the setup failed.",
  close:  "Full exit — the position moves to Recaps.",
  hold:   "No buy or sell — a status update on an open position.",
  watch:  "An idea being watched. Not a position (lives in Watchlist).",
  expire: "An option reached expiry with no exit posted.",
};

function DemoActions() {
  return (
    <div className="guide-rows">
      {Object.keys(GUIDE_ACTION_DESC).map(a => (
        <div key={a} className="guide-row">
          <ActionPill kind={a} />
          <span className="guide-row-txt">{GUIDE_ACTION_DESC[a]}</span>
        </div>
      ))}
    </div>
  );
}

function DemoInstruments() {
  return (
    <div className="guide-rows">
      <div className="guide-row"><InstrumentTag event={{ instrument: "option", optionType: "call" }} /><span className="guide-row-txt">Call option — profits when the underlying rises.</span></div>
      <div className="guide-row"><InstrumentTag event={{ instrument: "option", optionType: "put" }} /><span className="guide-row-txt">Put option — profits when the underlying falls.</span></div>
      <div className="guide-row"><InstrumentTag event={{ instrument: "shares" }} /><span className="guide-row-txt">Plain shares — no strike or expiry.</span></div>
    </div>
  );
}

function DemoConfidence() {
  return (
    <div className="guide-rows">
      <div className="guide-row"><ConfPill level="high" /><span className="guide-row-txt">An explicit call with a clear action ("Bought NVDA 210C 6/20 @ 4.40").</span></div>
      <div className="guide-row"><ConfPill level="medium" /><span className="guide-row-txt">Probably a trade, but loosely worded.</span></div>
      <div className="guide-row"><ConfPill level="low" /><span className="guide-row-txt">Ambiguous chatter — read the raw message before acting on it.</span></div>
    </div>
  );
}

function DemoPositionRow() {
  return (
    <div className="guide-posrow">
      <div className="guide-posrow-l">
        <span className="ticker">$NVDA</span>
        <InstrumentTag event={{ instrument: "option", optionType: "call" }} />
        <span className="mono guide-dim">Jun 20 · 210 · SL 3.80</span>
      </div>
      <div className="guide-posrow-r">
        <span className="mono">4.40 <span className="guide-up">→ 6.10 (+38.6%)</span></span>
        <ActionPill kind="hold" />
        <ConfPill level="high" />
      </div>
    </div>
  );
}

function DemoDiscordFlow() {
  return (
    <div className="guide-flow">
      <div className="guide-discord">
        <span className="guide-discord-author">DEV</span>
        <span className="guide-discord-msg">Bought $NVDA 210C 6/20 @ 4.40 — SL 3.80</span>
      </div>
      <div className="guide-flow-arrow mono">→ parsed →</div>
      <div className="guide-parsed mono">
        <span>ticker <strong>NVDA</strong></span>
        <span>action <strong>open</strong></span>
        <span>contract <strong>6/20 210C</strong></span>
        <span>fill <strong>4.40</strong></span>
        <span>stop <strong>3.80</strong></span>
      </div>
    </div>
  );
}

// ── The tab ──────────────────────────────────────────────────────────────────

function GuideTab({ role, canManage, canViewBugs, canViewRatings, canViewTodos }) {
  const r = role || "viewer";
  const toc = [
    ["what", "What is Sinux Signals", "viewer"],
    ["reading", "Reading a signal", "viewer"],
    ["positions", "Open Positions", "viewer"],
    ["events", "Signal Events", "analyst"],
    ["watchlist", "Watchlist", "viewer"],
    ...(window.CHARTS_ENABLED ? [["charts", "Charts", "viewer"]] : []),
    ["recaps", "Recaps", "viewer"],
    ["sources", "Sources", "viewer"],
    ["notifications", "Notifications", "viewer"],
    ["install", "Install the app", "viewer"],
    ["tips", "Tips & shortcuts", "viewer"],
    ["report", "Reporting a bug", "viewer"],
    ["rating", "Rating the platform", "viewer"],
    ["an-tools", "Your tools", "analyst"],
    ["ed-access", "Managing users", "editor"],
    ["ed-audit", "Audit log", "editor"],
    ["ad-channels", "Channels", "admin"],
    ["ad-analysts", "Analysts", "admin"],
    ["ad-manage", "Managing positions", "admin"],
    ["ad-cleanup", "Correcting data", "admin"],
    ["ad-access", "Access & roles", "admin"],
    ...(canViewBugs ? [["ad-bugs", "Bug triage", "admin"]] : []),
    ...(canViewRatings ? [["ad-ratings", "Ratings", "admin"]] : []),
    ...(canViewTodos ? [["ad-todos", "To-Do", "admin"]] : []),
    ["ow-servers", "Servers & integrations", "owner"],
    ["ow-assistant", "Assistant", "owner"],
  ].filter(([, , min]) => (GUIDE_RANK[r] || 0) >= (GUIDE_RANK[min] || 99));

  function jump(id) {
    const el = document.getElementById("g-" + id);
    if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
  }

  return (
    <div className="guide">
      <aside className="guide-toc">
        <div className="guide-toc-hdr mono">On this page</div>
        {toc.map(([id, label, min]) => (
          <button key={id} className="guide-toc-item" onClick={() => jump(id)}>
            <span>{label}</span>
            {min !== "viewer" && <GuideRoleChip min={min} />}
          </button>
        ))}
        <div className="guide-toc-foot mono">Guide for your role: <strong>{r}</strong> · v{APP_VERSION}</div>
      </aside>

      <div className="guide-body">
        <div className="guide-tour-cta">
          <div className="gtc-txt">
            <div className="gtc-title">{I("zap", { size: 15 })} New here? Take the guided tour</div>
            <div className="gtc-sub">A one-minute interactive walkthrough of the app — it points out each part as you go. Skip or replay it anytime.</div>
          </div>
          <button className="btn primary" onClick={() => window.dispatchEvent(new CustomEvent("sinux:start-tour"))}>Start tour</button>
        </div>

        <GSection id="what" title="What is Sinux Signals?" min="viewer" role={r}>
          <p>Sinux Signals turns analysts' Discord posts into a live trading dashboard. A bot watches the registered channels; every message from a registered analyst is parsed by AI into a structured trade event, which flows into <strong>Positions</strong>, <strong>Events</strong>, <strong>Watchlist</strong>{window.CHARTS_ENABLED && <>, <strong>Charts</strong></>} and <strong>Recaps</strong> within seconds — no one types anything in by hand.</p>
          <Shot caption="A Discord message becomes structured data automatically.">
            <DemoDiscordFlow />
          </Shot>
          <p className="guide-note">Everything you see mirrors what an analyst posted — it is information, not financial advice. When in doubt, every row lets you read the original message.</p>
        </GSection>

        <GSection id="reading" title="Reading a signal" min="viewer" role={r}>
          <p>Every event carries an <strong>action</strong>, an <strong>instrument</strong> and a <strong>confidence</strong>:</p>
          <Shot caption="Actions — what the analyst did.">
            <DemoActions />
          </Shot>
          <Shot caption="Instruments — what they're trading.">
            <DemoInstruments />
          </Shot>
          <Shot caption="Confidence — how explicit the message was.">
            <DemoConfidence />
          </Shot>
          <p>Option contracts read like <span className="mono">6/20 210C</span>: expiry June 20, strike 210, Call. A price like <span className="mono">4.40 → 6.10</span> means the fill was 4.40 and the latest posted mark is 6.10.</p>
        </GSection>

        <GSection id="positions" title="Open Positions" min="viewer" role={r}>
          <p>The home tab. One row per live position, aggregated from every related event (open → adds → trims → …).</p>
          <Shot caption="A position: contract details on the left, entry → latest mark with P&L on the right.">
            <DemoPositionRow />
          </Shot>
          <ul className="guide-list">
            <li><strong>Analyst-first landing (optional, per server).</strong> A server can open Positions on a <strong>roster of analysts</strong> instead of the full list — a ranked league table where each row shows that analyst's open count, their book health (a up/down/unmarked meter), average stated P&amp;L and a peek at what they're holding, most-active first. Tap a row to see just their positions; <span className="mono">← Analysts</span> goes back. A gentler first screen when a book runs dozens of plays at once. Owners switch it on in Settings → Servers.</li>
            <li><strong>Entry → Last stated</strong> uses the analyst's stated average when given, else the first fill. The arrow value is the latest price evidence — a posted mark or the most recent fill (a trim @ 10 moves it to 10) — with the % move. Unless the server has <strong>Live market data</strong> switched on, every price here is a mark an analyst stated in Discord, so each one carries its <strong>age</strong> ("· 3h"); marks nobody has updated for 2+ days turn amber, and a position with no mark yet says so rather than showing a stale number.</li>
            <li><strong>Live prices (when the server has them on).</strong> A share position then updates from a live market feed — its price flashes green or red on each change and carries a <strong>mini chart</strong> of the day's path. Option plays keep the analyst's stated premium (live option marks need a data plan that carries them) and show the <strong>underlying stock's live price</strong> right on the row. A small <strong>LIVE</strong> chip marks a price that's coming from the live feed, so it's always clear which numbers are live and which are an analyst's stated mark. Turn it on per server in Settings → Servers.</li>
            <li><strong>Time left</strong> (options): a small chip counts down to expiry from the contract itself — amber at 5 days or less, red on expiry day (<span className="mono">0DTE</span>), struck through once past. The summary strip also counts what's <strong>expiring this week</strong>.</li>
            <li><strong>Stop</strong> shows the latest stop-loss the analyst stated (— when none).</li>
            <li><strong>Click a row</strong> to expand the full interaction timeline — every event on that trade, in order, with the entry anchored at the left and the follow-ups (adds / trims / holds / exits) stepped in beneath it.</li>
          <li><strong>Tap any analyst's name</strong> — here, in Recaps, on an event — to open their <strong>profile</strong>: trading style, risk-management approach, and their win rate / record / average from tracked trades. Knowing a style upfront (e.g. aggressive 0DTE vs. strategic swing) helps you size a trade sensibly. Analysts write their own profile right from that card; owners and server owners can also edit it in Settings → Analysts.</li>
            <li>The <strong>calculator</strong> button converts contracts ↔ cost or budget ↔ contracts{window.CHARTS_ENABLED && <>; the <strong>chart</strong> button jumps to the ticker's chart</>}.</li>
            <li>Options <strong>auto-expire</strong>: a 0DTE with no update by the end of its session — or any dated contract past its expiry — moves to Recaps on its own.</li>
            <li><strong>Two filter rows, different jobs.</strong> The <strong>top</strong> row sets <em>reach</em> — the <strong>timeframe</strong> (This week / 2W / 1M / <span className="mono">All</span>, by how recently a position was last updated; defaults to <strong>all open positions</strong>), plus the ticker search, the analyst dropdown (lists only analysts with open positions) and the instrument toggle (All / Options / Shares). The <strong>bottom</strong> row is a quick <strong>view</strong> of what's already showing — <span className="mono">All / Winners / Losers / Today</span>, where <strong>Today</strong> means this US market session. The two stack: pick <span className="mono">All</span> + <span className="mono">Today</span> on a quiet weekend and nothing traded today, so the list is empty <em>on purpose</em> — it now says exactly that (with a one-tap "show all") rather than looking broken.</li>
            <li><strong>Trading days, not your days.</strong> Every "day" on the platform — recaps, day separators, the Today filter, expiry countdowns — is the <strong>US market day</strong> (New York), so a trade made at the 4pm close belongs to that session for everyone, wherever you are. Clock times still show in your own timezone. Prices in another currency show their symbol (e.g. <span className="mono">A$7.62</span>); P&amp;L is a percentage, so it reads the same either way.</li>
            <li>On mobile the table becomes a tap-to-expand card list; the same details live inside each card.</li>
          </ul>
        </GSection>

        <GSection id="events" title="Signal Events" min="analyst" role={r}>
          <p>The raw feed — every parsed event, newest first, before any aggregation. Use it to see exactly what came in and when.</p>
          <ul className="guide-list">
            <li>Filter by ticker (search box), analyst (a dropdown of who's in the feed), category (Signals / Analysis / Chat), action, or instrument.</li>
            <li>The <strong>Price</strong> column shows the fill stated in that message, plus <span className="mono">→ mark</span> when the message also gave a current value.</li>
            <li><strong>Click a row</strong> to read the full raw Discord message.</li>
            <li>Parsed wrong? Hit <strong>Override</strong> to correct it (ticker, action, instrument, prices, stop — even <strong>when it happened</strong>, if it was booked into the wrong day). The owner can fix any event; an analyst can fix their own — and every view updates from the correction.</li>
          </ul>
        </GSection>

        <GSection id="watchlist" title="Watchlist" min="viewer" role={r}>
          <p>Ideas, not positions. When an analyst floats tickers — a daily watchlist, levels to watch, an analysis write-up — it lands here, grouped by day. Nothing in Watchlist affects Positions or Recaps until the analyst actually takes a trade.</p>
          <p>A <strong>timeframe</strong> control (top right, same as Positions) keeps the feed current — <span className="mono">This week / 2W / 1M / All</span>, defaulting to the <strong>last 2 weeks</strong> so stale ideas fall away on their own. There's also an <strong>analyst</strong> dropdown and a type toggle (All / Watchlists / Analysis); the search box narrows by ticker.</p>
        </GSection>

        {window.CHARTS_ENABLED && (
        <GSection id="charts" title="Charts" min="viewer" role={r}>
          <p>Candles for any ticker with the analysts' events marked in price context. Switch the timeframe with the buttons above the chart — <span className="mono">5m / 15m / 1H</span> intraday through <span className="mono">D / W / M</span> — markers and axis adapt automatically (intraday only shows events from its own window). The panels beside the chart list the ticker's open positions and recent events — same format as the main tabs.</p>
        </GSection>
        )}

        <GSection id="recaps" title="Recaps" min="viewer" role={r}>
          <p>Closed history. The top of the tab summarizes the selected analysts' track record — <strong>win rate</strong>, wins/losses, average P&amp;L (and average win vs average loss) — plus a <strong>performance chart</strong>: one bar per period showing what that period actually netted, with the <strong>exact % printed on each bar</strong>. Switch between <span className="mono">Daily / Weekly / Monthly</span> with the toggle above it. Bar heights use a <strong>signed-log scale</strong> so a single +12,900% period can't flatten everything else — the printed figure is exact either way. Hover a bar for its trades, W/L, trims and the running total; <strong>click a bar</strong> to open that period's full recap right under the chart.</p>
          <p>Below that, switch between two views. <strong>Periods</strong> rolls closed trades up into recaps — pick <span className="mono">Daily / Weekly / Monthly / 3M / 6M / 12M</span> and each bucket shows that period's win rate, total &amp; average gains, and the trades grouped by analyst (click a line to expand the full timeline). The Daily view also lists the day's <strong>trims</strong> — partial exits taken that day (amber square), with the gain vs the analyst's stated average when known. A trim scales out of a position rather than closing it, though a position trimmed in the morning may still be fully closed later the same day — in which case it appears in both lists. A day with only trims still gets its recap block. <strong>Trades</strong> lists every closed position as its own card with the full timeline, sortable by Recent / Top wins / Top losses. Both are derived from our own tracked data, so every server gets recaps with no extra setup. An <strong>analyst</strong> dropdown narrows to specific analysts and the search box filters by ticker.</p>
          <p><strong>How a trade is scored.</strong> Cost basis is the analyst's stated average, else their entry. The exit is the price they <em>got out at</em> when they state one — a stated fill always wins. When an exit states no price ("Closed here"), or the contract simply expired, we use the <strong>last price the analyst published on that position</strong>: a trim fill or a price update, whichever came last. A posted update counts because it's a price members could act on — so a call that ran from <span className="mono">.27</span> to a posted <span className="mono">2.00</span> is scored on the 2.00, not on an earlier trim. Buying more ("adding here") is an <em>entry</em> and never counts as an exit.</p>
          <p><strong>When the analyst states the result instead of a price.</strong> Exits like <em>"85% gains im taking profit now"</em>, <em>"Out of DELL -25%"</em> or <em>"Cut RIOT btw, 40% L"</em> tell us the outcome without a fill. That figure is used to score the trade whenever there's no price available — a price still wins when we have one. Only the trade's own gain or loss counts: a trim <em>size</em> ("trim 10%"), a percentage stop ("SL 10%") and the underlying stock's move ("AMZN swings up 10%") are all left out, and anything genuinely ambiguous is ignored rather than guessed.</p>
          <p><strong>"Awaiting price".</strong> Sometimes a contract lapses at expiry — or is closed with no price given and no stated result — and there's nothing to price it against. Rather than book it as a 0% scratch (which would quietly claim a break-even on what's usually a loss), it's marked <strong>awaiting price</strong>: still listed, but held out of win rate and averages until the real closing price is confirmed. The count shows next to the win rate and on each period. A position where an automated price card and a manual update disagree materially carries a <strong>CHECK</strong> marker so you can look before trusting it.</p>
          <p>If a server runs a <strong>Recap channel</strong> (a daily published summary of closed trades), the Daily view also overlays the analyst's <strong>published recap</strong> for that day — its headline numbers plus a badge showing whether it <em>reconciles</em> with our data or has flags. The owner can toggle this per server (Settings → Servers) and works any reconciliation discrepancies from a panel at the top of the tab.</p>
          <p><strong>Auto-publish to Discord (owner).</strong> The bot can post this day's recap back to a Discord channel for you — a branded embed with the 🏆 play of the day, positions closed by analyst, trims taken and the day's stats, computed from your tracked trades. Turn it on in <strong>Settings → Servers → Publish daily recap</strong>: pick a channel, an optional ping role, a label, a post time (ET, after the close), and an optional <strong>footer note</strong> (a call-to-action or disclaimer). A <strong>Preview message</strong> button on that screen renders the exact embed — same numbers, branding and note — so you can see precisely what will post before switching it on. It carries the server's name, accent colour and logo, and links back to the live dashboard. Empty days are skipped.</p>
        </GSection>

        <GSection id="sources" title="Sources" min="viewer" role={r}>
          <p>Choose whose signals you see. Toggle an analyst off and they disappear from <strong>every tab — including Open Positions</strong>; <strong>solo</strong> isolates one analyst. Your choice is <strong>remembered on this device</strong>, so it stays exactly as you left it when you come back. The server picker (top right) does the same per Discord server when more than one is connected.</p>
        </GSection>

        <GSection id="notifications" title="Notifications" min="viewer" role={r}>
          <p>The <strong>{I("bell", { size: 12 })} bell</strong> at the top right keeps you on top of new signals — a badge shows how many you haven't seen, and the dropdown lists them newest-first. Tap any one to jump to that ticker. The in-app bell always works on its own; <strong>push notifications</strong> are the optional extra that reach you <strong>even when the app is closed</strong> (delivered by a service worker, not just while the tab is open).</p>
          <p><strong>Turn on push notifications — step by step:</strong></p>
          <ol className="guide-list guide-steps">
            <li><strong>On iPhone / iPad, install the app first.</strong> Apple only delivers push to a web app that's been <strong>added to the Home Screen</strong> (see <em>Install the app</em> below), and you must open it <em>from that icon</em>. On Android and desktop you can skip straight to step 2.</li>
            <li>Tap the <strong>{I("bell", { size: 12 })} bell</strong> (top right), then the <strong>settings gear</strong> in the dropdown.</li>
            <li>Switch <strong>Push notifications on</strong>, and tap <strong>Allow</strong> when your browser or device asks for permission (it only asks once, per device).</li>
            <li><strong>Choose what you hear about</strong> — tick Entries, Adds, Trims, Cuts, Closes, Updates, plus <strong>Watchlist</strong> (new watchlist / analysis ideas) and <strong>Recaps</strong> (a new daily recap published). By default you'll get <strong>entries and exits</strong> (open / trim / cut / close); holds, watchlist and recaps are off unless you switch them on.</li>
          </ol>
          <ul className="guide-list">
            <li>Alerts follow the analysts you've <strong>enabled in Sources</strong> and the servers you're scoped to — turn an analyst off and they go quiet.</li>
            <li>Preferences are saved <strong>per device</strong>, so you can run loud alerts on your phone and stay silent on your laptop.</li>
            <li>Not getting push? Re-open the bell's settings to confirm it's on, check you didn't block notifications for the site in your browser/OS, and on iPhone make sure you opened the app from its <strong>home-screen icon</strong> (not a Safari tab).</li>
            <li><strong>Trims count toward the stats</strong> — a partial exit is a realized result, so priced trims feed the win rate, totals and the chart by default. The <em>Trims in stats</em> toggle in the controls turns this off for your device; the closed-trades lists are never mixed — trims stay in their own labelled section.</li>
          </ul>
        </GSection>

        <GSection id="install" title="Install the app (Add to Home Screen)" min="viewer" role={r}>
          <p>Sinux Signals is a web app you can <strong>install like a native one</strong> — its own icon, full-screen with no browser bars, and (on iPhone/iPad) the only way to receive push alerts. It takes a few seconds:</p>
          <ul className="guide-list">
            <li><strong>iPhone / iPad — Safari</strong>: tap the <strong>Share</strong> button (the square with an ↑ arrow) → scroll down to <strong>Add to Home Screen</strong> → <em>Add</em>. Then open Sinux Signals from the new icon. It <em>must</em> be Safari — Chrome and other iOS browsers can't add it.</li>
            <li><strong>Android — Chrome</strong>: tap the <strong>⋮</strong> menu (top right) → <strong>Add to Home screen</strong> (or <em>Install app</em>) → <em>Install</em>. Many phones also pop up a one-tap <em>Install</em> banner you can just accept.</li>
            <li><strong>Android — Samsung Internet / Firefox</strong>: tap the menu (<strong>⋮</strong> or ≡) → <strong>Add page to → Home screen</strong> / <strong>Install</strong>.</li>
            <li><strong>Desktop — Chrome / Edge</strong>: click the <strong>install icon</strong> in the address bar (a small monitor with a ↓, at the right end) — or the <strong>⋮</strong> menu → <em>Install Sinux Signals…</em>. It opens in its own window.</li>
            <li><strong>Desktop — Safari (macOS)</strong>: <strong>File → Add to Dock</strong>.</li>
          </ul>
          <p className="guide-note">If you only have access to a single <strong>branded</strong> community, the installed icon and name match that community (e.g. Charan Invests) rather than Sinux Signals — iOS captures whatever's on screen when you add it, so sign in first, and <em>re-add</em> the app later to refresh the icon.</p>
        </GSection>

        <GSection id="tips" title="Tips & shortcuts" min="viewer" role={r}>
          <ul className="guide-list">
            <li><strong>Theme</strong>: sun / moon / monitor switch at the bottom of the sidebar (system follows your OS).</li>
            <li><strong>Text size</strong>: next to the theme switch is an <strong>A A A</strong> control — three text sizes for the whole app. The middle one is the default; tap a smaller or larger A to suit you. Remembered on this device.</li>
            <li><strong>Density</strong>: the Tweaks panel offers Tight / Std / Roomy table density.</li>
            <li><strong>Phone</strong>: install it to your home screen for a full-screen app with its own icon — see <strong>Install the app</strong> above for the steps on every device.</li>
            <li>The thin green line at the very top on mobile is a reading-progress bar — it fills as you scroll.</li>
            <li>The search box clears when you switch tabs, so a filter never follows you to the wrong page.</li>
            <li><strong>Picks up where you left off</strong>: the app remembers your last tab and server selection on this device, so a refresh — or reopening the home-screen app — drops you back where you were (re-checked against your current access).</li>
          </ul>
        </GSection>

        <GSection id="report" title="Reporting a bug" min="viewer" role={r}>
          <p>Spotted something broken, odd, or missing? Use the <strong>{I("bug", { size: 12 })} Report a bug</strong> button at the bottom of the sidebar — it's there on every page, for every user.</p>
          <ul className="guide-list">
            <li>Pick <strong>Bug</strong> for something broken, <strong>Idea</strong> for a feature suggestion, or <strong>Other</strong> for anything else.</li>
            <li>Describe <strong>what happened and what you expected</strong> — the page you were on, the ticker or analyst involved, and what you tapped right before. The more specific, the faster the fix.</li>
            <li>Your role, app version and current page are attached automatically, so you don't need to include technical details.</li>
            <li>Reports go straight to the Sinux Signals team and are tracked until resolved. Thank you — every report makes the platform better for your whole community.</li>
          </ul>
        </GSection>

        <GSection id="rating" title="Rating the platform" min="viewer" role={r}>
          <p>Every so often a small card slides in from the corner asking you to <strong>rate your Sinux Signals experience</strong> — tap 1–5 stars, add a note if you like, and hit Submit. It's entirely optional and never blocks the app; choose <em>Maybe later</em> to dismiss it, and it won't return until the next cycle.</p>
          <ul className="guide-list">
            <li>How often you're asked — and whether you're asked at all — is set by your community's owner. By default it's at most <strong>once a day</strong>.</li>
            <li>Only your <strong>star score and optional note</strong> are sent (along with your name and current server, so the owner knows whose feedback it is). Nothing else is collected.</li>
          </ul>
        </GSection>

        <GSection id="an-tools" title="Your tools (Analyst)" min="analyst" role={r}>
          <p>As an analyst you have read-only access to every tab, plus a few actions scoped to <strong>your own</strong> signals:</p>
          <ul className="guide-list">
            <li><strong>Override your own events</strong> — hit <strong>Override</strong> on any of your events (an Events row, the event flyout, or the pencil on your position's timeline) to fix a mis-parse: ticker, action, instrument, strike/expiry, prices, stop or confidence. Positions, recaps and charts recompute from the correction and it's recorded in the audit log. You can only edit events attributed to you.</li>
            <li><strong>Delete your own events &amp; watchlists</strong> — the trash icon removes your own rows from the Events and Watchlist tabs (every view recomputes instantly). Other analysts' rows are off-limits. Deleted something by mistake? Flip the <strong>Live / Deleted</strong> toggle (top-right of the Events or Watchlist filter bar) to open the <strong>recycle bin</strong> and <strong>Restore</strong> it — your deletes are kept there for 30 days.</li>
            <li><strong>Manage your own positions</strong> — if the owner has toggled <em>"Can manage positions"</em> on for you (Settings → Access), the green pencil on your position rows lets you log an Add / Trim / Close / Cut or refresh the mark/stop, and <strong>Log position</strong> (top right) opens a new position by hand — all attributed to you and audit-logged. Without that toggle you stay read-only.</li>
          </ul>
          <p className="guide-note">Everything else — channels, other analysts, access and server settings — is admin-only. If you need a correction outside your own trades, ask an admin.</p>
        </GSection>

        <GSection id="ed-access" title="Managing users (Editor)" min="editor" role={r}>
          <p>Editors can grant and manage <strong>viewer</strong> access for their own servers under <em>Settings → Access</em>:</p>
          <ul className="guide-list">
            <li><strong>Add user</strong> with their Discord user ID (right-click a user in Discord → Copy User ID, with Developer Mode on). They then sign in with Discord.</li>
            <li>The <strong>toggle</strong> on each row suspends/reactivates access instantly. Suspension always wins — even if the person holds a granting Discord role.</li>
            <li>You can only manage users whose servers are within your own scope, and only at roles below yours.</li>
          </ul>
          <p>Editors also get <strong>read-only</strong> visibility of the Channels and Analysts screens — you can see how the bot is set up, but the setup how-to (registering channels, analysts) is admin-level and lives in the Admin sections below.</p>
        </GSection>

        <GSection id="ed-audit" title="Audit log (Editor)" min="editor" role={r}>
          <p><em>Settings → Audit log</em> records every configuration change — who did what, to which item, when. Filter by type, server or free text. If something looks different than you remember, check here first.</p>
        </GSection>

        <GSection id="ad-channels" title="Channels (Admin)" min="admin" role={r}>
          <p><em>Settings → Channels</em> controls what the bot reads. Register a channel with its Discord ID — the <strong>name fills in automatically</strong> from the ID (you can still edit it), and <strong>stays in sync</strong> if the channel is later renamed on Discord — and tag it with one or more categories — a channel can be both <strong>Signals</strong> and <strong>Analysis</strong> at once (toggle the pills). A newly registered channel <strong>defaults to Signals + Analysis</strong> (both alerts and write-ups) — narrow it to either if you like. <strong>Recap</strong> and <strong>Ignore</strong> own the whole channel, so they can't be combined with others:</p>
          <ul className="guide-list">
            <li><strong>Signals</strong> — highest priority; every message from a registered analyst is parsed.</li>
            <li><strong>Analysis</strong> — parsed with context; watchlists and write-ups usually live here.</li>
            <li><strong>Recap</strong> — a daily published summary of closed/TP'd trades. The bot reads each line, attributes it to its analyst, and <strong>reconciles it against tracked positions</strong>. It <strong>checks our own records first</strong>: a recap line reads "entry → exit", which doesn't mean the play was closed (analysts often list a play they only <em>trimmed</em>), so if we already recorded a matching trim or close at that price the recap adds nothing and <strong>raises no task</strong>. Only an exit we have no record of becomes a To-Do — along with a trade we never tracked, an unknown analyst, or an exit at a materially different price. It <strong>never closes a position from a recap</strong>: it can't tell "took some off" from "fully out", so you pick the action. Per server you can switch that item from <em>manual approve</em> to <strong>auto-fill</strong> (Settings → Servers), which records the exit as a <em>trim</em> — never a close. Fix a true mis-parse with <strong>Override</strong>, not the recap. Lock the channel to the recap sender via its <strong>Authors</strong>.</li>
            <li><strong>Chat</strong> — noisy; only registered analysts are parsed.</li>
            <li><strong>Ignore</strong> — skipped entirely (bots, screeners, off-topic).</li>
          </ul>
          <p>Per channel you can also force an <strong>instrument mode</strong> (options-only / shares-only) and bind one or more <strong>Authors</strong> — only those accounts are parsed there (leave it empty to allow any registered analyst). That's how you lock a recap channel to its sender, or let a trader who posts both himself and via his bot flow into one channel. With multiple servers connected, the list groups by server.</p>
          <p><strong>Separate account (challenge / PA books).</strong> Some traders run a distinct book in its own channel — a <em>20k challenge</em>, a paper account, a second strategy. Switch on <strong>Separate account</strong> for that channel (edit it → the toggle, with a label like <span className="mono">20k Challenge</span>) and its posts are tracked as their <strong>own entity</strong> — <span className="mono">Trader · 20k Challenge</span>, in the trader's colour — with its own positions, win rate and recaps, <strong>never mixed</strong> into the trader's normal channels. The trader does nothing different; it's decided purely by which channel they post in. Lock that channel's <strong>Authors</strong> to the trader so only their posts land in the account.</p>
        </GSection>

        <GSection id="ad-analysts" title="Analysts (Admin)" min="admin" role={r}>
          <p><em>Settings → Analysts</em> is the registry of who gets parsed. Only registered analysts produce events.</p>
          <ul className="guide-list">
            <li>Register by Discord user ID — the handle <strong>auto-fills</strong> from the user's Discord name (edit it for a custom label) — and each analyst gets a color used across the app.</li>
            <li><strong>They get platform access automatically.</strong> Adding a real (non-bot) analyst grants them the <strong>Analyst role for that server</strong>, so they can sign in and manage their own positions — no separate step under Access. If they already have a higher role it's left untouched.</li>
            <li><strong>Tier</strong> (core / secondary / watchlist) weights how aggressively their chat-channel posts are parsed.</li>
            <li><strong>Channel scope</strong> limits which categories they're parsed in (signals / analysis / chat).</li>
            <li><strong>Bot/app analyst</strong>: turn this on for app-posted alerts (embeds and post-then-edit messages are captured).</li>
            <li><strong>Breakeven on trim</strong>: for analysts whose convention is "trim = runners with stop at entry", a trim with no stated stop auto-sets the stop to the entry price.</li>
            <li><strong>States % as gain</strong>: for analysts who post the position's <em>gain</em> as a bare percentage — "MSFT at 20%", "25% trim on MSFT", "45% on it" — instead of a fill price or a trim size. With this on, the bot reads that percentage as the trade's <strong>result</strong>, so a trim banks its real % in Recaps instead of showing a blank. Leave it off for analysts who use "trim 25%" to mean they sold a quarter of the position.</li>
            <li><strong>Removing an analyst</strong> takes them off the parsing roster straight away — nothing of theirs is parsed again and their trades stop showing immediately. Their <strong>trade history and watchlist entries for that server are then deleted after 14 days</strong>, and <strong>re-adding them within those 14 days cancels it</strong>. Only that server's data goes: if the same person is an analyst in another server, their history there is untouched. Published daily recaps are never deleted, because one recap covers several analysts at once.</li>
            <li><strong>Also known as</strong>: the other names a <strong>published recap</strong> uses for this analyst. A recap might head a section <span className="mono">BISHOP CALLS:</span> while the roster says <span className="mono">THE PAWN (THE MARKET BISHOP)</span> — too far apart to match safely, and the system refuses to guess rather than risk crediting a trade to the wrong person. State the name here once and every future recap attributes correctly. Branding words are already handled for you ("DEMON" finds "DEMON ALERTS"), so only add a name that's genuinely different. A name that already identifies another analyst in the same server is rejected — a shared name would make recaps ambiguous, and neither analyst would match.</li>
            <li><strong>Linked accounts</strong>: connect a trader's <strong>bot or alt account</strong> to this analyst (edit the analyst → <em>Linked accounts</em> → paste the account's user ID). Both then post under <strong>one identity</strong> — the linked account's existing calls merge in and future ones attribute here, so positions and recaps combine (e.g. Dev's manual calls + his bot's alerts become one "Dev"). If a channel is <em>author-locked</em>, add both accounts to its Authors.</li>
            <li><strong>Read images</strong> (off by default): for analysts who post their calls as pictures, the bot vision-reads them. It's <em>conservative</em> — a posted <strong>portfolio / closed-trades table</strong> is reconciled read-only against what we track (gaps show as reconciliation flags, nothing is auto-closed), and a <strong>single-trade card</strong> updates the mark on a position we already track or is flagged for you to confirm. An image never opens or closes a position on its own.</li>
          </ul>
        </GSection>

        <GSection id="ad-manage" title="Managing positions from the portal (Admin)" min="admin" role={r}>
          {canManage ? (
            <p>You have position management enabled. Two tools, both on the Positions tab:</p>
          ) : (
            <p className="guide-note">This capability is granted per admin by the owner (Settings → Access → edit an admin → "Can manage positions"). You'll see the tools below once it's enabled for you.</p>
          )}
          <ul className="guide-list">
            <li><strong>Manage</strong> (the green pencil on a position row): log an Add / Trim / Close / Cut, or just refresh the mark/stop — for when an analyst's update didn't get posted or parsed. On a trim / cut / close you can enter the fill as a <strong>$ price or a % gain</strong> — flip the field between the two and a % auto-computes the dollar fill from the position's entry (entry 5.18 + 16% → $6.01), so P&amp;L reads correctly (it falls back to $ when there's no recorded entry). On an <strong>Add</strong>, flip the field between <strong>Fill</strong> and <strong>New avg</strong>: enter the analyst's new blended average directly when that's what you know (not the exact add price), and it updates the cost basis — an add can't re-average on its own since we don't track share counts.</li>
            <li><strong>Log position</strong> (top right): open a brand-new position by hand — pick the analyst, ticker, option/shares, strike &amp; expiry, entry price. Use <strong>Posted at</strong> (in <span className="mono">ET</span>) when you're recording a call the analyst made earlier, so it lands in the right trading session instead of the moment you typed it; leave it blank for "now".</li>
            <li>Both write a real event attributed to the analyst, tagged with a <span className="manual-tag">portal</span> chip in the timeline, and recorded in the audit log.</li>
          </ul>
        </GSection>

        <GSection id="ad-cleanup" title="Correcting data (Admin)" min="admin" role={r}>
          <ul className="guide-list">
            <li><strong>Override a mis-parsed event</strong> — the <strong>Override</strong> action (on an Events row, the event flyout, or the pencil on a position's timeline) opens an editor pre-filled with the parse. Fix the ticker, action, instrument, strike/expiry, prices, stop or confidence — or <strong>when it happened</strong> (the trading session, entered in <span className="mono">ET</span>), to move a trade booked into the wrong day into the right recap — and save; positions, recaps and charts recompute, and the change is in the audit log. The <strong>owner</strong> can override any event; an <strong>analyst</strong> can override their own.</li>
            <li><strong>Edit a whole position at once</strong> — when the parser read the <em>contract</em> differently across messages (a strike typed as 205 in one alert and 210 in the next), those messages become <strong>two positions</strong>, and one of them loses its entry price. Overriding them one at a time can't fix that — each save just re-splits it. Use <strong>Edit position</strong> above a position's timeline: it rewrites the ticker, strike, expiry and type across <em>every</em> event in the position in one go. A red <em>contract mismatch</em> badge appears when the events genuinely disagree. Prices and actions are deliberately left alone — those are per-event, so correct them with Override.</li>
            <li><strong>Delete an event</strong> from the Events tab (trash icon, or multi-select for bulk). Positions, recaps and charts recompute instantly — deleting a bad parse fixes every view at once.</li>
            <li><strong>Watchlist entries</strong> delete the same way from the Watchlist tab.</li>
            <li><strong>Recycle bin (30-day undo)</strong> — every delete is reversible. The <strong>Live / Deleted</strong> toggle at the top-right of the Events and Watchlist filter bars opens the bin: <strong>Restore</strong> puts a row back (and every derived view with it), <strong>Delete forever</strong> removes one permanently, and <strong>Empty bin</strong> clears it. Anything not restored is purged automatically after 30 days. You see the bin for the servers you moderate; an analyst sees only their own deleted rows.</li>
            <li>A wrong price on a live position? Use <strong>Manage → Update</strong> to post the correct mark rather than deleting history.</li>
          </ul>
        </GSection>

        <GSection id="ad-access" title="Access & roles (Admin)" min="admin" role={r}>
          <p>Two ways into the portal, both under <em>Settings → Access</em>:</p>
          <ul className="guide-list">
            <li><strong>Individual users</strong> — added by Discord ID, with a <strong>role per server</strong> (toggle a server on and pick its role — someone can be an Analyst in one community and a Viewer, or even Server owner, in another):
              <ul className="guide-list" style={{ marginTop: 6 }}>
                <li><strong>Viewer</strong> — read-only.</li>
                <li><strong>Analyst</strong> — read-only, plus they can delete <em>their own</em> events & watchlists and (if you toggle it) manage <em>their own</em> positions.</li>
                <li><strong>Editor</strong> — manages individual users (viewers), sees the audit log and read-only config.</li>
                <li><strong>Admin</strong> — pick exactly what they can do with per-user toggles: manage channels, manage analysts, manage users & access, manage & moderate positions, manage server settings, and (owner-granted only) view bug reports.</li>
                <li><strong>Server owner</strong> — a full clone of admin for their servers: channels, analysts, users, positions and server settings, all on.</li>
              </ul>
            </li>
            <li><strong>Role access</strong> — map a Discord role to an access level (any of the roles above) and everyone holding it can sign in. Checks are live: granting or removing the Discord role takes effect within about a minute, no re-login needed.</li>
            <li>Per server, the <strong>highest tier wins</strong> — an individual entry and a granting Discord role combine, so an analyst who also holds a Mod/VIP role gets the higher access level in that server. <strong>Suspending</strong> an individual entry beats everything — it keeps them out even if they hold a granting role.</li>
            <li>Registering someone as a (non-bot) <strong>analyst</strong> automatically grants them Analyst access for that server, without touching their standing anywhere else.</li>
            <li>Everyone except the owner is <strong>scoped to their own servers</strong>; only the owner sees everything and can add/remove servers and view all bug reports.</li>
          </ul>
          <p style={{ marginTop: 14 }}><strong>What each role can do</strong> — ✓ yes · <span className="mx-own">own</span> only their own content · <span className="mx-tog">⚙</span> per-user toggle (set when you add/edit them) · <span className="mx-tog">★</span> owner-granted toggle · — no. Every mark applies <em>in the server where the person holds that role</em>.</p>
          <div className="guide-matrix">
            <table>
              <thead>
                <tr><th>Capability</th><th>Viewer</th><th>Analyst</th><th>Editor</th><th>Admin</th><th>Server owner</th><th>Owner</th></tr>
              </thead>
              <tbody>
                {[
                  ["View all tabs (Positions, Events, Watchlist, Charts, Recaps)", "✓", "✓", "✓", "✓", "✓", "✓"],
                  ["Report a bug / submit a rating", "✓", "✓", "✓", "✓", "✓", "✓"],
                  ["Delete events & watchlists (+ recycle bin)", "—", "own", "—", "⚙¹", "✓", "✓"],
                  ["Override (correct) a mis-parsed event", "—", "own", "—", "⚙¹", "✓", "✓"],
                  ["Log & manage positions from the portal", "—", "own ⚙", "—", "⚙¹", "✓", "✓"],
                  ["Latest-events rail beside Open Positions", "⚙", "⚙", "⚙", "⚙", "⚙", "✓"],
                  ["View channels & analysts config (read-only)", "—", "—", "✓", "✓", "✓", "✓"],
                  ["Audit log", "—", "—", "✓", "✓", "✓", "✓"],
                  ["Manage channels", "—", "—", "—", "⚙", "✓", "✓"],
                  ["Manage analysts", "—", "—", "—", "⚙", "✓", "✓"],
                  ["Manage users & access", "—", "—", "viewers", "⚙²", "✓³", "✓"],
                  ["Discord role grants (Role access)", "—", "—", "—", "⚙²", "✓", "✓"],
                  ["Server settings (branding, recap overlay, name)", "—", "—", "—", "⚙", "✓", "✓"],
                  ["Bugs tab (triage reports)", "—", "—", "—", "★", "—", "✓"],
                  ["Ratings tab (view & delete ratings)", "—", "—", "—", "★", "★", "✓"],
                  ["Add / remove servers · Reset data · Integrations · Usage", "—", "—", "—", "—", "—", "✓"],
                ].map((row, i) => (
                  <tr key={i}>{row.map((c, j) => j === 0 ? <td key={j}>{c}</td> : <td key={j} className={cx("mx", c === "—" && "mx-no", c === "✓" && "mx-yes")}>{c}</td>)}</tr>
                ))}
              </tbody>
            </table>
          </div>
          <p className="guide-note" style={{ marginTop: 8 }}>¹ the admin "positions" toggle — carries moderation (delete/override/manage any). ² the admin "users" toggle — can manage editors, analysts and viewers. ³ server owners manage up to admins. Analysts' "own ⚙" = the <em>Can manage positions</em> toggle, always limited to their own. Suspending a user blocks them everywhere, whatever roles they hold.</p>
        </GSection>

        {canViewBugs && (
        <GSection id="ad-bugs" title="Bug triage (Admin)" min="admin" role={r}>
          <p>The owner has granted you the <strong>Bugs</strong> tab — every report filed through "Report a bug" across your scope, so you can triage them.</p>
          <ul className="guide-list">
            <li>Each report shows the <strong>reporter</strong>, their <strong>type</strong> (Bug / Idea / Other), the message, and the <strong>context</strong> captured automatically — app version, the page they were on, and viewport.</li>
            <li>Move a report through <strong>open → resolved / dismissed</strong> as you work it. The sidebar <strong>Bugs</strong> badge counts what's still open.</li>
            <li>Filter by status or type to focus a triage session; resolved and dismissed reports stay on record.</li>
          </ul>
          <p className="guide-note">This tab is visible only to users the owner has granted the bug-reports capability (Settings → Access → edit an admin → "View bug reports"). Reporting a bug, by contrast, is open to everyone.</p>
        </GSection>
        )}

        {canViewRatings && (
        <GSection id="ad-ratings" title="Ratings (Admin)" min="admin" role={r}>
          <p>The <strong>Ratings</strong> tab aggregates the platform-satisfaction feedback users submit through the rating prompt{r === "owner" ? " across every server" : ", scoped to your own servers"}.</p>
          <ul className="guide-list">
            <li><strong>At a glance</strong>: the average score with a star meter, the full 1–5 <strong>distribution</strong>, and a <strong>daily-average</strong> trend strip. Switch the timeframe (30d / 90d / 180d / 1Y){r === "owner" && <>; with more than one server you can also filter by server</>}.</li>
            <li><strong>Individual ratings</strong> lists every submission with its stars, the user, the server and any note — filter to 4–5★ / 3★ / 1–2★ to focus. Remove any rating with the <strong>trash</strong> icon (it's gone for good).</li>
            {r === "owner"
              ? <li><strong>Prompt settings</strong> (top of the tab, owner only): turn the prompt on or off, set the cadence in hours (default 24 = daily), reword the question, and choose <strong>who's asked</strong> — by <strong>Discord role</strong> (any role you've granted access to under Access → Role access), by <strong>platform access level</strong> (viewers, analysts…), and/or by <strong>specific user</strong>. They combine (a member is asked if they match any), and leaving everything unchecked asks everyone with access. To let a server owner see their servers' ratings, grant it under Settings → Access → their user → <em>"View ratings"</em>.</li>
              : <li>You're seeing this because the owner granted you the <em>"View ratings"</em> capability. Cadence and targeting are configured by the platform owner.</li>}
          </ul>
        </GSection>
        )}

        {canViewTodos && (
        <GSection id="ad-todos" title="To-Do (Admin)" min="admin" role={r}>
          <p>The <strong>To-Do</strong> tab is one queue for everything waiting on you{r === "owner" ? " across every server" : ", scoped to your own servers"} — you don't have to go hunting through tabs to find what needs attention.</p>
          <ul className="guide-list">
            <li><strong>What lands here</strong>: an <strong>unrecognised ticker</strong> (the symbol isn't in our master list — it may be a brand-new listing, or a misparse); an option <strong>opened with no resolvable expiry</strong>; and <strong>recap reconciliation</strong> mismatches. The trade itself is still recorded either way — a To-Do never means data was thrown away.</li>
            <li><strong>Confirm closing price</strong> appears when an option was <strong>trimmed with no price posted and then expired</strong> — there's no number to score it from, so it sits outside the stats until you set one. When a trim <em>did</em> state a price, that price is simply taken as the close and nothing is raised; same when the analyst posted a price after the trim.</li>
            <li><strong>No cost basis</strong> means no entry price was ever posted, so the trade can't be scored at all. Those are <strong>kept out of the Positions tab</strong> — a row that can only ever read "—" is just noise — and raised here instead. They still appear in Events and Recaps: nothing is hidden from the record. Add the entry with <strong>Override</strong>, or dismiss it if it was never a real position.</li>
            <li>These last two aren't posted by anyone — they're worked out from the positions themselves, and refresh in the background. If you fix the underlying trade, the item <strong>closes itself</strong>. Once you've settled one it never reappears.</li>
            <li><strong>Grouped by server</strong> when you're viewing more than one, so it's obvious what belongs where. Each row shows the type, the ticker/contract, the analyst and when it appeared; a parse-related item also shows the <strong>original Discord message</strong> that triggered it, with its channel, so you can judge it without going hunting.</li>
            <li>Clear an item with <strong>Done</strong> (you fixed it) or <strong>Dismiss</strong> (nothing to act on). <strong>Mark all done</strong> clears everything currently visible — it respects the filters, so it never touches items you can't see. Changed your mind? <strong>Reopen</strong> puts a settled item back on the open list.</li>
            <li><strong>The card's colour tells you its state</strong>: a green stripe and tint means <em>done</em>, a dimmed grey card means <em>dismissed</em>, and an open item keeps full contrast with a stripe in its type's colour. The state is also written out beside the type pill.</li>
            <li>Filter by <strong>Open / Done / All</strong>, by <strong>type</strong>, and by server. The sidebar <strong>To-Do</strong> badge counts what's still open, the same as Bugs.</li>
            <li><strong>Recap update to approve</strong> means the daily recap named an exit we have no record of — the analyst never posted it in their channel. The bot deliberately will not act on that: a recap can't tell a trim from a close, and closing a still-running play is the one mistake you can't undo. Hit <strong>Approve</strong>, check the recap's entry/exit/% shown alongside, adjust the price if needed, then pick <strong>Trim</strong> (part sold, rest still open) or <strong>Close</strong> (fully out). The event is logged against that analyst's position and marked as coming from the recap, so the timeline shows it was approved rather than spoken.</li>
            <li><strong>Get notified.</strong> Open the bell → <em>To-Do items</em> to have new tasks arrive as notifications, including on your phone's home-screen app. It's off until you switch it on, it's per device, and it ignores the Sources analyst filter — a parse problem matters whoever posted it. New tasks are swept hourly.</li>
            <li>Fixing most items means correcting the underlying event — use <strong>Override</strong> on the event (Events tab, or the pencil in a position's timeline) to set the right ticker or expiry, then mark the To-Do done. The <strong>Find</strong> button on a row opens the Events feed pre-filtered to that ticker, so you land on the offending event in one tap.</li>
          </ul>
          {r === "owner"
            ? <p className="guide-note">The bot also <strong>DMs you</strong> new to-dos as they appear, so nothing needs the dashboard open to be noticed. To let an admin or server owner work this queue, grant it under Settings → Access → their user → <em>"View the To-Do queue"</em>.</p>
            : <p className="guide-note">You're seeing this because the owner granted you the <em>"View the To-Do queue"</em> capability.</p>}
        </GSection>
        )}

        <GSection id="ow-servers" title="Servers & integrations (Owner)" min="owner" role={r}>
          <ul className="guide-list">
            <li><strong>Servers</strong>: connect a Discord server by guild ID (the bot must already be a member) — the display name <strong>auto-fills</strong> from the server. Deleting a server removes all of its channels, analysts, events, watchlist entries and role grants.</li>
            <li><strong>Reset a server's data</strong> (owner-only): edit a server → <strong>Reset data</strong> wipes all of that server's recorded trade data — open positions, signal events, watchlists and recaps — while <strong>keeping</strong> its channels, analysts, users and access. It confirms first and can't be undone; use it for a clean re-start (e.g. before a fresh backfill).</li>
            <li><strong>Live market data</strong> (per server): edit a server → toggle <strong>Live market data</strong> on. That server's members then see share positions tick with live prices and a mini chart, and option plays show the underlying's live price. It's off by default and independent per server — turn it on only for servers whose live-data usage you intend to pay for, since it's the switch that spends the market-data quota. A one-time "what's new" banner tells those members about it.</li>
            <li><strong>White-label branding</strong>: a server can carry its own portal identity (name, mark, colors). Members who only see that server get its branded portal — with a "Powered by Sinux Signals" sub-line — and you can preview it by selecting just that server in the server picker. Configured per server (branding specs are applied by the developer).</li>
            <li><strong>Integrations</strong>: the live endpoint configuration the dashboard polls. If the feed shows "paused" or "endpoint error", start here.</li>
            <li><strong>Per-admin position management</strong>: grant or revoke the "Can manage positions" switch on each admin in Access.</li>
            <li><strong>Usage</strong> (owner-only tab): how the platform is consumed — daily active users, activity volume (~5-minute blocks), signals ingested per day / analyst / server, config-change volume, and a per-user activity table. A <strong>server filter</strong> scopes the signal &amp; configuration metrics to one server (the "by server" panel becomes a per-channel breakdown); user activity is tracked platform-wide, so those panels stay platform-wide and are labelled as such.</li>
            <li><strong>Bugs</strong>: you can grant the Bugs tab to any admin (Access → "View bug reports"); triaging it is covered in <em>Bug triage</em> above. As owner you always see every report across all servers.</li>
            <li>Bot-side changes (parser behavior) live in the bot project and need a bot restart to apply.</li>
          </ul>
        </GSection>

        <GSection id="ow-assistant" title="Assistant (Owner)" min="owner" role={r}>
          <p>A private chat, visible only to you, in the bottom-right corner (the spark button). Ask it about the book in plain language, or tell it what to fix — it reads your live data to answer and never changes anything on its own.</p>
          <ul className="guide-list">
            <li><strong>Ask anything about your data</strong>: "why is INSTINCT LRCX showing that P&amp;L?", "find positions with no cost basis", "which of DEV's trades closed red this week?". It reads the whole book — every server — to answer.</li>
            <li><strong>Corrections are confirm-first</strong>: when you ask it to fix something, it shows the exact change as a <strong>before → after</strong> card with a <strong>Confirm</strong> / <strong>Cancel</strong>. Nothing is written until you press Confirm — the same as making the edit yourself with Override.</li>
            <li><strong>It knows the gotchas</strong>: cross-posting analysts (INSTINCT, DEV, DEMON) create two copies of each message, and editing only one desyncs the position — the assistant proposes the fix to <strong>both</strong> copies. It also understands how a recap's "@ entry → exit" prices book.</li>
            <li><strong>Reversible</strong>: any events it deletes go to the <strong>recycle bin</strong>, and every correction is tagged as an override in the audit trail, just like a manual edit.</li>
            <li>It's owner-only end to end — the endpoint refuses anyone who isn't you.</li>
          </ul>
        </GSection>

        <div className="guide-foot mono">This guide is updated with every release · Sinux Signals v{APP_VERSION}</div>
      </div>
    </div>
  );
}

Object.assign(window, { GuideTab });
