// The shell's shared mutable state — the seven bindings every part of the chat // page reads (docs/shell-modules.md § The shellstate seam). This module OWNS // them; everyone else imports them under these same names. ES-module live // bindings make reads and property mutations (threadKeys[id] = k) work // unchanged — only reassigning a WHOLE binding needs its setter, which is all // the setters are: assignment, no logic. Shared *behaviour* does not belong // here; helpers keep moving into their own modules. export const embed = window.__lchatEmbed || null; // native-webview embed config, or null in a browser // Feature gates the server stamps into the page as meta tags ("0" = off). const gateMeta = (name) => ((document.querySelector(`meta[name="${name}"]`) || {}).content || "") !== "0"; export const ENABLE_AI = gateMeta("enable-ai"); export const ENABLE_BOTS = gateMeta("enable-bots"); export const ENABLE_APPS = gateMeta("enable-apps"); export const ENABLE_MEMBER_APPS = gateMeta("enable-member-apps"); // The places-UI experiment gate — OFF by default, and deliberately NOT a // server gate: it hides the multi-place app surfaces (the focus-and-explain // Apps sheet, the + picker's places doorway, the space menu's directory) // while the machinery underneath stays live for experiments. Flip it // per-device from the console: localStorage.lchat_places_ui = "1" (remove or // set anything else to turn back off, then reload). With it off, tapping an // app in the + picker just opens/creates its personal copy, and a chat's // ⋮ → Apps only adds shared copies to THAT chat. See docs/space-apps.md // § The pullback. export const PLACES_UI = (() => { try { return localStorage.getItem("lchat_places_ui") === "1"; } catch { return false; } })(); export let session = null; // { token, name } — set at boot (embed or localStorage) export let roster = []; // [{user_id, label, public_key}] export let myUserId = ""; export let threads = []; // server Thread objects (id, kind, name_blob, members, wrapped) export let threadKeys = {}; // thread id -> CryptoKey export let activeId = null; // the open thread's id, or null export let ws = null; // the live WebSocket, or null between connects export let nicknames = {}; // uid -> the name I call them (per-viewer names, #577) export let threadName = {}; // thread id -> decrypted display name export let instanceKey = null; // the all-members AES key (profile photos), or null until granted export let instanceKeyB64 = null; export let threadKeyB64 = {}; // thread id -> raw key (base64url), to wrap for new members export let unread = {}; // thread id -> bool export let hidden = {}; // thread id -> bool: closed (hidden from the list), synced from the server export let dmClosed = {}; // thread id -> bool: DM ended by either party (server-enforced) export let pinned = {}; // thread id -> bool: pinned to the top of the list, synced from the server export let muted = {}; // thread id -> bool: muted (no notifications), synced from the server export let sealedBranding = null; // decrypted {name, description, admin_name, admin_email, avatar_alt, avatar_mime} export let brandingB = null; // the short branding key (lowercase base32) export let threadApp = {}; // thread id -> mini-app name, for a kind=="self" app-thread export let tiers = []; // /api/tiers rows, personalised to this user export let tierKeys = {}; // tier id -> CryptoKey export let tierKeyB64 = {}; // tier id -> raw base64url (kept to wrap for others) export let pendingJoinThread = null; // the DM a fresh invite created — the welcome conversation offers it at the end export const ACTIVE_KEY = "lchat_active"; // last-open thread id, so a refresh lands back in it export const MAX_UPLOAD = 25 * 1024 * 1024; // matches the default upload cap (main.go) export function setSession(v) { session = v; } export function setRoster(v) { roster = v; } export function setMyUserId(v) { myUserId = v; } export function setThreads(v) { threads = v; } export function setThreadKeys(v) { threadKeys = v; } export function setActiveId(v) { activeId = v; } export function setWs(v) { ws = v; } export function setTiers(v) { tiers = v; } export function setPendingJoinThread(v) { pendingJoinThread = v; } export function setSealedBranding(v) { sealedBranding = v; } export function setBrandingB(v) { brandingB = v; } export function setNicknames(v) { nicknames = v; } export function setThreadName(v) { threadName = v; } export function setInstanceKey(key, b64) { instanceKey = key; instanceKeyB64 = b64; }