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