// Apps — connect business tools and choose their access.
(function () {
  const { useState, useEffect } = React;
  const h = React.createElement;
  const { Card, CardHead, Badge, RiskBadge, Button, KPI, Avatar, PageHeader, Segmented, Toggle, ProgressBar, EmptyHint, FutureStep, Modal, Section, Icon } = window;
  const { RoleIcon, RoleDot, InlineIcon, StatusIcon, Chip, levelChip, caveatLine, Seg3, AUDIT_TONE } = window;
  window.Screens = window.Screens || {};

  // C3: status is 繁中 in DATA; logic compares 繁中, display maps to English so i18n localizes it.
  // 已連接 -> "Connected" (localizes via existing dict "Connected":"已連線"); 未連接 -> "Not connected" (new key).
  const STATUS_EN = { "已連接": "Connected", "未連接": "Not connected" };
  const isOn = (c) => c.status === "已連接";
  const healthMessage = () => "Reconnect to resume updates.";

  // Connector seed records intentionally stay in Traditional Chinese because
  // they are also persisted and exercised as runtime contracts. Adapt only at
  // the render boundary so English never exposes the raw sample copy.
  const CONNECTOR_TEXT_EN = {
    "電子郵件": "Email",
    "東主（陳大文）": "Business Owner",
    "草擬＋審批後發送": "Draft, then send after approval",
    "審批後發送": "Send after approval",
    "唯讀": "View only",
    "新郵件即時進入收發室": "New emails arrive instantly",
    "今天 14 封來信 · 8 封已起草 · 3 封待分類": "14 emails today · 8 drafted · 3 to sort",
    "上次事件 今早 09:41": "Last update today 09:41",
	    "來信交給合適員工起草。任何回覆發出前，必先經你的桌。": "Incoming email is drafted. Nothing sends before approval.",
	    "郵件可一鍵改派，員工會記住更正。更改密碼後需重新授權。": "Reassign email anytime. Reconnect after changing the password.",
    "讀取你的來信": "Read incoming email",
    "建立草稿": "Create drafts",
    "核准後代你寄出": "Send after approval",
    "可隨時關閉——關閉後一切只到草稿為止": "Turn this off anytime. Everything will remain a draft.",
    "09:41 Gmail 推送 · 2 封新來信 · 1 封已起草": "09:41 Gmail update · 2 new emails · 1 drafted",
    "09:12 已派發 3 封至客服助理": "09:12 Sent 3 emails to the Customer Support Assistant",
    "08:55 Gmail 推送 · 5 封新來信": "08:55 Gmail update · 5 new emails",
    "08:00 排程：整理昨天重複查詢（客服助理）": "08:00 Schedule · grouped yesterday's repeated questions",
    "Google 帳戶": "Google account",
	    "你會被帶到 Google 官方授權頁完成確認；我們不會看到你的密碼，可隨時在 Google 帳戶移除存取權。": "Google handles sign-in; we never see your password. Remove access in Google.",
    "找到 1,240 封過去 90 天郵件…": "Found 1,240 emails from the past 90 days…",
    "建立分類索引…": "Building the sorting index…",
    "已派發 3 封待回覆郵件給對應員工": "Sent 3 emails to the right employees",
    "完成": "Complete",

    "@morninglight.home · 專業帳號（商業）": "@morninglight.home · Business account",
    "排程發佈 · 每週一整理成效": "Scheduled publishing · weekly results every Monday",
    "已排程 5 則貼文 · 今日發佈額度 8/100": "5 posts scheduled · 8/100 published today",
    "上次發佈 昨天 19:04": "Last published yesterday 19:04",
	    "你核准後才按排程發佈。需要手動貼紙時，系統會提醒你。": "Approved posts publish on schedule. Manual steps get a reminder.",
	    "部分 Instagram 功能需在 IG App 內完成；更改密碼後需重新連接。": "Some features need Instagram. Reconnect after changing the password.",
    "今日發佈額度": "Today's publishing limit",
    "發佈貼文與限時動態": "Publish posts and stories",
    "讀取留言與私訊並草擬回覆": "Read comments and messages and draft replies",
    "核准後代你發送回覆": "Send replies after approval",
	    "私訊僅限 Meta 24 小時窗口內；窗口外轉為 IG App 手動回覆": "Reply within Meta's 24-hour window. Use Instagram after that.",
    "讀取成效數據（週報用）": "Read results for weekly reports",
    "今天 08:20 動態已自動發佈（無貼紙）": "Today 08:20 Story published (no sticker)",
    "昨天 19:00 開箱影片發佈失敗（錯誤 2207026：轉碼超時）→ 19:04 自動重試成功": "Yesterday 19:00 Video failed to publish (2207026: processing timeout) · retry succeeded at 19:04",
    "昨天 23:40 動態成效已於到期前擷取存檔": "Yesterday 23:40 Story results saved before expiry",
    "週一 09:30 成效週報已生成": "Monday 09:30 Weekly results report created",
    "Meta 商業帳戶 · 經官方授權頁確認": "Meta business account · confirmed by Meta",
	    "你會被帶到 Meta 官方授權頁完成確認；可隨時在 Meta 商業設定移除存取權。": "Meta handles sign-in. Remove access anytime in Meta settings.",
    "驗證專業帳號（商業）": "Confirming the business account…",
	    "請在 IG App「設定 → 訊息」開啟『允許連線工具存取訊息』": "Instagram: Settings → Messages → Allow connected tools.",
    "讀取過去 50 篇貼文成效…": "Reading results for the last 50 posts…",
    "建立最佳發佈時段基準…": "Finding the best publishing times…",

    "晨光貿易共用資料夾（唯讀）": "Morning Light shared folder (view only)",
    "已同步 132 個檔案 · 同步正常": "132 files added · updates working",
	    "你昨日在 Google 帳戶『安全性 → 第三方應用程式』移除了本應用的存取權——同步已暫停（最後成功：昨天 21:14）。重新授權即可恢復，期間新檔案會在恢復後補同步。": "Access was removed yesterday. Reconnect to add missed files.",
	    "變更即時推送 · 每 15 分鐘增量核對（推送頻道每 7 天自動續期）": "Updates arrive at once · checked every 15 minutes",
    "已同步 132 個檔案 · 同步暫停中": "132 files added · updates paused",
    "最後成功同步 昨天 21:14": "Last successful update yesterday 21:14",
	    "新檔案會出現在「待歸檔」，由行政助理建議名稱和資料夾。": "New files wait in Files for a name and folder.",
	    "同步是單向唯讀：員工不會改動你 Drive 裡的原檔，也不會讀取所選資料夾以外的檔案。Google 文件和試算表會以 Office 格式（docx／xlsx）加入檔案。": "Only the chosen folder is read. Drive files stay unchanged. Docs and Sheets become DOCX or XLSX.",
	    "唯讀存取（Google 受限權限）": "View only",
	    "技術授權涵蓋雲端硬碟唯讀；系統只讀取你選定的「晨光貿易共用資料夾」——資料夾外不索引、不儲存，可在同步紀錄核查。不會修改或刪除任何檔案。": "Only the chosen folder is read. Nothing is changed or deleted.",
    "drive.readonly（Google 受限範圍；正式產品需完成相應審核，本示範不建立真實授權）": "drive.readonly (Google restricted scope; this demo creates no real authorization)",
    "昨天 21:14 同步暫停：存取權已被移除（Google 帳戶安全設定）": "Yesterday 21:14 Updates paused · access removed in Google security settings",
    "昨天 21:10 變更通知 · 1 個新檔案 → 已入待歸檔": "Yesterday 21:10 Change found · 1 new file added to Files",
    "昨天 18:02 增量核對 · 無變更": "Yesterday 18:02 Check complete · no changes",
    "Google 帳戶 · 將選定一個資料夾": "Google account · choose one folder",
    "你會被帶到 Google 官方授權頁；系統只同步你選定的資料夾。": "Google handles sign-in. Only the folder you choose is added.",
    "已選擇「晨光貿易共用資料夾」": "Selected Morning Light shared folder",
    "找到 132 個檔案": "Found 132 files",
    "1 個檔案等待行政助理建議名稱": "1 file is waiting for an Admin Assistant suggestion",

    "WhatsApp Business": "WhatsApp",
    "同一號碼可同時用手機 App 與系統": "Use the same number in the mobile app and here",
	    "即時（Webhook）": "Instant",
    "連接後：客服助理起草對話回覆": "After connecting: the Customer Support Assistant drafts replies",
    "今日 9 個對話 · 6 份草稿待批": "9 conversations today · 6 drafts to approve",
	    "客戶來訊後可起草回覆；所有草稿先經你的桌。": "Replies wait for approval before sending.",
	    "連接前需完成 WhatsApp 商業設定。部分手機 App 功能仍需手動處理。": "Finish WhatsApp Business setup first. Some mobile features stay manual.",
	    "接收客戶訊息並草擬回覆": "Receive messages and draft replies",
	    "僅限 24 小時客服窗口內；窗口外須用預審範本": "Reply within 24 hours. After that, use an approved template.",
    "whatsapp_business_messaging（Cloud API）": "whatsapp_business_messaging (Cloud API)",
    "晨光貿易有限公司": "Morning Light Trading Limited",
	    "Meta 商業帳戶 · 經官方 Embedded Signup 內嵌註冊（彈出視窗）": "Meta business account · verified",
    "整個流程在 Meta 官方彈出視窗完成；我們不會看到你的 Meta 密碼。": "Meta handles the entire sign-in. We never see your password.",
    "登入 Meta 商業帳戶": "Sign in to Meta Business",
    "以公司的 Meta 商業帳戶登入並選擇 WhatsApp Business 帳號。": "Sign in with the company Meta account and choose a WhatsApp Business account.",
    "號碼使用方式": "Choose how to use the number",
	    "共存模式（建議）：掃 QR 後，手機 WhatsApp Business App 與系統同時運作，最近 6 個月對話會同步入系統。": "Recommended: scan the QR code to keep the app and add six months of chats.",
    "提示：共存模式下廣播名單、限時訊息等 App 功能會受限。": "Some mobile features, including broadcast lists and disappearing messages, are limited in coexistence mode.",
	    "亦可選『專用模式』：號碼完全遷移至 API，手機 App 停用。": "Dedicated mode turns off the mobile app and moves the number here.",
    "商業驗證狀態": "Business verification",
    "示範帳號：已驗證 · 每日對話上限 1,000": "Demo account: verified · 1,000 conversations per day",
    "未驗證帳戶每 24 小時最多向 250 個新客戶發起對話。": "Unverified accounts can start conversations with up to 250 new customers every 24 hours.",
	    "註冊 Webhook…": "Setting up updates…",
    "讀取 3 個進行中的對話…": "Reading 3 active conversations…",

	    "支援個人與團隊資料夾（團隊帳戶需管理員批准）": "Personal and team folders (team folders need admin approval)",
    "檔案有變更時同步": "Update when files change",
    "連接後自動同步到檔案": "After connecting: add new files to Files",
    "已同步 64 個檔案 · 與 Google Drive 重複 11 個已自動去重": "64 files added · 11 Google Drive duplicates skipped",
	    "可與 Google Drive 同時連接；重複檔案會自動略過。": "Use with Google Drive. Duplicate files are skipped.",
	    "只讀取你選定的資料夾，不會改動 Dropbox 原檔。": "Only the chosen folder is read. Original Dropbox files never change.",
    "讀取你選定資料夾的檔案與清單": "Read files and lists in the selected folder",
    "Dropbox 帳戶": "Dropbox account",
	    "你會被帶到 Dropbox 官方授權頁；可隨時在 Dropbox 設定移除存取權。": "Dropbox handles sign-in. Remove access anytime in Dropbox.",
    "找到 64 個檔案…": "Found 64 files…",
    "與 Google Drive 比對：11 個重複已略過": "Compared with Google Drive · 11 duplicates skipped",

	    "OneDrive 商業版／SharePoint 文件庫（唯讀）": "OneDrive and SharePoint folders (view only)",
    "已同步 87 個檔案 · 與 Google Drive／Dropbox 內容雜湊去重 6 個": "87 files added · 6 Google Drive or Dropbox duplicates skipped",
	    "新檔案會進入「待歸檔」，由行政助理建議名稱和資料夾。重複檔案會略過。": "New files wait in Files. Duplicates are skipped.",
	    "同步為唯讀，不會改動 OneDrive 原檔。系統只索引你選定的資料夾。": "Only the chosen folder is read. Original OneDrive files never change.",
    "唯讀存取你選定的資料夾": "View-only access to the selected folder",
	    "技術授權為帳戶層級唯讀（Files.Read.All）；系統只讀取選定資料夾，不修改或刪除任何檔案。": "Account access is view only. No files are changed or deleted.",
    "Files.Read.All · offline_access（雲端檔案 API 委派權限；正式產品需通過供應商應用驗證，本示範不建立真實授權）": "Files.Read.All · offline_access (delegated cloud-file access; this demo creates no real authorization)",
    "雲端檔案帳戶 · 將選定一個資料夾": "Cloud file account · choose one folder",
	    "你會被帶到供應商官方授權頁；系統只同步你選定的資料夾。": "Your provider handles sign-in. Only your chosen folder is added.",
    "已選擇 OneDrive 資料夾": "Selected a OneDrive folder",
    "找到 87 個檔案": "Found 87 files",
    "已略過 6 個重複檔案": "Skipped 6 duplicate files",

	    "Box 企業內容雲（支援單一資料夾協作邀請）": "Box folders (view only)",
	    "已同步 53 個檔案 · 內容雜湊（SHA-1）去重 4 個": "53 files added · 4 duplicates skipped",
	    "新檔案會以唯讀方式進入檔案的「待整理」區。你也可只讓應用程式存取單一資料夾。": "New files wait in Files. You can limit access to one folder.",
	    "同步為唯讀，不會改動 Box 原檔。可只連接指定資料夾。": "View-only access never changes Box files.",
	    "預設為帳戶層級唯讀；可改用單一資料夾協作邀請做到資料夾隔離。不會修改或刪除任何檔案。": "Account access is view only. No files are changed or deleted.",
    "root_readonly（Box OAuth 2.0；正式產品需 Box 應用審核，本示範不建立真實授權）": "root_readonly (Box OAuth 2.0; this demo creates no real authorization)",
	    "Box 帳戶 · 將選定一個資料夾或協作邀請": "Box account · choose one folder",
    "你會被帶到 Box 官方授權頁；可隨時在 Box 設定移除存取權。": "Box handles sign-in. Remove access anytime in Box settings.",
    "找到 53 個檔案…": "Found 53 files…",
	    "跨來源 SHA-1 比對：4 個重複已略過": "Duplicate check · 4 files skipped",

    "Facebook 專頁": "Facebook Pages",
    "行事曆": "Calendar",
    "小紅書": "Xiaohongshu",
    "開發中": "In development",
    "計劃中": "Planned",
    "研究中": "Exploring",
    "支援發佈與留言管理": "Publish posts and manage comments",
    "Google／Outlook：讀取日程、草擬會議邀請（核准後發出）": "Google / Outlook: read calendars and draft meeting invites for approval",
    "審批通知可直接在 Slack 蓋章": "Approve notifications directly in Slack",
    "先做草稿與手機提醒發佈": "Draft first, then remind you to publish on mobile",
  };

  function connectorLang() {
    return window.useScenarioLang ? window.useScenarioLang() : (window.I18N ? window.I18N.getLang() : "en");
  }
  function connectorText(value, lang) {
    const raw = String(value == null ? "" : value);
    if (!raw) return raw;
    const english = CONNECTOR_TEXT_EN[raw];
    if (lang === "en") return english || raw;
    if (english && window.I18N) return window.I18N.t(english);
    if (lang === "zh-Hans" && window.I18N && window.I18N.toHansText) return window.I18N.toHansText(raw);
    return raw;
  }
  function connectorName(conn, lang) {
    if (lang === "en") return (window.CONN_NAME_EN && window.CONN_NAME_EN[conn.id]) || connectorText(conn.name, lang);
    return connectorText(conn.name, lang);
  }

  // PC-3: real brand SVG tiles ported verbatim from the office app (src lines 913-938).
  const BRANDS = window.BRANDS = {
    email: { bg: "#efeaff", svg: () => h("svg", { width: 26, height: 26, viewBox: "0 0 24 24", fill: "none" },
      h("rect", { x: 2, y: 5, width: 20, height: 14, rx: 3, fill: "#7c5cff" }),
      h("path", { d: "M3.5 7.5 12 13.5l8.5-6", stroke: "#fff", strokeWidth: 1.8, fill: "none", strokeLinecap: "round", strokeLinejoin: "round" })) },
    instagram: { bg: "#ffeef6", svg: () => h("svg", { width: 26, height: 26, viewBox: "0 0 24 24", fill: "none" },
      h("defs", null, h("linearGradient", { id: "igG", x1: "0", y1: "1", x2: "1", y2: "0" },
        h("stop", { offset: "0%", stopColor: "#FFC107" }),
        h("stop", { offset: "45%", stopColor: "#F4435A" }),
        h("stop", { offset: "100%", stopColor: "#9C27B0" }))),
      h("rect", { x: 2.6, y: 2.6, width: 18.8, height: 18.8, rx: 5.6, stroke: "url(#igG)", strokeWidth: 2.2 }),
      h("circle", { cx: 12, cy: 12, r: 4.6, stroke: "url(#igG)", strokeWidth: 2.2 }),
      h("circle", { cx: 17.1, cy: 6.9, r: 1.5, fill: "url(#igG)" })) },
    whatsapp: { bg: "#e9f9ef", svg: () => h("svg", { width: 26, height: 26, viewBox: "0 0 24 24" },
      h("path", { d: "M12 2a10 10 0 0 0-8.6 15.1L2 22l5.1-1.3A10 10 0 1 0 12 2z", fill: "#25D366" }),
      h("path", { d: "M8.6 7.4c.2-.5.5-.5.7-.5h.6c.2 0 .5 0 .7.5.2.5.8 1.9.8 2 0 .2-.1.4-.2.5l-.4.5c-.1.2-.3.3-.1.6.2.3.8 1.3 1.7 2.1 1.2 1 2.1 1.3 2.4 1.5.3.1.5.1.6-.1l.7-.9c.2-.3.4-.2.7-.1l1.8.9c.3.1.5.2.5.4 0 .2 0 1-.4 1.6-.4.6-1.5 1.2-2 1.2-.5.1-1.2.1-1.9-.1a13 13 0 0 1-3.9-2.1 12 12 0 0 1-2.7-3.3c-.5-.9-.6-1.7-.6-2.3 0-.9.5-1.7.9-2.4z", fill: "#fff" })) },
    gdrive: { bg: "#fff7e0", svg: () => h("svg", { width: 26, height: 26, viewBox: "0 0 24 24" },
      h("path", { d: "M8.7 3h6.6L23 16.4h-6.6z", fill: "#FFBA00" }),
      h("path", { d: "M8.7 3 1 16.4l3.3 5.6L12 8.6z", fill: "#00AC47" }),
      h("path", { d: "M7.5 16.4 4.3 22h13.2l3.2-5.6z", fill: "#2684FC" })) },
    dropbox: { bg: "#e8f1ff", svg: () => h("svg", { width: 26, height: 26, viewBox: "0 0 24 24" },
      h("path", { d: "M7.5 1.8 12 5 7.5 8.2 3 5z", fill: "#0061FF" }),
      h("path", { d: "M16.5 1.8 21 5 16.5 8.2 12 5z", fill: "#0061FF" }),
      h("path", { d: "M7.5 8.2 12 11.4 7.5 14.6 3 11.4z", fill: "#0061FF" }),
      h("path", { d: "M16.5 8.2 21 11.4 16.5 14.6 12 11.4z", fill: "#0061FF" }),
      h("path", { d: "M12 14.9 16.3 17.9 12 21 7.7 17.9z", fill: "#0061FF" })) },
    onedrive: { bg: "#e6f1fb", svg: () => h("svg", { width: 26, height: 26, viewBox: "0 0 24 24" },
      h("path", { d: "M13.9 8.2a5.2 5.2 0 0 0-9.3 1.8A4.2 4.2 0 0 0 5 18.3h2.9l1.5-6.1 4.5-4z", fill: "#0364B8" }),
      h("path", { d: "M13.9 8.2 9.4 12.2 8 18.3h9.7a3.6 3.6 0 0 0 .7-7.1 4.3 4.3 0 0 0-4.5-3z", fill: "#0078D4" }),
      h("path", { d: "M14.4 8.3a4.3 4.3 0 0 1 4 2.9 3.6 3.6 0 0 1 2.4 1 4 4 0 0 0-6.6-3.9z", fill: "#28A8EA" })) },
    box: { bg: "#e6f0fc", svg: () => h("svg", { width: 26, height: 26, viewBox: "0 0 24 24" },
      h("rect", { x: 2.5, y: 2.5, width: 19, height: 19, rx: 4.5, fill: "#0061D5" }),
      h("circle", { cx: 9.2, cy: 13.4, r: 2.2, fill: "none", stroke: "#fff", strokeWidth: 1.5 }),
      h("circle", { cx: 14.8, cy: 13.4, r: 2.2, fill: "none", stroke: "#fff", strokeWidth: 1.5 }),
      h("path", { d: "M9.2 11.2 7.4 7.4M14.8 11.2 16.6 7.4", stroke: "#fff", strokeWidth: 1.5, strokeLinecap: "round" })) },
  };

  function scopeRows(scopes, lang) {
    return scopes.map((sc, i) => h("div", { key: i, className: "scope-row" },
      h("span", { className: "scope-ico" }, h(Icon, { name: sc.level === "write" ? "edit" : "search", size: 14 })),
      h("div", { className: "grow" },
        h("div", { className: "row gap8 wrap" },
          h("span", null, connectorText(sc.label, lang)),
	          h(Chip, { tone: sc.level === "write" ? "amber" : "blue" }, sc.level === "write" ? "Can edit" : "Can view")),
        sc.note && h("div", { className: "scope-note" }, h("span", null, connectorText(sc.note, lang))))));
  }

  const CONNECTOR_ACCOUNT_OPTIONS = {
    email: [
      { account: "support@morninglight-demo.hk", accountSub: "Shared support mailbox" },
      { account: "orders@morninglight-demo.hk", accountSub: "Orders mailbox" },
    ],
    instagram: [
      { account: "@morninglight.shop", accountSub: "Social commerce account" },
      { account: "@morninglight.support", accountSub: "Customer care account" },
    ],
    gdrive: [
      { account: "ops@morninglight-demo.hk", accountSub: "Operations shared drive" },
      { account: "finance@morninglight-demo.hk", accountSub: "Finance source folder" },
    ],
    whatsapp: [
      { account: "Morning Light Support", accountSub: "Verified business number" },
      { account: "Morning Light Wholesale", accountSub: "Wholesale enquiry number" },
    ],
    dropbox: [
      { account: "ops@morninglight-demo.hk", accountSub: "Team folder owner" },
      { account: "receipts@morninglight-demo.hk", accountSub: "Receipts intake folder" },
    ],
    onedrive: [
      { account: "operations@morninglight-demo.hk", accountSub: "Shared document library" },
      { account: "finance@morninglight-demo.hk", accountSub: "Finance document library" },
    ],
    box: [
      { account: "ops-content@morninglight-demo.hk", accountSub: "Content workspace" },
      { account: "finance-vault@morninglight-demo.hk", accountSub: "Restricted folder collaborator" },
    ],
  };

  function connectorAccountOptions(conn) {
    const flow = conn.connectFlow || {};
    const out = [];
    const add = (account, accountSub) => {
      const a = String(account || "").trim();
      if (!a || out.some((x) => x.account === a)) return;
      out.push({ account: a, accountSub: String(accountSub || "Connected account").trim() || "Connected account" });
    };
    add(flow.account || conn.connectedAccount || conn.detail || conn.name, flow.accountSub || conn.connectedAccountSub);
    (CONNECTOR_ACCOUNT_OPTIONS[conn.id] || []).forEach((opt) => add(opt.account, opt.accountSub));
    return out.slice(0, 3);
  }

  function ConnectModal({ conn, ctx, reauth, onClose }) {
    const { Button, Icon, Modal, ProgressBar, Chip } = window;
    const lang = connectorLang();
    const flow = conn.connectFlow || {}; const wizard = flow.wizard || [];
	    const firstSync = flow.firstSync || ["Checking access…", "Checking for new items…", "Complete"];
    const accountOptions = connectorAccountOptions(conn);
	    const initialAccount = accountOptions[0] || { account: conn.name, accountSub: "Connected account" };
    const stepIds = reauth ? ["account"] : ["account"].concat(wizard.map((_, i) => "wiz" + i)).concat(["scopes", "confirm"]);
    const [si, setSi] = useState(0); const [techOpen, setTechOpen] = useState(false);
    const [syncIdx, setSyncIdx] = useState(-1);
    const [accountMode, setAccountMode] = useState("saved");
    const [selectedAccount, setSelectedAccount] = useState(initialAccount.account);
    const [customAccount, setCustomAccount] = useState("");
	    const [customSub, setCustomSub] = useState(connectorName(conn, lang) + " account");
    const [accountError, setAccountError] = useState("");
    const syncing = syncIdx >= 0;
    function accountMeta() {
      const fromSaved = accountOptions.find((opt) => opt.account === selectedAccount) || initialAccount;
      const custom = accountMode === "custom";
      return {
        account: String(custom ? customAccount : fromSaved.account || "").trim(),
	        accountSub: String(custom ? customSub : fromSaved.accountSub || "Connected account").trim() || "Connected account",
        accountMode: custom ? "custom" : "saved",
      };
    }
    function accountReady() { return !!accountMeta().account; }
    function pickSaved(account) {
      setSelectedAccount(account);
      setAccountMode("saved");
      setAccountError("");
    }
    function continueFromAccount() {
	      if (!accountReady()) { setAccountError("Enter the account to connect."); return; }
      setAccountError("");
      if (reauth) { ctx.reauth(conn.id, accountMeta()); onClose(); return; }
      setSi(1);
    }
    useEffect(() => {
      if (!syncing) return;
	      if (syncIdx >= firstSync.length) { const t = setTimeout(() => { ctx.connect(conn.id, accountMeta()); onClose(); ctx.showToast(connectorName(conn, lang) + " connected"); }, 450); return () => clearTimeout(t); }
      const t = setTimeout(() => setSyncIdx(syncIdx + 1), 700); return () => clearTimeout(t);
    }, [syncIdx]);
	    function cancel() { if (!reauth) ctx.showToast("Connection cancelled"); onClose(); }
    const cur = stepIds[si];
    let body = null, footer = [h(Button, { key: "c", onClick: cancel }, "Cancel")];
    if (cur === "account") {
      body = h("div", null,
        accountOptions.map((opt) => h("button", { key: opt.account, type: "button", className: "oauth-acct" + (accountMode === "saved" && selectedAccount === opt.account ? " selected" : ""), "data-connector-account-option": opt.account, onClick: () => pickSaved(opt.account) },
          h("span", { className: "oauth-ava" }, (opt.account || "?").charAt(0).toUpperCase()),
          h("div", { className: "grow" }, h("b", { style: { fontSize: 14 } }, h("span", null, connectorText(opt.account, lang))), h("div", { style: { fontSize: 12, color: "var(--text-faint)" } }, h("span", null, connectorText(opt.accountSub, lang)))),
          accountMode === "saved" && selectedAccount === opt.account && h(Chip, { tone: "blue" }, "Selected"))),
	        h("button", { type: "button", className: "oauth-acct alt" + (accountMode === "custom" ? " selected" : ""), "data-connector-account-custom-toggle": "true", onClick: () => { setAccountMode("custom"); setAccountError(""); } },
	          h("span", { className: "oauth-ava", style: { background: "var(--border)", color: "var(--text)" } }, "＋"),
		          h("div", { className: "grow" }, "Use a different account", h("div", { style: { fontSize: 12, color: "var(--text-faint)" } }, "Connect another account or folder."))),
        accountMode === "custom" && h("div", { className: "acct-form" },
          h("input", { className: "field", "data-connector-account-custom": "true", value: customAccount, placeholder: "account@company.com or workspace name", onChange: (e) => { setCustomAccount(e.target.value); setAccountError(""); } }),
          h("input", { className: "field", "data-connector-account-custom-sub": "true", value: customSub, placeholder: "Account description", onChange: (e) => setCustomSub(e.target.value) })),
        accountError && h("div", { className: "scope-note", style: { color: "var(--red)" } }, accountError),
        flow.trust && h("p", { style: { fontSize: 12, lineHeight: 1.6, marginTop: 10, color: "var(--text-faint)" } }, h("span", null, connectorText(flow.trust, lang))));
	      footer.push(h(Button, { key: "n", variant: "primary", "data-connector-account-continue": "true", disabled: !accountReady(), onClick: continueFromAccount }, reauth ? "Reconnect" : "Continue"));
    } else if (cur.indexOf("wiz") === 0) {
      const w = wizard[parseInt(cur.slice(3), 10)];
      body = h("div", null, h("b", { style: { fontSize: 14 } }, h("span", null, connectorText(w.title, lang))),
        h("div", { style: { marginTop: 10 } }, w.lines.map((l, i) => h("p", { key: i, style: { fontSize: 13.5, lineHeight: 1.65, margin: "0 0 8px" } }, h("span", null, connectorText(l, lang))))));
      footer.push(h(Button, { key: "n", variant: "primary", onClick: () => setSi(si + 1) }, "Next"));
    } else if (cur === "scopes") {
      body = h("div", null, scopeRows(conn.scopes, lang),
	        h(Button, { variant: "ghost", sm: true, style: { marginTop: 10 }, onClick: () => setTechOpen((v) => !v) }, h(Icon, { name: techOpen ? "chevDown" : "chevRight", size: 14 }), " Advanced details"),
        techOpen && h("div", { className: "tech-scopes" }, h("span", null, connectorText(conn.techScopes, lang))));
      footer.push(h(Button, { key: "n", variant: "primary", onClick: () => setSi(si + 1) }, "Continue"));
    } else {
      const meta = accountMeta();
      body = h("div", null,
        h("div", { className: "oauth-acct selected" }, h("span", { className: "oauth-ava" }, (meta.account || "?").charAt(0).toUpperCase()),
          h("div", { className: "grow" }, h("b", { style: { fontSize: 14 } }, h("span", null, connectorText(meta.account, lang))), h("div", { style: { fontSize: 12, color: "var(--text-faint)" } }, h("span", null, connectorText(meta.accountSub, lang)))),
	          h(Chip, { tone: "blue" }, h("span", null, connectorText(conn.mode, lang))), h(Chip, { tone: "gray" }, h("span", null, conn.scopes.length + " "), h("span", null, "access items"))),
        syncing && h("div", { style: { marginTop: 12 } }, h(ProgressBar, { value: Math.min(100, syncIdx / firstSync.length * 100), tone: "blue", height: 6 }),
          h("div", { style: { marginTop: 10 } }, firstSync.slice(0, syncIdx).map((l, i) => h("p", { key: i, className: "fade-line mono", style: { fontSize: 13, margin: "0 0 6px" } }, h("span", null, connectorText(l, lang)))))));
	      footer.push(h(Button, { key: "go", variant: "primary", icon: "link", disabled: syncing, "data-connector-connect-final": "true", onClick: () => setSyncIdx(0) }, syncing ? "Connecting…" : "Allow and connect"));
    }
	    return h(Modal, { title: (reauth ? "Reconnect · " : "Connect · ") + connectorName(conn, lang), width: 560, onClose: cancel,
	      sub: reauth ? "Reconnect to add missed items." : "Choose an account and review access.",
      footer },
	      stepIds.length > 1 && h("div", { className: "row gap6", style: { marginBottom: 14 } }, stepIds.map((_, j) => h("span", { key: j, className: "step-dot" + (j === si ? " on" : "") }))),
	      h("div", { "data-oauth-step": cur }, body));
  }

	  function ConnectorDetail({ ctx }) {
    const { PageHeader, Card, Button, Icon, ProgressBar, Chip } = window;
    const lang = connectorLang();
    const [discStep, setDiscStep] = useState(false);
    const [reauth, setReauth] = useState(false);
    const conn = ctx.office.connectors.find((x) => x.id === ctx.pageArg);
    if (!conn) {
      return h("div", null,
        h(PageHeader, { eyebrow: "Apps", title: "App Not Found",
          sub: "This app link does not match a current app.",
	          right: h(Button, { sm: true, onClick: () => ctx.openPage("connectors") }, "Back to Apps") }),
	        h(Card, { style: { padding: 24 } }, h(window.EmptyHint, { icon: "mail", text: "No app matches #" + (ctx.pageArg || "unknown") + "." })));
    }
    const on = isOn(conn);
    const row = (label, content) => h("div", { style: { marginBottom: 14 } }, h("b", { style: { fontSize: 12.5, display: "block", marginBottom: 4 } }, label), content);
    return h("div", null,
      h(PageHeader, { eyebrow: "Apps", title: "Settings · " + connectorName(conn, lang), icon: "mail",
        sub: connectorText(conn.detail, lang),
	        right: [
	          h(Button, { key: "back", sm: true, variant: "ghost", onClick: () => ctx.openPage("connectors") }, "Back to Apps"),
	          h(Button, { key: "sync", sm: true, icon: "refresh", "data-connector-action": "sync-now", "data-connector-id": conn.id,
	            disabled: !on || !ctx.can("connectorMode"), title: !on ? "Connect this app first" : ctx.can("connectorMode") ? "Check for new items" : ctx.denyReason("connectorMode"),
	            onClick: () => ctx.syncConnector(conn.id) }, "Check now"),
	          h(Button, { key: "test", sm: true, variant: "primary", icon: "play", "data-connector-action": "test-connection", "data-connector-id": conn.id,
	            disabled: !on || !ctx.can("connectorMode"), title: !on ? "Connect this app first" : ctx.can("connectorMode") ? "Check this connection" : ctx.denyReason("connectorMode"),
	            onClick: () => ctx.testConnector(conn.id) }, "Check connection"),
	          h(Button, { key: "disc", sm: true, variant: "ghost", "data-connector-action": "disconnect", "data-connector-id": conn.id, "data-connector-confirming": discStep ? "true" : "false", disabled: !ctx.can("disconnect"), title: ctx.can("disconnect") ? "" : ctx.denyReason("disconnect"),
	            onClick: () => { if (!discStep) { setDiscStep(true); return; } ctx.disconnect(conn.id); setDiscStep(false); } }, discStep ? "Confirm disconnect?" : "Disconnect"),
	        ] }),
      h(Card, { style: { padding: 18, marginBottom: 16 } },
	        conn.health !== "ok" && h("div", { className: "health-banner" }, h(Icon, { name: "alert", size: 15 }), h("span", { className: "grow" }, healthMessage(conn)),
	          h(Button, { sm: true, variant: "primary", "data-connector-action": "reauth", "data-connector-id": conn.id, disabled: !ctx.can("reauth"), title: ctx.can("reauth") ? "" : ctx.denyReason("reauth"), onClick: () => setReauth(true) }, "Reconnect")),
        h("div", { className: "row gap8", style: { marginBottom: 14, flexWrap: "wrap" } }, h(Chip, { tone: on ? "mint" : "gray" }, STATUS_EN[conn.status]), h(Chip, { tone: "blue" }, h("span", null, connectorText(conn.mode, lang)))),
	        row("Checks for new items", h("span", { style: { fontSize: 13 } }, connectorText(conn.cadence, lang))),
	        conn.quota && row("Usage limit", h("div", null,
          h(ProgressBar, { value: Math.min(100, conn.quota.used / conn.quota.total * 100), tone: conn.quota.used / conn.quota.total >= 0.8 ? "amber" : "blue", height: 6 }),
          h("div", { style: { fontSize: 12, color: "var(--text-faint)", marginTop: 4 } }, h("span", null, connectorText(conn.quota.label, lang)), " — ", h("span", null, conn.quota.used + " / " + conn.quota.total)))),
	        row("Access", h("div", null, scopeRows(conn.scopes, lang), h("div", { className: "tech-scopes" }, h("span", null, connectorText(conn.techScopes, lang))))),
        caveatLine(connectorText(conn.caveat, lang)),
	        row("Recent checks", conn.syncLog && conn.syncLog.length ? h("div", null, conn.syncLog.map((l, i) => h("div", { key: i, className: "sync-row" }, h("span", null, connectorText(l, lang))))) : h("p", { style: { fontSize: 12.5, color: "var(--text-faint)" } }, "No checks yet")),
	        h("p", { style: { fontSize: 12, lineHeight: 1.6, color: "var(--text-faint)" } }, "Disconnecting stops new items. Files already copied stay in Files.")),
	      reauth && h(ConnectModal, { conn, ctx, reauth: true, onClose: () => setReauth(false) }));
	  }

  function RequestIntegrationModal({ ctx, onClose, initialSource }) {
    const { Button, Modal } = window;
    const lang = connectorLang();
    const roadmap = ctx.office.connectorRoadmap || [];
	    const first = roadmap[0] || { name: "Custom app", eta: "Planned", note: "" };
    const initial = initialSource === "custom" ? null : (roadmap.find((r) => r.name === initialSource) || first);
    const [source, setSource] = useState(initial ? initial.name : "custom");
    const [name, setName] = useState(initial ? connectorText(initial.name, lang) : "");
    const [nameEdited, setNameEdited] = useState(false);
    const [priority, setPriority] = useState("Medium");
    const [roleId, setRoleId] = useState((ctx.office.roles || []).find((r) => r.hired)?.id || "");
	    const [reason, setReason] = useState(initial ? connectorText(initial.note || "", lang) : "Send this work to an employee and My Desk.");
    const [reasonEdited, setReasonEdited] = useState(false);
    const [err, setErr] = useState("");
    useEffect(() => {
      if (source === "custom") return;
      const pick = roadmap.find((r) => r.name === source) || first;
      if (!nameEdited) setName(connectorText(pick.name || "", lang));
      if (!reasonEdited) setReason(connectorText(pick.note || "", lang));
    }, [lang]);
    function chooseSource(value) {
      setSource(value);
      if (value === "custom") {
        setName("");
	      setNameEdited(false);
	        setReason("Send this work to an employee and My Desk.");
        setReasonEdited(false);
        return;
      }
      const pick = roadmap.find((r) => r.name === value) || first;
      setName(connectorText(pick.name || "", lang));
      setNameEdited(false);
      setReason(connectorText(pick.note || "", lang));
      setReasonEdited(false);
    }
    function submit() {
      if (!ctx.can("addConnector")) { setErr(ctx.denyReason("addConnector")); return; }
      const trimmed = String(name || "").trim();
	      if (!trimmed) { setErr("Add the app name before sending."); return; }
      const requestSource = source === "custom" ? "custom" : "roadmap";
      const selectedRoadmap = requestSource === "roadmap" ? roadmap.find((r) => r.name === source) : null;
      const contractName = selectedRoadmap && !nameEdited ? selectedRoadmap.name : trimmed;
      const contractReason = selectedRoadmap && !reasonEdited ? (selectedRoadmap.note || "") : reason;
      const exists = (ctx.office.integrationRequests || []).some((req) =>
        String(req.name || "").toLowerCase() === contractName.toLowerCase() &&
        (req.source || "roadmap") === requestSource &&
        req.status !== "removed");
	      if (exists) { setErr(trimmed + " is already requested."); return; }
      const queued = ctx.requestIntegration({ name: contractName, priority, roleId, reason: contractReason, source: requestSource });
      if (queued) onClose();
    }
    const roleOptions = (ctx.office.roles || []).filter((r) => r.hired);
    return h(Modal, { title: "Request app", icon: "mail", width: 620, onClose,
	      sub: "Name the app and who needs it.",
	      footer: [h(Button, { key: "c", onClick: onClose }, "Cancel"), h(Button, { key: "s", variant: "primary", "data-integration-request-submit": "true", disabled: !ctx.can("addConnector"), title: ctx.can("addConnector") ? "" : ctx.denyReason("addConnector"), onClick: submit }, "Send request")] },
      h("div", { className: "request-grid" },
	        h("label", { className: "request-field" }, "App",
          h("select", { className: "field", "data-integration-request-source": "true", value: source, onChange: (e) => chooseSource(e.target.value) },
            roadmap.map((r) => h("option", { key: r.name, value: r.name }, connectorText(r.name, lang) + " · " + connectorText(r.eta, lang))),
            h("option", { value: "custom" }, "Custom app"))),
        h("label", { className: "request-field" }, "Priority",
          h("select", { className: "field", "data-integration-request-priority": "true", value: priority, onChange: (e) => setPriority(e.target.value) },
            ["High", "Medium", "Low"].map((p) => h("option", { key: p, value: p }, p)))),
        h("label", { className: "request-field full" }, "App name",
	          h("input", { className: "field", "data-integration-request-name": "true", value: name, placeholder: "e.g. Shopify", onChange: (e) => { setName(e.target.value); setNameEdited(true); setErr(""); } })),
	        h("label", { className: "request-field" }, "Employee",
          h("select", { className: "field", "data-integration-request-target": "true", value: roleId, onChange: (e) => setRoleId(e.target.value) },
            roleOptions.map((r) => h("option", { key: r.id, value: r.id }, (window.ROLE_SHORT_EN && window.ROLE_SHORT_EN[r.id]) || r.short || r.title || r.id)))),
	        h("label", { className: "request-field" }, "What happens",
	          h("input", { className: "field", value: "Draft, approve, save", readOnly: true })),
	        h("label", { className: "request-field full" }, "Why you need it",
	          h("textarea", { className: "field", rows: 4, "data-integration-request-reason": "true", value: reason, onChange: (e) => { setReason(e.target.value); setReasonEdited(true); }, placeholder: "What work should this app send?" })),
        err && h("div", { className: "request-field full", style: { color: "var(--red)" } }, err)));
  }

  function Connectors({ ctx }) {
    const { Card, Button, Icon, PageHeader, Chip } = window;
    const lang = connectorLang();
	    const FLOW = [["inbox", "New item"], ["edit", "Employee drafts"], ["stamp", "You approve"], ["upload", "Send and save"]];
    const [connectM, setConnectM] = useState(null);
    const [requestOpen, setRequestOpen] = useState(false);
    const [requestSource, setRequestSource] = useState(null);
    const [view, setView] = useState("cards");
    const canRequestIntegration = ctx.can("addConnector");
    const connOf = (id) => ctx.office.connectors.find((x) => x.id === id);
    const requests = ctx.office.integrationRequests || [];
    function openRequest(source) {
      if (!canRequestIntegration) { if (ctx.showToast) ctx.showToast(ctx.denyReason("addConnector")); return; }
      setRequestSource(source || null);
      setRequestOpen(true);
    }
    function closeRequest() {
      setRequestOpen(false);
      setRequestSource(null);
    }

	    const connectorCards = view === "cards" && h("div", { className: "conn-grid", "data-connector-view": "cards", "data-connector-count": String(ctx.office.connectors.length) }, ctx.office.connectors.map((c) => {
      const b = BRANDS[c.id] || BRANDS.email; const on = isOn(c);
      return h(Card, { key: c.id, className: "conn-card" + (on ? " on" : ""), "data-connector-id": c.id, style: { padding: 16 } },
        h("div", { className: "row gap10", style: { marginBottom: 10 } },
          h("div", { className: "brand-tile", style: { background: b.bg } }, b.svg()),
          h("div", { className: "grow" }, h("b", { style: { fontSize: 15 } }, h("span", null, connectorName(c, lang))), h("div", { style: { fontSize: 12.5, color: "var(--text-faint)" } }, h("span", null, connectorText(c.detail, lang)))),
          h(Chip, { tone: on ? "mint" : "gray" }, STATUS_EN[c.status])),
	        on && c.health !== "ok" && h("div", { className: "health-banner" }, h(Icon, { name: "alert", size: 15 }), h("span", { className: "grow" }, healthMessage(c)),
	          h(Button, { sm: true, variant: "primary", "data-connector-action": "reauth", "data-connector-id": c.id, disabled: !ctx.can("reauth"), title: ctx.can("reauth") ? "" : ctx.denyReason("reauth"), onClick: () => setConnectM({ id: c.id, reauth: true }) }, "Reconnect")),
        h("div", { className: "conn-stat" }, h("span", null, connectorText(c.stat, lang))),
        h("p", { style: { fontSize: 13, lineHeight: 1.6, color: "var(--text-dim)" } }, h("span", null, connectorText(c.note, lang))),
        h("div", { className: "row", style: { marginTop: 12 } },
          h("span", { style: { fontSize: 12, color: "var(--text-faint)" } }, on ? h("span", null, connectorText(c.last, lang)) : "Not connected"), h("span", { className: "grow" }),
          on ? h(Button, { sm: true, "data-connector-action": "settings", "data-connector-id": c.id, onClick: () => ctx.openPage("connector", c.id) }, "Settings")
             : h(Button, { variant: "primary", sm: true, icon: "link", "data-connector-action": "connect", "data-connector-id": c.id, disabled: !ctx.can("connect"), title: ctx.can("connect") ? "" : ctx.denyReason("connect"), onClick: () => setConnectM({ id: c.id, reauth: false }) }, "Connect")),
        caveatLine(connectorText(c.caveat, lang)));
    }));

    function connectorTableRow(c) {
      const b = BRANDS[c.id] || BRANDS.email;
      const on = isOn(c);
      const action = on
        ? h(Button, { sm: true, "data-connector-action": "settings", "data-connector-id": c.id, onClick: () => ctx.openPage("connector", c.id) }, "Settings")
        : h(Button, { variant: "primary", sm: true, icon: "link", "data-connector-action": "connect", "data-connector-id": c.id, disabled: !ctx.can("connect"), title: ctx.can("connect") ? "" : ctx.denyReason("connect"), onClick: () => setConnectM({ id: c.id, reauth: false }) }, "Connect");
      return h("tr", { key: c.id, "data-connector-id": c.id },
        h("td", null,
          h("div", { className: "row gap10" },
            h("div", { className: "brand-tile", style: { background: b.bg, width: 32, height: 32, flex: "none" } }, b.svg()),
            h("div", { style: { minWidth: 0 } },
              h("b", { style: { fontSize: 13.5 } }, h("span", null, connectorName(c, lang))),
              h("div", { style: { fontSize: 12, color: "var(--text-faint)" } }, h("span", null, connectorText(c.detail, lang)))))),
        h("td", null,
          h(Chip, { tone: on ? "mint" : "gray" }, STATUS_EN[c.status]),
          on && c.health !== "ok" && h("div", { style: { fontSize: 11.5, color: "var(--amber)", marginTop: 4 } }, healthMessage(c))),
        h("td", null, h("span", null, connectorText(c.owner || "—", lang))),
        h("td", { style: { textAlign: "right" } },
          h("div", { className: "row gap8", style: { justifyContent: "flex-end" } },
            on && c.health !== "ok" && h(Button, { sm: true, variant: "primary", "data-connector-action": "reauth", "data-connector-id": c.id, disabled: !ctx.can("reauth"), title: ctx.can("reauth") ? "" : ctx.denyReason("reauth"), onClick: () => setConnectM({ id: c.id, reauth: true }) }, "Reconnect"),
            action)));
    }

	  const connectorTable = view === "table" && h(Card, { "data-connector-view": "table", "data-connector-count": String(ctx.office.connectors.length), style: { padding: 0, overflow: "hidden", marginBottom: 14 } },
      h("div", { style: { overflowX: "auto" } },
        h("table", { className: "tbl" },
          h("thead", null, h("tr", null, ["App", "Status", "Owner", "Actions"].map((cH, i) => h("th", { key: i, style: i === 3 ? { textAlign: "right" } : null }, cH)))),
          h("tbody", null, ctx.office.connectors.map(connectorTableRow)))));

    const requestQueue = requests.length > 0 && h("div", { className: "integration-queue" },
      requests.map((req) => h("div", { key: req.id, className: "integration-request", "data-integration-request-card": "true", "data-integration-request-id": req.id },
        h("div", null,
          h("b", null, connectorText(req.name, lang)),
          h("span", null, [req.priority || "Medium", req.targetRoleName || "Workspace", req.status === "reviewed" ? "Reviewed" : "Requested"].join(" · ")),
          req.reason && h("span", { style: { display: "block", marginTop: 3 } }, connectorText(req.reason, lang))),
        h("div", { className: "row gap8 wrap", style: { justifyContent: "flex-end" } },
          h(Chip, { tone: req.status === "reviewed" ? "mint" : "amber" }, req.status === "reviewed" ? "Reviewed" : "Requested"),
          req.status !== "reviewed" && h(Button, { sm: true, variant: "primary", "data-integration-request-action": "review", disabled: !ctx.can("connect"), title: ctx.can("connect") ? "" : ctx.denyReason("connect"), onClick: () => ctx.reviewIntegrationRequest(req.id) }, "Mark reviewed")))));

    const roadmapCard = h(Card, { style: { padding: 18 } },
	      h("div", { className: "row gap10 wrap" }, h("b", { style: { fontSize: 13.5 } }, "Planned apps"),
        ctx.office.connectorRoadmap.map((r) => {
          const req = requests.find((item) => item.name === r.name && item.source === "roadmap");
          const statusLabel = req ? (req.status === "reviewed" ? "Reviewed" : "Requested") : r.eta;
          return h("button", { key: r.name, type: "button", className: "chip chip-" + (req ? (req.status === "reviewed" ? "mint" : "amber") : "gray"), title: canRequestIntegration ? connectorText(r.note || "", lang) : ctx.denyReason("addConnector"), disabled: !canRequestIntegration, "data-integration-roadmap-name": r.name, "data-integration-roadmap-status": req ? req.status : "available", onClick: () => openRequest(r.name) },
            h("span", null, connectorText(r.name, lang)), h("span", { "aria-hidden": "true" }, " · "), h("span", null, connectorText(statusLabel, lang)));
        }),
        requests.length > 0 && h(Chip, { tone: "amber", "data-integration-request-count": String(requests.length) }, h("span", null, requests.length), " ", h("span", null, "requested")),
        h("span", { className: "grow" }), h(Button, { sm: true, "data-integration-request-open": "true", disabled: !canRequestIntegration, title: canRequestIntegration ? "" : ctx.denyReason("addConnector"), onClick: () => openRequest(null) }, "Need another app? Tell us")),
      requestQueue);

		    return h("div", { "data-connectors-screen": "true", "data-connectors-view": view },
		      h(PageHeader, { eyebrow: "Company", title: "Apps", icon: "mail",
		        sub: "Connect email, messages, and files. New work waits for approval.",
	        right: h(Segmented, { value: view, onChange: setView, options: [{ value: "cards", label: "Cards", attrs: { "data-connector-view-option": "cards", "data-connector-view-active": view === "cards" ? "true" : "false" } }, { value: "table", label: "Table", attrs: { "data-connector-view-option": "table", "data-connector-view-active": view === "table" ? "true" : "false" } }] }) }),
      h(Card, { className: "flow-strip", style: { marginBottom: 16 } },
        FLOW.map((f, i) => h(React.Fragment, { key: i },
          h("div", { className: "flow-node" }, h("span", { className: "flow-ico" }, h(Icon, { name: f[0], size: 17 })), h("b", null, f[1])),
          i < FLOW.length - 1 && h("span", { className: "flow-arrow" }, "->")))),
      connectorCards,
      connectorTable,
      roadmapCard,
      connectM && connOf(connectM.id) && h(ConnectModal, { conn: connOf(connectM.id), ctx, reauth: connectM.reauth, onClose: () => setConnectM(null) }),
      requestOpen && h(RequestIntegrationModal, { ctx, initialSource: requestSource, onClose: closeRequest }));
  }

  window.Screens.connector = ConnectorDetail;
  window.Screens.connectors = Connectors;
})();
