CII Eye
Vision by CII
Your World • Your AI • Your Super Platform
Debate Arena
Live
Debate Arena
Step into the Arena.
V-moderated debates • trending now
Tip: click V orb to greet + listen
Live Feed
scroll • tap image to expand
V-Platform Vault
Vault
Tools. Creation. Power.
Built for everyone • unlock your stack
Enter Vault
V
Listening…
Listening…
function createOrUpdate(doc){ const d = doc.data() || {}; let el = nodes.get(doc.id); if(!el){ el = document.createElement("div"); el.className = "cii-post cii-feed-card cii-tilt"; el.dataset.id = doc.id; nodes.set(doc.id, el); // insert later in correct order } const me = auth && auth.currentUser ? auth.currentUser.uid : null; const OWNER_UID = "u7vHccI739WjUHf59qeQ46X4iZs1"; const canDelete = me && d.authorId && (me === d.authorId || me === OWNER_UID); el.innerHTML = `
${await (async ()=>{ const profile = await getProfile(d.authorId); const avatar = profile.photoURL ? `` : `
`; return avatar + `${profile.displayName}`; })()}
${esc(timeAgo(d.createdAt))}
${esc(d.text)}
V Assist
View all comments
`; // Delete const del = el.querySelector(".cii-del"); if (del && canDelete){ del.onclick = async ()=>{ try{ await db.collection(FEED_COLL).doc(doc.id).delete(); }catch(e){ console.error("[CII SOCIAL] delete failed", e); } }; } // Multi-reactions (FB-style: one per user, toggle/switch) const actions = el.querySelector('[data-actions="1"]'); const postRef = db.collection(FEED_COLL).doc(doc.id); async function getMyReaction(uid){ try{ const r = await postRef.collection("reactions").doc(uid).get(); return r.exists ? (r.data()||{}).reaction || null : null; }catch(_){ return null; } } function paintReaction(active){ const btns = el.querySelectorAll(".cii-react[data-react]"); btns.forEach(b=>{ const r = b.getAttribute("data-react"); if(active && r===active) b.classList.add("on"); else b.classList.remove("on"); }); } // best-effort: paint active state from cached field if present try{ const my = (auth && auth.currentUser) ? auth.currentUser.uid : null; if(my){ // async paint (non-blocking) getMyReaction(my).then(paintReaction); } }catch(_){} if(actions && !el.__reactBound){ el.__reactBound = true; actions.addEventListener("click", async (e)=>{ const btn = e.target && e.target.closest ? e.target.closest(".cii-react[data-react]") : null; if(!btn) return; e.preventDefault(); e.stopPropagation(); const user = firebase.auth().currentUser; if(!user){ alert("Sign-in not ready. Refresh once."); return; } const uid = user.uid; const reaction = btn.getAttribute("data-react"); const reactRef = postRef.collection("reactions").doc(uid); // optimistic UI: toggle immediately const wasOn = btn.classList.contains("on"); paintReaction(wasOn ? null : reaction); try{ await db.runTransaction(async (tx)=>{ const [pSnap, rSnap] = await Promise.all([tx.get(postRef), tx.get(reactRef)]); const post = pSnap.data() || {}; const reacts = Object.assign({}, post.reacts || {}); const prev = rSnap.exists ? (rSnap.data()||{}).reaction : null; // helper to inc/dec safely function inc(key, delta){ const cur = Number(reacts[key]||0); const next = Math.max(0, cur + delta); if(next===0) delete reacts[key]; else reacts[key]=next; } if(prev && prev===reaction){ // toggle off inc(prev, -1); tx.delete(reactRef); }else{ if(prev) inc(prev, -1); inc(reaction, +1); tx.set(reactRef, { userId: uid, reaction, updatedAt: firebase.firestore.FieldValue.serverTimestamp(), createdAt: rSnap.exists ? (rSnap.data()||{}).createdAt || firebase.firestore.FieldValue.serverTimestamp() : firebase.firestore.FieldValue.serverTimestamp() }, {merge:true}); } tx.set(postRef, { reacts }, {merge:true}); }); }catch(err){ console.log("[CII SOCIAL] react failed", err); // revert paint from server on failure try{ paintReaction(await getMyReaction(uid)); }catch(_){} } }); } // Comment UI const cbox = el.querySelector(".cii-cbox"); const toggle = el.querySelector(".cii-comment-toggle"); toggle.onclick = ()=>{ try{ if(window.CII_openThreadSheet) return window.CII_openThreadSheet(doc.id, (d.authorId||d.authorUid||null)); }catch(e){} cbox.style.display = (cbox.style.display==="block"?"none":"block"); }; const viewAll = el.querySelector(".cii-view"); viewAll.onclick = ()=>{ try{ if(window.CII_openThreadSheet) return window.CII_openThreadSheet(doc.id, (d.authorId||d.authorUid||null)); }catch(e){} cbox.style.display = "block"; el.querySelector(".cii-input").focus(); }; // Live preview: last 1–2 post comments (IG-style mini preview) if (!el.__commentsUnsub) { try { const postRef = db.collection(FEED_COLL).doc(doc.id); el.__commentsUnsub = postRef .collection("comments") .orderBy("createdAt", "desc") .limit(2) .onSnapshot( (snap) => { const lastComments = el.querySelector('.cii-comment-list'); if (!lastComments) return; const items = []; snap.forEach((c) => items.push({ id: c.id, ...c.data() })); items.reverse(); // show oldest-first in preview if (!items.length) { lastComments.innerHTML = `
No comments yet — be first.
`; return; } lastComments.innerHTML = items .map((c) => { const name = esc(c.displayName || c.authorName || "User"); const txt = esc(c.text || ""); return `
${name}${txt}
`; }) .join(""); }, (err) => console.warn("[CII SOCIAL] comments preview failed", err) ); } catch (e) { console.warn("[CII SOCIAL] comments preview init failed", e); } } // Send comment (WhatsApp style) const input = el.querySelector(".cii-input"); const send = el.querySelector(".cii-send"); const sendComment = async ()=>{ try{ const txt = String(input.value||"").trim(); if(!txt) return; const u = firebase.auth().currentUser; if(!u){ // best-effort anonymous sign-in (rules require signedIn()) await Promise.resolve(); /* anon disabled */ } const user = firebase.auth().currentUser; // Write REAL post comment (shows immediately + matches Firestore rules) const postRef = db.collection(FEED_COLL).doc(doc.id); await postRef.collection("comments").add({ authorId: user ? user.uid : "guest", displayName: (user && user.displayName) ? String(user.displayName).slice(0,80) : "V", text: txt.slice(0, 2000), createdAt: firebase.firestore.FieldValue.serverTimestamp() }); // keep post comment counter in-sync for live feed badges try{ await postRef.set({ commentCount: firebase.firestore.FieldValue.increment(1) }, {merge:true}); }catch(_){ } // Optional: trigger V auto-reply thread (backend writes V replies under /v_assist_comments/{threadId}) // We intentionally ignore "role:user" entries from that collection (see bottom sheet), // so user comments aren't duplicated in the UI. try{ const call = firebase.functions().httpsCallable("vAssistAddComment"); await call({ threadId: `post_${doc.id}`, text: txt, role: "user", displayName: (user && user.displayName) ? String(user.displayName).slice(0,80) : "V", deviceId: (window.CII_DEVICE_ID || "") }); }catch(e){ // non-fatal — V Assist can fail without breaking comments console.warn("[CII VASSIST] addComment non-fatal", e); } input.value=""; cbox.classList.remove("open"); }catch(e){ console.error("[CII SOCIAL] comment failed", e); } }; send.onclick = sendComment; input.onkeydown = (e)=>{ if(e.key==="Enter") sendComment(); }; // V Assist const chips = el.querySelector(".cii-vassist"); const sugg = el.querySelector(".cii-suggest"); const ta = el.querySelector(".cii-suggest textarea"); const mini = el.querySelector(".cii-mini"); const use = el.querySelector(".cii-use"); let lastKind = "agree"; chips.addEventListener("click", async (ev)=>{ const chip = ev.target.closest(".cii-chip"); if(!chip) return; lastKind = chip.getAttribute("data-kind") || "agree"; sugg.style.display = "block"; use.disabled = true; mini.textContent = "V is writing…"; try{ const suggestion = await callVAssist({ assistKind: lastKind, postText: d.text || "" }); ta.value = suggestion || ""; use.disabled = !ta.value.trim(); mini.textContent = suggestion ? "Edit if you want, then tap Use." : "No suggestion returned."; }catch(e){ console.error("[CII VASSIST] failed", e); mini.textContent = "V Assist failed (check callable name / deploy)."; } }); ta.addEventListener("input", ()=>{ use.disabled = !ta.value.trim(); }); use.onclick = async ()=>{ const t = (ta.value||"").trim(); if(!t) return; const u = auth && auth.currentUser ? auth.currentUser : null; if(!u){ alert("Please sign in to comment."); return; } ta.value = ""; use.disabled = true; mini.textContent = "Posting…"; // optimistic comment count const cc = el.querySelector(".cc"); const prev = Number(cc.textContent||0); cc.textContent = String(prev+1); try{ await db.collection("v_assist_comments").doc(doc.id).collection("comments").add({ text: t, authorId: u.uid, authorName: (window.currentUser && window.currentUser.displayName) ? window.currentUser.displayName : (window.currentUser && window.currentUser.isAnonymous ? "User" : "User"), createdAt: firebase.firestore.Timestamp.now(), createdAtServer: firebase.firestore.FieldValue.serverTimestamp(), assistKind: lastKind, assistUsed: true }); await db.collection(FEED_COLL).doc(doc.id).update({ commentCount: firebase.firestore.FieldValue.increment(1) }); mini.textContent = "Posted."; }catch(e){ cc.textContent = String(prev); mini.textContent = "Could not post."; console.error("[CII VASSIST] post failed", e); } }; return el; } // Main feed listener (single owner). NO innerHTML clears. db.collection(FEED_COLL).orderBy("createdAt","desc").onSnapshot((snap)=>{ // Ensure DOM order matches snapshot order without clearing snap.docs.forEach((doc, idx)=>{ const el = createOrUpdate(doc); const currentAt = feed.children[idx]; if (currentAt !== el){ // insert/move into correct spot feed.insertBefore(el, currentAt || null); } }); // remove nodes not in snapshot const ids = new Set(snap.docs.map(d=>d.id)); Array.from(nodes.keys()).forEach(id=>{ if(!ids.has(id)){ const el = nodes.get(id); try{ if(el && el.__commentsUnsub) el.__commentsUnsub(); }catch(e){} if(el && el.parentNode) el.remove(); nodes.delete(id); } }); console.log("[CII SOCIAL] Rendered (no flash):", snap.size); }, (err)=>console.error("[CII SOCIAL] listener error", err)); console.log("[CII SOCIAL] Phase 1+2+3 + V Assist mounted (no flash)."); })();
V Assist Threads
Realtime comments • reactions • V replies
Tip: type “@V …” or ask a question and V will auto-reply inside the thread.
Saved
Draft updated
// ===== PROFESSIONAL HARD DELETE (LOCKED) ===== async function ciiHardDeletePost(postId){ try{ if(!firebase.auth().currentUser){ alert("You must be logged in."); return; } const uid = firebase.auth().currentUser.uid; const ref = firebase.firestore().collection("community_posts").doc(postId); const snap = await ref.get(); if(!snap.exists){ console.warn("Post already deleted."); return; } const data = snap.data() || {}; if(data.authorId !== uid){ alert("Delete denied: not post owner."); return; } await ref.delete(); console.log("[CII] Delete successful:", postId); }catch(err){ console.error("[CII] DELETE FAILED:", err); alert("Delete failed: " + (err.message || err)); } } // ===== END DELETE PATCH =====