/* =========================================================================== launch-integrations.jsx — Two-way project-manager sync (ClickUp · Asana · Trello · Basecamp). Mounts inside Settings → Integrations and exposes a per-task send control. Reads/writes the `pmIntegrations` fold seam. Components → window: PMIntegrations, TaskSyncControl. =========================================================================== */ const { Section: PMSection, act: pmAct, ENGINE_LABELS: PM_ENG, ENGINE_COLORS: PM_ENGC } = window; const RECON_TYPES = { conflict: { label: "Conflict", color: "#E0B638" }, inbound: { label: "New in tool", color: "#10B5C7" }, outbound: { label: "Not pushed", color: "#7A4FBF" }, deleted: { label: "Removed in tool", color: "#F25BA7" }, unmapped: { label: "Unmapped", color: "var(--pvt-text-dim)" }, }; const PM_DIRS = [ { id: "push", label: "Push" }, { id: "two-way", label: "Two-way" }, { id: "pull", label: "Pull" } ]; const PM_FREQ = { realtime: "Real-time", "15m": "Every 15 min", hourly: "Hourly", manual: "Manual" }; const PM_MAP_ROWS = [ { key: "root", lcc: "Workspace root", hint: "The launch maps to one…" }, { key: "engines", lcc: "Engines", hint: "Six operating engines" }, { key: "tasks", lcc: "Tasks", hint: "This-week task board" }, { key: "pipelines", lcc: "Pipelines", hint: "Inquiry → Advanced" }, { key: "timeline", lcc: "Timeline", hint: "Production weeks" }, ]; const PM_SCOPE_ROWS = [ { key: "tasks", label: "Tasks & status", sub: "Two-way task + status sync" }, { key: "engines", label: "Engines", sub: "Group tasks by engine" }, { key: "pipelines", label: "Pipelines", sub: "Stage board cards" }, { key: "timeline", label: "Timeline", sub: "Milestone schedule" }, ]; /* Brand monogram tile (no external logos). */ function PMMark({ pm, size = 38 }) { return ( {pm.name[0]} ); } function PMStatusChip({ pm }) { const tone = pm.connected ? "#10B5C7" : "var(--pvt-text-dim)"; return ( {pm.connected ? "Connected" : "Not connected"} ); } /* Small segmented control. */ function PMSeg({ value, options, onChange }) { return (
{options.map((o) => { const on = o.id === value; return ( ); })}
); } function PMToggle({ on, onClick }) { return ( ); } /* --------------------------------------------------------------------------- Configuration slide-over for one connector. --------------------------------------------------------------------------- */ function PMConfigDrawer({ pm, onClose, patch }) { React.useEffect(() => { const onKey = (e) => { if (e.key === "Escape") onClose(); }; document.addEventListener("keydown", onKey); return () => document.removeEventListener("keydown", onKey); }, []); if (!pm) return null; const conflictOpts = { lcc: "LCC wins", platform: pm.name + " wins", recent: "Most recent wins" }; const connect = () => patch(pm.id, pm.connected ? { connected: false, account: "", who: "", lastSync: "—" } : { connected: true, account: "ARGT 2026", who: "kam@argt.com", lastSync: "just now", log: [{ at: "just now", dir: "out", text: "Connected " + pm.name + " · initial sync queued" }, ...(pm.log || [])] }); const syncNow = () => { patch(pm.id, { lastSync: "just now", log: [{ at: "just now", dir: "out", text: "Manual sync — pushed 8 tasks, pulled 2 updates" }, ...(pm.log || [])] }); if (window.argtPing) window.argtPing(pm.name + " synced just now"); }; const setMap = (k, v) => patch(pm.id, { mapping: { ...pm.mapping, [k]: v } }); const setScope = (k) => patch(pm.id, { scope: { ...pm.scope, [k]: !pm.scope[k] } }); return (
e.stopPropagation()} style={{ width: "min(500px, 96vw)", height: "100%", background: "var(--pvt-surface-raised)", borderLeft: "1px solid var(--pvt-border-light)", boxShadow: "-32px 0 80px rgba(0,0,0,0.5)", display: "flex", flexDirection: "column", animation: "lccDrawerIn 220ms ease" }}> {/* header */}
{pm.name}
{pm.hierarchy}
{pm.connected &&
{pm.account} · {pm.who} · last sync {pm.lastSync}
}
{/* body */}
{/* direction */}
Sync direction
patch(pm.id, { direction: v })} />

{pm.direction === "two-way" ? "Changes flow both ways. Edits here push out; edits in " + pm.name + " pull back in." : pm.direction === "push" ? "LCC is the source of truth — changes push out to " + pm.name + " only." : pm.name + " is the source of truth — changes pull into LCC only."}

{/* what syncs */}
What syncs
{PM_SCOPE_ROWS.map((r) => (
{r.label}
{r.sub}
setScope(r.key)} />
))}
{/* mapping — group LCC surfaces onto the platform's native hierarchy */}
Mapping · how LCC groups into {pm.name}
{PM_MAP_ROWS.map((row) => (
{row.lcc}
{row.hint}
))}
{/* cadence + conflict */}
Frequency
On conflict
{/* activity */}
Sync activity
{(pm.log || []).map((l, i) => (
{l.dir === "in" ? "↓" : "↑"}
{l.text}
{l.at}
))} {(!pm.log || pm.log.length === 0) &&
No sync activity yet.
}
); } function pmSelStyle() { return { background: "var(--pvt-surface-mid)", border: "1px solid var(--pvt-border-light)", borderRadius: "var(--r-md)", color: "var(--pvt-text)", padding: "10px 12px", fontSize: 13, fontFamily: "var(--pvt-font-body)", outline: "none", width: "100%" }; } /* --------------------------------------------------------------------------- Manager — grid of connectors, mounted in Settings → Integrations. --------------------------------------------------------------------------- */ function PMIntegrations() { const [list, setList] = window.useDB("pmIntegrations"); const [recon] = window.useDB("pmReconcile"); const [open, setOpen] = React.useState(null); const patch = (id, fields) => setList((xs) => xs.map((p) => p.id === id ? { ...p, ...fields } : p)); const reconBy = (k) => recon.filter((r) => r.platform === k).length; const connectedCount = list.filter((p) => p.connected).length; const syncAll = () => { setList((xs) => xs.map((p) => p.connected ? { ...p, lastSync: "just now" } : p)); if (window.argtPing) window.argtPing("Synced all connected tools"); }; const active = open ? list.find((p) => p.id === open) : null; return ( {connectedCount}/{list.length} CONNECTED · 2-WAY}>
{list.map((pm) => (
setOpen(pm.id)} title={"Configure " + pm.name} style={{ background: "var(--pvt-surface-mid)", border: "1px solid var(--pvt-border-light)", borderRadius: "var(--r-md)", padding: 16, display: "flex", flexDirection: "column", gap: 12, cursor: "pointer", transition: "border-color 140ms, transform 140ms" }} onMouseEnter={(e) => { e.currentTarget.style.borderColor = pm.color; e.currentTarget.style.transform = "translateY(-2px)"; }} onMouseLeave={(e) => { e.currentTarget.style.borderColor = "var(--pvt-border-light)"; e.currentTarget.style.transform = "none"; }}>
{pm.name}
{pm.hierarchy}
{reconBy(pm.key) > 0 && {reconBy(pm.key)} to reconcile}
{pm.connected ? "Synced " + pm.lastSync : "Set up sync"} {pm.connected ? "Configure" : "Connect"}
))}
setOpen(null)} patch={patch} />
); } /* --------------------------------------------------------------------------- Per-task send/sync control — drops into the task edit modal. --------------------------------------------------------------------------- */ function TaskSyncControl({ task, setTask }) { const [list] = window.useDB("pmIntegrations"); const connected = list.filter((p) => p.connected); const sync = task && task.sync; const sendTo = (pm) => { setTask((t) => ({ ...t, sync: { key: pm.key, name: pm.name, color: pm.color, mapped: pm.mapping.tasks, at: "just now" } })); if (window.argtPing) window.argtPing("Task sent to " + pm.name + " as a " + pm.mapping.tasks); }; const openExt = () => { if (window.argtPing) window.argtPing("Opening task in " + sync.name); }; const unlink = () => setTask((t) => { const n = { ...t }; delete n.sync; return n; }); return (
Project-manager sync
{connected.length === 0 ? (
No tools connected. Connect one in Settings → Integrations.
) : sync ? (
Synced to {sync.name}{sync.mapped ? · {sync.mapped} : null}
) : (
Send to {connected.map((pm) => ( ))}
)}
); } /* --------------------------------------------------------------------------- Reconciliation queue — divergences a two-way sync can't auto-resolve. Lives on the integrations page, below the connector grid. --------------------------------------------------------------------------- */ function ReconRow({ item, pm, onResolve }) { const meta = RECON_TYPES[item.type] || RECON_TYPES.unmapped; const [mapEngine, setMapEngine] = React.useState("content"); const engLabel = item.engine ? (PM_ENG[item.engine] || item.engine) : null; const name = pm ? pm.name : item.platform; const color = pm ? pm.color : "var(--pvt-text-dim)"; const actions = { conflict: [ { l: "Keep LCC", k: "keep-lcc" }, { l: "Keep " + name, k: "keep-ext" } ], inbound: [ { l: "Import to LCC", k: "import", primary: true }, { l: "Ignore", k: "ignore", dim: true } ], outbound: [ { l: "Push to " + name, k: "push", primary: true }, { l: "Skip", k: "skip", dim: true } ], deleted: [ { l: "Delete in LCC", k: "delete", danger: true }, { l: "Restore in " + name, k: "restore" } ], unmapped: [ { l: "Map + import", k: "map", primary: true }, { l: "Ignore", k: "ignore", dim: true } ], }[item.type] || []; return (
{item.taskTitle} {meta.label} {engLabel && {engLabel}} {name} · {item.detected}
{item.type === "conflict" && item.fields ? (
Field
LCC
{name}
{item.fields.map((f, i) => (
{f.label}
{f.lcc}
{f.ext}
))}
) : (
{item.summary}
)}
{item.type === "unmapped" && ( )} {actions.map((a) => ( ))}
); } function reconCell() { return { background: "var(--pvt-surface)", padding: "8px 12px", fontSize: 12 }; } function PMReconcile() { const [recon, setRecon] = window.useDB("pmReconcile"); const [list, setList] = window.useDB("pmIntegrations"); const [filter, setFilter] = React.useState(null); const pmFor = (k) => list.find((p) => p.key === k); const shown = filter ? recon.filter((r) => r.platform === filter) : recon; const connected = list.filter((p) => p.connected); const resolve = (item, choice, mapEngine) => { const pm = pmFor(item.platform); const name = pm ? pm.name : item.platform; const msg = { "keep-lcc": "Kept LCC version of '" + item.taskTitle + "' · pushed to " + name, "keep-ext": "Accepted " + name + " version of '" + item.taskTitle + "'", "import": "Imported '" + item.taskTitle + "' from " + name, "ignore": "Ignored '" + item.taskTitle + "'", "push": "Pushed '" + item.taskTitle + "' to " + name, "skip": "Skipped '" + item.taskTitle + "'", "delete": "Deleted '" + item.taskTitle + "' in LCC", "restore": "Restored '" + item.taskTitle + "' in " + name, "map": "Mapped '" + item.taskTitle + "' to " + (PM_ENG[mapEngine] || mapEngine) + " and imported", }[choice] || "Resolved"; setRecon((xs) => xs.filter((r) => r.id !== item.id)); if (pm) { const dir = (choice === "keep-lcc" || choice === "push" || choice === "restore") ? "out" : "in"; setList((xs) => xs.map((p) => p.id === pm.id ? { ...p, log: [{ at: "just now", dir, text: msg }, ...(p.log || [])] } : p)); } if (window.argtPing) window.argtPing(msg); }; return ( 0 ? {recon.length} NEED REVIEW : ALL IN SYNC}> {recon.length > 0 && (
{connected.map((pm) => { const n = recon.filter((r) => r.platform === pm.key).length; if (!n) return null; return ; })}
)} {shown.length > 0 ? (
{shown.map((item) => )}
) : (
Everything is in sync
No conflicts or pending changes across your connected tools.
)}
); } Object.assign(window, { PMIntegrations, TaskSyncControl });