openvibers/openvibe.live Commits
Offline channel fixes seen on the deployed page at 1280 and 1600px: the "While X is away" count sat in the heading's rule column and stretched into a full-width blue bar (grid auto-placement), and the rule dropped to a second row; every part now has its column. The count's text uses --on-accent. With one top card, the "Most watched" block narrows to it so the heading sits over the card. The channel tabs are one row that scrolls sideways instead of wrapping Analytics onto a row of its own. N-1 fixtures re-recorded from 14f1618, the release now in production.
Changes
@@@ -130,7 +130,11 @@transition: border-color .22s;}/* A section heading of the offline page (not a card): the rule under it runs the full width. */--.dsc-toggle::after { content: ''; grid-column: 3; height: 1px; align-self: center;++/* Explicit columns: auto-placement put the count into the 1fr rule column (a full-width blue bar) and++ pushed the rule onto a second row. */++.dsc-toggle-ico { grid-column: 1; } .dsc-toggle-text { grid-column: 2; }++.dsc-toggle-count { grid-column: 4; } .dsc-toggle-chev { grid-column: 5; }++.dsc-toggle::after { content: ''; grid-column: 3; grid-row: 1; height: 1px; align-self: center;background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 40%, transparent), transparent 85%); }.dsc-toggle:hover { border-bottom-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }.dsc-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }@@@ -146,7 +150,7 @@.dsc-toggle-text small { font-size: 0.72rem; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.dsc-toggle-count {flex: none; min-width: 26px; padding: 3px 8px; border-radius: 999px;-- font-size: 0.76rem; font-weight: 900; text-align: center; color: #fff;++ font-size: 0.76rem; font-weight: 900; text-align: center; color: var(--on-accent, #fff);background: var(--accent);box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 60%, transparent);}@@@ -167,6 +171,7 @@@media (max-width: 560px) {.dsc-toggle { gap: 9px; grid-template-columns: auto minmax(0, 1fr) auto auto; }.dsc-toggle::after { display: none; }++ .dsc-toggle-count { grid-column: 3; } .dsc-toggle-chev { grid-column: 4; }.dsc-toggle-ico { width: 29px; height: 29px; font-size: 0.74rem; }.dsc-toggle-text b { font-size: 0.83rem; }.dsc-toggle-text small { font-size: 0.68rem; }@@@ -988,6 +988,8 @@.off-cyc-arrow:hover { border-color: var(--accent); color: var(--text-primary); }.off-cyc-body { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }.off-cyc-body:has(.off-top-card:only-child) { grid-template-columns: minmax(0, 420px); justify-content: center; }++/* One card: the whole block narrows to it, so the "Most watched" heading sits over the card instead of far to its left. */++.off-cyc:has(.off-top-card:only-child) { max-width: calc(420px + 2 * clamp(10px, 3vw, 24px)); }.off-cyc-fade { animation: offCycFade .32s ease; }.off-top-card { display: flex; flex-direction: column; text-align: left; text-decoration: none; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; transition: border-color .15s, transform .15s, box-shadow .15s; }.off-top-card:hover { border-color: var(--accent); transform: translateY(-2px); box-shadow: var(--shadow, 0 6px 20px rgba(0,0,0,.3)); }@@@ -5314,13 +5314,15 @@ textarea.form-input { resize: vertical; min-height: 60px; }/* ── Channel below-fold tabs (Videos / Clips / Pastes / Analytics / About …) ── */.ch-tabs {-- display: flex; gap: 6px; flex-wrap: wrap; align-items: center;++ /* One row that scrolls sideways when it runs out of room, never a second row of one tab. */++ display: flex; gap: 2px; flex-wrap: nowrap; align-items: center; overflow-x: auto; scrollbar-width: none; overscroll-behavior-x: contain;border-bottom: 1px solid var(--border); margin: 4px 0 18px; padding-bottom: 0;}++.ch-tabs::-webkit-scrollbar { display: none; }.ch-tab {appearance: none; background: transparent; border: none; cursor: pointer;color: var(--text-muted); font-size: 0.9rem; font-weight: 650;-- padding: 10px 14px; border-radius: 8px 8px 0 0; position: relative;++ padding: 10px 12px; border-radius: 8px 8px 0 0; position: relative; flex: none; white-space: nowrap;display: inline-flex; align-items: center; gap: 7px;transition: color 0.15s ease, background 0.15s ease;}@@@ -5328,7 +5330,7 @@ textarea.form-input { resize: vertical; min-height: 60px; }.ch-tab:hover { color: var(--text-primary); background: color-mix(in srgb, var(--accent) 8%, transparent); }.ch-tab.active { color: var(--accent); }.ch-tab.active::after {-- content: ''; position: absolute; left: 8px; right: 8px; bottom: -1px; height: 2.5px;++ content: ''; position: absolute; left: 8px; right: 8px; bottom: 0; height: 2.5px;background: var(--accent); border-radius: 2px;}/* Pencil edit affordance riding on the About tab (streamer + allowed mods only). */@@@ -1,16 +1,16 @@{"service": "live",-- "release": "a825d8069fabbe79511df77a7f75e3568859ed4d",-- "recorded_at": "2026-09-28T23:50:44.753Z",++ "release": "14f16182e7c2eeeb5e4d542306c50467c5eca171",++ "recorded_at": "2026-09-29T00:16:55.917Z","note": "N-1 fixture: written by `npm run n-1:record`, replayed by test/n-1.test.js","manifest": {"service": "live",-- "release": "a825d8069fab",-- "released_at": "2026-09-28T23:50:03.000Z",-- "booted_at": "2026-09-28T23:50:41.722Z",++ "release": "14f16182e7c2",++ "released_at": "2026-09-29T00:16:18.000Z",++ "booted_at": "2026-09-29T00:16:53.417Z","contracts_version": "0.71.0","packages": {-- "openvibe-shared": "1.28.1",++ "openvibe-shared": "1.29.1","openvibe-sdk": "0.16.0"},"min_client_release": null,@@@ -18,15 +18,15 @@"components": {"styles": {"kind": "style",-- "version": "10e2fa18ee51"++ "version": "16efe97aaa50"},"shell": {"kind": "script",-- "version": "5233008c6e24"++ "version": "f43131c4e2d5"},"server": {"kind": "server",-- "version": "e94fb2f865f1"++ "version": "0a2a64444801"}},"assets": {@@@ -41,9 +41,9 @@"integrity": "sha384-+UNgBYuMRLHmTl0hBPmZI+91u+ud0yHLElwmd/4UrYgHoLHIYVElOwsOQ4G8UdWB"},"/css/context-cards.css": {-- "url": "/css/context-cards.css?v=60ad33ae3e48",++ "url": "/css/context-cards.css?v=997aed9c5db8","component": "styles",-- "integrity": "sha384-QafukgfMwvBUHfswsJWBwg8bmN1uOmnZxkxgcTcmRSWKV+PIQMCJp8gDNA+KZf4h"++ "integrity": "sha384-DnA7VkyZvjgI7fzkIQy7nTdyw6vr/hIBeNjkdAgu547ijqfc2CaB6prMbOzR81Jp"},"/css/cosmetics.css": {"url": "/css/cosmetics.css?v=193dbe9046fb",@@@ -61,9 +61,9 @@"integrity": "sha384-soU+Q4G+XY0bwjBFTyF4Q2YSOGKmH6joFz+2JFXRg9Fg4J17LYo9DYyoDR/ekWCj"},"/css/features/channel.css": {-- "url": "/css/features/channel.css?v=dfe3fa1b9949",++ "url": "/css/features/channel.css?v=2f0cf7c3ec13","component": "styles",-- "integrity": "sha384-YALxSITW32gKIdeBKV2ohJL2eQnhJFXoVFhKQyNanfa1IarVcdlIYuB50FMI8V04"++ "integrity": "sha384-0SIuauQ3Ziaf5eqN/Fzhp1FDRJZzXwFrHgSyVFKcn8n3ZDLwfaB9Hvme8k0qIixZ"},"/css/features/chatpage.css": {"url": "/css/features/chatpage.css?v=c5c63e94503f",@@@ -1093,6 +1093,7 @@"channels[].offline_screen_url": "null","channels[].offline_html": "null","channels[].offline_css": "null",++ "channels[].social_links": "null","channels[].moderation_settings": "object","channels[].moderation_settings.channel_id": "number","channels[].moderation_settings.slow_mode_seconds": "number",@@@ -3820,6 +3821,64 @@"public/js/app-search.js"]},++ {++ "method": "GET",++ "path": "/api/social/catalog",++ "auth": "anon",++ "status": 200,++ "kind": "json",++ "reads": {++ "platforms": "array",++ "platforms[]": "object",++ "platforms[].kind": "string",++ "platforms[].name": "string",++ "platforms[].icon": "string",++ "platforms[].color": "string",++ "platforms[].handle": "boolean"++ },++ "from": [++ "public/js/channel-socials.js"++ ]++ },++ {++ "method": "GET",++ "path": "/api/social/catalog",++ "auth": "user",++ "status": 200,++ "kind": "json",++ "reads": {++ "platforms": "array",++ "platforms[]": "object",++ "platforms[].kind": "string",++ "platforms[].name": "string",++ "platforms[].icon": "string",++ "platforms[].color": "string",++ "platforms[].handle": "boolean"++ },++ "from": [++ "public/js/channel-socials.js"++ ]++ },++ {++ "method": "GET",++ "path": "/api/social/preview/n1star/1",++ "auth": "anon",++ "status": 404,++ "kind": "json",++ "from": [++ "public/js/channel-socials.js"++ ]++ },++ {++ "method": "GET",++ "path": "/api/social/preview/n1star/1",++ "auth": "user",++ "status": 404,++ "kind": "json",++ "from": [++ "public/js/channel-socials.js"++ ]++ },{"method": "GET","path": "/api/streams",@@@ -3892,6 +3951,7 @@"streams[].channel.offline_screen_url": "null","streams[].channel.offline_html": "null","streams[].channel.offline_css": "null",++ "streams[].channel.social_links": "null","streams[].external_viewer_count": "number","streams[].total_viewer_count": "number"},@@@ -3971,6 +4031,7 @@"streams[].channel.offline_screen_url": "null","streams[].channel.offline_html": "null","streams[].channel.offline_css": "null",++ "streams[].channel.social_links": "null","streams[].external_viewer_count": "number","streams[].total_viewer_count": "number"},@@@ -4050,7 +4111,8 @@"offline_screen_type": "string","offline_screen_url": "null","offline_html": "null",-- "offline_css": "null"++ "offline_css": "null",++ "social_links": "null"},"from": ["public/js/dashboard.js",@@@ -4100,6 +4162,7 @@"channel.offline_screen_url": "null","channel.offline_html": "null","channel.offline_css": "null",++ "channel.social_links": "array","channel.username": "string","channel.display_name": "string","channel.avatar_url": "null",@@@ -4288,6 +4351,7 @@"channel.offline_screen_url": "null","channel.offline_html": "null","channel.offline_css": "null",++ "channel.social_links": "array","channel.username": "string","channel.display_name": "string","channel.avatar_url": "null",@@@ -4302,6 +4366,10 @@"channel.chat_limits.tts_max_length": "number","channel.mods_can_edit_about": "boolean","channel.viewer_can_edit_about": "boolean",++ "channel.social_links_editor": "object",++ "channel.social_links_editor.connected": "array",++ "channel.social_links_editor.restreams_without_link": "array",++ "channel.social_links_editor.hidden_auto": "array","channel.ai_overview": "null","language": "object","language.code": "string",@@@ -4476,6 +4544,7 @@"channel.offline_screen_url": "null","channel.offline_html": "null","channel.offline_css": "null",++ "channel.social_links": "array","channel.username": "string","channel.display_name": "string","channel.avatar_url": "null",@@@ -4664,6 +4733,7 @@"channel.offline_screen_url": "null","channel.offline_html": "null","channel.offline_css": "null",++ "channel.social_links": "array","channel.username": "string","channel.display_name": "string","channel.avatar_url": "null",@@@ -4678,6 +4748,10 @@"channel.chat_limits.tts_max_length": "number","channel.mods_can_edit_about": "boolean","channel.viewer_can_edit_about": "boolean",++ "channel.social_links_editor": "object",++ "channel.social_links_editor.connected": "array",++ "channel.social_links_editor.restreams_without_link": "array",++ "channel.social_links_editor.hidden_auto": "array","channel.ai_overview": "null","language": "object","language.code": "string",@@@ -4852,6 +4926,7 @@"channel.offline_screen_url": "null","channel.offline_html": "null","channel.offline_css": "null",++ "channel.social_links": "array","channel.username": "string","channel.display_name": "string","channel.avatar_url": "null",@@@ -5040,6 +5115,7 @@"channel.offline_screen_url": "null","channel.offline_html": "null","channel.offline_css": "null",++ "channel.social_links": "array","channel.username": "string","channel.display_name": "string","channel.avatar_url": "null",@@@ -5054,6 +5130,10 @@"channel.chat_limits.tts_max_length": "number","channel.mods_can_edit_about": "boolean","channel.viewer_can_edit_about": "boolean",++ "channel.social_links_editor": "object",++ "channel.social_links_editor.connected": "array",++ "channel.social_links_editor.restreams_without_link": "array",++ "channel.social_links_editor.hidden_auto": "array","channel.ai_overview": "null","language": "object","language.code": "string",@@@ -5328,6 +5408,7 @@"channel.offline_screen_url": "null","channel.offline_html": "null","channel.offline_css": "null",++ "channel.social_links": "array","channel.username": "string","channel.display_name": "string","channel.avatar_url": "null",@@@ -5516,6 +5597,7 @@"channel.offline_screen_url": "null","channel.offline_html": "null","channel.offline_css": "null",++ "channel.social_links": "array","channel.username": "string","channel.display_name": "string","channel.avatar_url": "null",@@@ -5530,6 +5612,10 @@"channel.chat_limits.tts_max_length": "number","channel.mods_can_edit_about": "boolean","channel.viewer_can_edit_about": "boolean",++ "channel.social_links_editor": "object",++ "channel.social_links_editor.connected": "array",++ "channel.social_links_editor.restreams_without_link": "array",++ "channel.social_links_editor.hidden_auto": "array","channel.ai_overview": "null","language": "object","language.code": "string",@@@ -5848,6 +5934,7 @@"channel.offline_screen_url": "null","channel.offline_html": "null","channel.offline_css": "null",++ "channel.social_links": "array","channel.username": "string","channel.display_name": "string","channel.avatar_url": "null",@@@ -6036,6 +6123,7 @@"channel.offline_screen_url": "null","channel.offline_html": "null","channel.offline_css": "null",++ "channel.social_links": "array","channel.username": "string","channel.display_name": "string","channel.avatar_url": "null",@@@ -6050,6 +6138,10 @@"channel.chat_limits.tts_max_length": "number","channel.mods_can_edit_about": "boolean","channel.viewer_can_edit_about": "boolean",++ "channel.social_links_editor": "object",++ "channel.social_links_editor.connected": "array",++ "channel.social_links_editor.restreams_without_link": "array",++ "channel.social_links_editor.hidden_auto": "array","channel.ai_overview": "null","language": "object","language.code": "string",@@@ -6657,6 +6749,7 @@"stream.channel.offline_screen_url": "null","stream.channel.offline_html": "null","stream.channel.offline_css": "null",++ "stream.channel.social_links": "null","stream.pip_overlay": "null"},"from": [@@@ -6735,6 +6828,7 @@"stream.channel.offline_screen_url": "null","stream.channel.offline_html": "null","stream.channel.offline_css": "null",++ "stream.channel.social_links": "null","stream.pip_overlay": "null"},"from": [@@@ -7387,7 +7481,7 @@},{"method": "GET",-- "path": "/shared/navbar.js?v=b3aef18429d0",++ "path": "/shared/navbar.js?v=6e398e3b31f5","auth": "anon","accept": "html","status": 200,@@@ -7508,7 +7602,7 @@},{"method": "GET",-- "path": "/shared/theme-loader.js?v=d9b9af081efe",++ "path": "/shared/theme-loader.js?v=ee4a6754d0f7","auth": "anon","accept": "html","status": 200,@@@ -7563,7 +7657,7 @@},{"method": "GET",-- "path": "/js/app-home.js?v=d88ec40a35c0",++ "path": "/js/app-home.js?v=4bbd63911072","auth": "anon","accept": "html","status": 200,@@@ -7605,17 +7699,6 @@"page /"]},-- {-- "method": "GET",-- "path": "/js/home-tour.js?v=a5ded0950973",-- "auth": "anon",-- "accept": "html",-- "status": 200,-- "kind": "other",-- "from": [-- "page /"-- ]-- },{"method": "GET","path": "/shared/tooltip.js?v=096488cf4948",@@@ -7629,7 +7712,7 @@},{"method": "GET",-- "path": "/css/features/channel.css?v=dfe3fa1b9949",++ "path": "/css/features/channel.css?v=2f0cf7c3ec13","auth": "anon","accept": "html","status": 200,@@@ -7640,7 +7723,7 @@},{"method": "GET",-- "path": "/css/context-cards.css?v=60ad33ae3e48",++ "path": "/css/context-cards.css?v=997aed9c5db8","auth": "anon","accept": "html","status": 200,@@@ -7695,7 +7778,7 @@},{"method": "GET",-- "path": "/js/context-cards.js?v=6719bbc1c432",++ "path": "/js/context-cards.js?v=edb4f671e62d","auth": "anon","accept": "html","status": 200,@@@ -7750,7 +7833,18 @@},{"method": "GET",-- "path": "/js/app-channel.js?v=1c0b1a8e0649",++ "path": "/js/channel-socials.js?v=3c59ca14bf8b",++ "auth": "anon",++ "accept": "html",++ "status": 200,++ "kind": "other",++ "from": [++ "page /@n1star"++ ]++ },++ {++ "method": "GET",++ "path": "/js/app-channel.js?v=fb9e1528dba8","auth": "anon","accept": "html","status": 200,@@@ -1,7 +1,7 @@{"service": "live",-- "release": "a825d8069fabbe79511df77a7f75e3568859ed4d",-- "recorded_at": "2026-09-28T23:50:44.753Z",++ "release": "14f16182e7c2eeeb5e4d542306c50467c5eca171",++ "recorded_at": "2026-09-29T00:16:55.917Z","note": "N-1 fixture: written by `npm run n-1:record`, replayed by test/n-1.test.js","user_version": 0,"schema": [@@@ -33,7 +33,7 @@"CREATE TABLE channel_points (\n user_id INTEGER NOT NULL,\n streamer_id INTEGER NOT NULL,\n balance INTEGER NOT NULL DEFAULT 0,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (user_id, streamer_id)\n )","CREATE TABLE channel_points_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n idempotency_key TEXT NOT NULL UNIQUE,\n user_id INTEGER NOT NULL,\n streamer_id INTEGER NOT NULL,\n delta INTEGER NOT NULL,\n reason TEXT,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n )","CREATE TABLE channel_sounds (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n channel_owner_id INTEGER NOT NULL, -- streamer whose channel this sound belongs to\n command TEXT NOT NULL, -- trigger word (without leading '!'), lowercased\n url TEXT NOT NULL, -- served path of the audio file on disk\n mime TEXT DEFAULT 'audio/mpeg',\n duration_seconds REAL DEFAULT 0,\n created_by INTEGER, -- uploader user id (NULL if removed user)\n created_by_name TEXT DEFAULT '',\n is_approved INTEGER DEFAULT 1,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP, emote_code TEXT DEFAULT '',\n -- Multiple sounds may share a command; playback picks one at random.\n FOREIGN KEY (channel_owner_id) REFERENCES users(id) ON DELETE CASCADE\n)",-- "CREATE TABLE channels (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n user_id INTEGER NOT NULL UNIQUE,\n title TEXT DEFAULT 'Untitled Channel',\n description TEXT DEFAULT '',\n category TEXT DEFAULT 'irl',\n tags TEXT DEFAULT '[]',\n protocol TEXT DEFAULT 'webrtc' CHECK(protocol IN ('jsmpeg', 'webrtc', 'rtmp')),\n is_nsfw INTEGER DEFAULT 0,\n auto_record INTEGER DEFAULT 0,\n offline_banner_url TEXT,\n panels TEXT DEFAULT '[]', -- JSON array of info panels\n emote_sources TEXT DEFAULT '{\"defaults\":true,\"custom\":true,\"ffz\":true,\"bttv\":true,\"7tv\":true}',\n default_vod_visibility TEXT DEFAULT 'public' CHECK(default_vod_visibility IN ('public', 'unlisted', 'private')),\n default_clip_visibility TEXT DEFAULT 'public' CHECK(default_clip_visibility IN ('public', 'unlisted', 'private')),\n vod_recording_enabled INTEGER DEFAULT 1,\n force_vod_recording_disabled INTEGER DEFAULT 0,\n weather_zip TEXT DEFAULT NULL,\n weather_detail TEXT DEFAULT 'basic' CHECK(weather_detail IN ('off', 'basic', 'hourly', 'detailed')),\n weather_show_location INTEGER DEFAULT 0,\n control_mode TEXT DEFAULT 'open' CHECK(control_mode IN ('open','whitelist','disabled')),\n anon_controls_enabled INTEGER DEFAULT 1,\n control_rate_limit_ms INTEGER DEFAULT 100,\n video_click_enabled INTEGER DEFAULT 0,\n video_click_rate_limit_ms INTEGER DEFAULT 0,\n active_control_config_id INTEGER,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, chat_language TEXT DEFAULT 'auto', cp_name TEXT DEFAULT 'Channel Points', cp_icon TEXT DEFAULT 'fa-coins', cp_watch_interval_min INTEGER DEFAULT 5, cp_watch_amount INTEGER DEFAULT 10, cp_game_interval_min INTEGER DEFAULT 0, clips_allow_creator_delete INTEGER DEFAULT 0, hide_ai_overview INTEGER DEFAULT 0, ai_overview_pref TEXT DEFAULT 'auto', ai_derivation_enabled INTEGER DEFAULT 1, ai_category TEXT, force_nsfw INTEGER DEFAULT 0, offline_screen_type TEXT DEFAULT 'none', offline_screen_url TEXT, offline_html TEXT, offline_css TEXT,\n FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE\n)",++ "CREATE TABLE channels (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n user_id INTEGER NOT NULL UNIQUE,\n title TEXT DEFAULT 'Untitled Channel',\n description TEXT DEFAULT '',\n category TEXT DEFAULT 'irl',\n tags TEXT DEFAULT '[]',\n protocol TEXT DEFAULT 'webrtc' CHECK(protocol IN ('jsmpeg', 'webrtc', 'rtmp')),\n is_nsfw INTEGER DEFAULT 0,\n auto_record INTEGER DEFAULT 0,\n offline_banner_url TEXT,\n panels TEXT DEFAULT '[]', -- JSON array of info panels\n emote_sources TEXT DEFAULT '{\"defaults\":true,\"custom\":true,\"ffz\":true,\"bttv\":true,\"7tv\":true}',\n default_vod_visibility TEXT DEFAULT 'public' CHECK(default_vod_visibility IN ('public', 'unlisted', 'private')),\n default_clip_visibility TEXT DEFAULT 'public' CHECK(default_clip_visibility IN ('public', 'unlisted', 'private')),\n vod_recording_enabled INTEGER DEFAULT 1,\n force_vod_recording_disabled INTEGER DEFAULT 0,\n weather_zip TEXT DEFAULT NULL,\n weather_detail TEXT DEFAULT 'basic' CHECK(weather_detail IN ('off', 'basic', 'hourly', 'detailed')),\n weather_show_location INTEGER DEFAULT 0,\n control_mode TEXT DEFAULT 'open' CHECK(control_mode IN ('open','whitelist','disabled')),\n anon_controls_enabled INTEGER DEFAULT 1,\n control_rate_limit_ms INTEGER DEFAULT 100,\n video_click_enabled INTEGER DEFAULT 0,\n video_click_rate_limit_ms INTEGER DEFAULT 0,\n active_control_config_id INTEGER,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, chat_language TEXT DEFAULT 'auto', cp_name TEXT DEFAULT 'Channel Points', cp_icon TEXT DEFAULT 'fa-coins', cp_watch_interval_min INTEGER DEFAULT 5, cp_watch_amount INTEGER DEFAULT 10, cp_game_interval_min INTEGER DEFAULT 0, clips_allow_creator_delete INTEGER DEFAULT 0, hide_ai_overview INTEGER DEFAULT 0, ai_overview_pref TEXT DEFAULT 'auto', ai_derivation_enabled INTEGER DEFAULT 1, ai_category TEXT, force_nsfw INTEGER DEFAULT 0, offline_screen_type TEXT DEFAULT 'none', offline_screen_url TEXT, offline_html TEXT, offline_css TEXT, social_links TEXT,\n FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE\n)","CREATE TABLE chat_ai_summaries (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n scope TEXT NOT NULL,\n subject_id INTEGER NOT NULL DEFAULT 0,\n window TEXT NOT NULL,\n overview TEXT DEFAULT '',\n memory_json TEXT DEFAULT '',\n timeline_json TEXT DEFAULT '[]',\n message_count INTEGER DEFAULT 0,\n window_message_count INTEGER DEFAULT 0,\n last_message_id INTEGER DEFAULT 0,\n window_label TEXT DEFAULT '',\n window_start DATETIME,\n window_end DATETIME,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n UNIQUE(scope, subject_id, window)\n )","CREATE TABLE \"chat_messages\" (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n stream_id INTEGER,\n user_id INTEGER, -- NULL for anon\n anon_id TEXT, -- 'anon12345' format\n username TEXT,\n message TEXT NOT NULL,\n message_type TEXT DEFAULT 'chat' CHECK(message_type IN ('chat', 'system', 'donation', 'command', 'tts', 'channel-sound', 'soundboard', 'clip')),\n metadata TEXT, -- JSON sidecar for rich events (donation/goal-reached)\n is_global INTEGER DEFAULT 0,\n is_deleted INTEGER DEFAULT 0,\n is_filtered INTEGER DEFAULT 0,\n reply_to_id INTEGER REFERENCES chat_messages(id) ON DELETE SET NULL,\n source_platform TEXT,\n deleted_by INTEGER,\n deleted_at DATETIME,\n auto_delete_at DATETIME,\n timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, channel_user_id INTEGER,\n FOREIGN KEY (stream_id) REFERENCES streams(id) ON DELETE CASCADE,\n FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL\n)","CREATE TABLE chat_timeline_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n scope TEXT NOT NULL DEFAULT 'global',\n subject_id INTEGER NOT NULL DEFAULT 0,\n ts DATETIME NOT NULL,\n label TEXT NOT NULL,\n detail TEXT DEFAULT '',\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n )",@@@ -257,19 +257,19 @@"schema_migrations": [{"id": "001_vibes_decimal_to_bits",-- "applied_at": "2026-09-28 23:50:40",++ "applied_at": "2026-09-29 00:16:52","mode": "adopted","duration_ms": 0},{"id": "002_pastes_ai_columns",-- "applied_at": "2026-09-28 23:50:40",++ "applied_at": "2026-09-29 00:16:52","mode": "applied",-- "duration_ms": 5++ "duration_ms": 7},{"id": "004_hot_path_indexes",-- "applied_at": "2026-09-28 23:50:40",++ "applied_at": "2026-09-29 00:16:52","mode": "applied","duration_ms": 1}@@@ -826,6 +826,7 @@"SELECT COUNT(DISTINCT user_id) AS count FROM streams WHERE user_id IS NOT NULL AND created_at >= datetime('now', ?) AND created_at < datetime('now', ?)","SELECT DISTINCT e.user_id FROM stream_timeline_events e JOIN users u ON u.id = e.user_id WHERE e.kind = 'speech' AND e.user_id IS NOT NULL AND e.created_at >= datetime('now', ?) AND COALESCE(u.is_banned, 0) = 0","SELECT DISTINCT il.user_id, u.role FROM ip_log il JOIN users u ON u.id = il.user_id WHERE il.ip_address = ? AND il.user_id IS NOT NULL",++ "SELECT DISTINCT platform FROM restream_destinations WHERE user_id = ? AND platform IN ('twitch', 'youtube', 'kick')","SELECT DISTINCT rd.user_id FROM restream_destinations rd WHERE rd.managed_stream_id IS NULL","SELECT DISTINCT s.user_id, u.stream_key, u.username, u.display_name, c.id AS channel_id, c.title AS channel_title, c.protocol AS channel_protocol, c.category AS channel_category, c.is_nsfw AS channel_is_nsfw FROM streams s JOIN users u ON s.user_id = u.id LEFT JOIN channels c ON c.user_id = s.user_id WHERE s.user_id NOT IN (SELECT user_id FROM managed_streams) AND u.stream_key IS NOT NULL","SELECT DISTINCT title FROM streams WHERE user_id = ? AND duration_seconds > 0 ORDER BY started_at DESC LIMIT 8",@@@ -994,6 +995,7 @@"SELECT payload, generated_at FROM ai_timeline_cache WHERE user_id = ?","SELECT payload_json, created_at FROM vibe_coding_events WHERE managed_stream_id = ? ORDER BY id DESC LIMIT ?","SELECT persona_json FROM arena_profiles WHERE user_id = ?",++ "SELECT platform, platform_username, channel_url FROM platform_connections WHERE user_id = ?","SELECT powerchat_count_views FROM restream_destinations WHERE id = ?","SELECT powerchat_relay FROM restream_destinations WHERE id = ?","SELECT powerchat_username FROM powerchat_connections WHERE user_id = ?",
@@ -130,7 +130,11 @@
@@ -130,7 +130,11 @@
transition: border-color .22s;
transition: border-color .22s;
}
}
/* A section heading of the offline page (not a card): the rule under it runs the full width. */
/* A section heading of the offline page (not a card): the rule under it runs the full width. */
-.dsc-toggle::after { content: ''; grid-column: 3; height: 1px; align-self: center;
+/* Explicit columns: auto-placement put the count into the 1fr rule column (a full-width blue bar) and
+ pushed the rule onto a second row. */
+.dsc-toggle-ico { grid-column: 1; } .dsc-toggle-text { grid-column: 2; }
+.dsc-toggle-count { grid-column: 4; } .dsc-toggle-chev { grid-column: 5; }
+.dsc-toggle::after { content: ''; grid-column: 3; grid-row: 1; height: 1px; align-self: center;
background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 40%, transparent), transparent 85%); }
background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 40%, transparent), transparent 85%); }
.dsc-toggle:hover { border-bottom-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
.dsc-toggle:hover { border-bottom-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
.dsc-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }
.dsc-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }
@@ -146,7 +150,7 @@
@@ -146,7 +150,7 @@
.dsc-toggle-text small { font-size: 0.72rem; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dsc-toggle-text small { font-size: 0.72rem; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dsc-toggle-count {
.dsc-toggle-count {
flex: none; min-width: 26px; padding: 3px 8px; border-radius: 999px;
flex: none; min-width: 26px; padding: 3px 8px; border-radius: 999px;
- font-size: 0.76rem; font-weight: 900; text-align: center; color: #fff;
+ font-size: 0.76rem; font-weight: 900; text-align: center; color: var(--on-accent, #fff);
background: var(--accent);
background: var(--accent);
box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 60%, transparent);
box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 60%, transparent);
}
}
@@ -167,6 +171,7 @@
@@ -167,6 +171,7 @@
@media (max-width: 560px) {
@media (max-width: 560px) {
.dsc-toggle { gap: 9px; grid-template-columns: auto minmax(0, 1fr) auto auto; }
.dsc-toggle { gap: 9px; grid-template-columns: auto minmax(0, 1fr) auto auto; }
.dsc-toggle::after { display: none; }
.dsc-toggle::after { display: none; }
+ .dsc-toggle-count { grid-column: 3; } .dsc-toggle-chev { grid-column: 4; }
.dsc-toggle-ico { width: 29px; height: 29px; font-size: 0.74rem; }
.dsc-toggle-ico { width: 29px; height: 29px; font-size: 0.74rem; }
.dsc-toggle-text b { font-size: 0.83rem; }
.dsc-toggle-text b { font-size: 0.83rem; }
.dsc-toggle-text small { font-size: 0.68rem; }
.dsc-toggle-text small { font-size: 0.68rem; }
@@ -988,6 +988,8 @@
@@ -988,6 +988,8 @@
.off-cyc-arrow:hover { border-color: var(--accent); color: var(--text-primary); }
.off-cyc-arrow:hover { border-color: var(--accent); color: var(--text-primary); }
.off-cyc-body { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.off-cyc-body { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.off-cyc-body:has(.off-top-card:only-child) { grid-template-columns: minmax(0, 420px); justify-content: center; }
.off-cyc-body:has(.off-top-card:only-child) { grid-template-columns: minmax(0, 420px); justify-content: center; }
+/* One card: the whole block narrows to it, so the "Most watched" heading sits over the card instead of far to its left. */
+.off-cyc:has(.off-top-card:only-child) { max-width: calc(420px + 2 * clamp(10px, 3vw, 24px)); }
.off-cyc-fade { animation: offCycFade .32s ease; }
.off-cyc-fade { animation: offCycFade .32s ease; }
.off-top-card { display: flex; flex-direction: column; text-align: left; text-decoration: none; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; transition: border-color .15s, transform .15s, box-shadow .15s; }
.off-top-card { display: flex; flex-direction: column; text-align: left; text-decoration: none; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; transition: border-color .15s, transform .15s, box-shadow .15s; }
.off-top-card:hover { border-color: var(--accent); transform: translateY(-2px); box-shadow: var(--shadow, 0 6px 20px rgba(0,0,0,.3)); }
.off-top-card:hover { border-color: var(--accent); transform: translateY(-2px); box-shadow: var(--shadow, 0 6px 20px rgba(0,0,0,.3)); }
@@ -5314,13 +5314,15 @@ textarea.form-input { resize: vertical; min-height: 60px; }
@@ -5314,13 +5314,15 @@ textarea.form-input { resize: vertical; min-height: 60px; }
/* ── Channel below-fold tabs (Videos / Clips / Pastes / Analytics / About …) ── */
/* ── Channel below-fold tabs (Videos / Clips / Pastes / Analytics / About …) ── */
.ch-tabs {
.ch-tabs {
- display: flex; gap: 6px; flex-wrap: wrap; align-items: center;
+ /* One row that scrolls sideways when it runs out of room, never a second row of one tab. */
+ display: flex; gap: 2px; flex-wrap: nowrap; align-items: center; overflow-x: auto; scrollbar-width: none; overscroll-behavior-x: contain;
border-bottom: 1px solid var(--border); margin: 4px 0 18px; padding-bottom: 0;
border-bottom: 1px solid var(--border); margin: 4px 0 18px; padding-bottom: 0;
}
}
+.ch-tabs::-webkit-scrollbar { display: none; }
.ch-tab {
.ch-tab {
appearance: none; background: transparent; border: none; cursor: pointer;
appearance: none; background: transparent; border: none; cursor: pointer;
color: var(--text-muted); font-size: 0.9rem; font-weight: 650;
color: var(--text-muted); font-size: 0.9rem; font-weight: 650;
- padding: 10px 14px; border-radius: 8px 8px 0 0; position: relative;
+ padding: 10px 12px; border-radius: 8px 8px 0 0; position: relative; flex: none; white-space: nowrap;
display: inline-flex; align-items: center; gap: 7px;
display: inline-flex; align-items: center; gap: 7px;
transition: color 0.15s ease, background 0.15s ease;
transition: color 0.15s ease, background 0.15s ease;
}
}
@@ -5328,7 +5330,7 @@ textarea.form-input { resize: vertical; min-height: 60px; }
@@ -5328,7 +5330,7 @@ textarea.form-input { resize: vertical; min-height: 60px; }
.ch-tab:hover { color: var(--text-primary); background: color-mix(in srgb, var(--accent) 8%, transparent); }
.ch-tab:hover { color: var(--text-primary); background: color-mix(in srgb, var(--accent) 8%, transparent); }
.ch-tab.active { color: var(--accent); }
.ch-tab.active { color: var(--accent); }
.ch-tab.active::after {
.ch-tab.active::after {
- content: ''; position: absolute; left: 8px; right: 8px; bottom: -1px; height: 2.5px;
+ content: ''; position: absolute; left: 8px; right: 8px; bottom: 0; height: 2.5px;
background: var(--accent); border-radius: 2px;
background: var(--accent); border-radius: 2px;
}
}
/* Pencil edit affordance riding on the About tab (streamer + allowed mods only). */
/* Pencil edit affordance riding on the About tab (streamer + allowed mods only). */
@@ -1,16 +1,16 @@
@@ -1,16 +1,16 @@
{
{
"service": "live",
"service": "live",
- "release": "a825d8069fabbe79511df77a7f75e3568859ed4d",
- "recorded_at": "2026-09-28T23:50:44.753Z",
+ "release": "14f16182e7c2eeeb5e4d542306c50467c5eca171",
+ "recorded_at": "2026-09-29T00:16:55.917Z",
"note": "N-1 fixture: written by `npm run n-1:record`, replayed by test/n-1.test.js",
"note": "N-1 fixture: written by `npm run n-1:record`, replayed by test/n-1.test.js",
"manifest": {
"manifest": {
"service": "live",
"service": "live",
- "release": "a825d8069fab",
- "released_at": "2026-09-28T23:50:03.000Z",
- "booted_at": "2026-09-28T23:50:41.722Z",
+ "release": "14f16182e7c2",
+ "released_at": "2026-09-29T00:16:18.000Z",
+ "booted_at": "2026-09-29T00:16:53.417Z",
"contracts_version": "0.71.0",
"contracts_version": "0.71.0",
"packages": {
"packages": {
- "openvibe-shared": "1.28.1",
+ "openvibe-shared": "1.29.1",
"openvibe-sdk": "0.16.0"
"openvibe-sdk": "0.16.0"
},
},
"min_client_release": null,
"min_client_release": null,
@@ -18,15 +18,15 @@
@@ -18,15 +18,15 @@
"components": {
"components": {
"styles": {
"styles": {
"kind": "style",
"kind": "style",
- "version": "10e2fa18ee51"
+ "version": "16efe97aaa50"
},
},
"shell": {
"shell": {
"kind": "script",
"kind": "script",
- "version": "5233008c6e24"
+ "version": "f43131c4e2d5"
},
},
"server": {
"server": {
"kind": "server",
"kind": "server",
- "version": "e94fb2f865f1"
+ "version": "0a2a64444801"
}
}
},
},
"assets": {
"assets": {
@@ -41,9 +41,9 @@
@@ -41,9 +41,9 @@
"integrity": "sha384-+UNgBYuMRLHmTl0hBPmZI+91u+ud0yHLElwmd/4UrYgHoLHIYVElOwsOQ4G8UdWB"
"integrity": "sha384-+UNgBYuMRLHmTl0hBPmZI+91u+ud0yHLElwmd/4UrYgHoLHIYVElOwsOQ4G8UdWB"
},
},
"/css/context-cards.css": {
"/css/context-cards.css": {
- "url": "/css/context-cards.css?v=60ad33ae3e48",
+ "url": "/css/context-cards.css?v=997aed9c5db8",
"component": "styles",
"component": "styles",
- "integrity": "sha384-QafukgfMwvBUHfswsJWBwg8bmN1uOmnZxkxgcTcmRSWKV+PIQMCJp8gDNA+KZf4h"
+ "integrity": "sha384-DnA7VkyZvjgI7fzkIQy7nTdyw6vr/hIBeNjkdAgu547ijqfc2CaB6prMbOzR81Jp"
},
},
"/css/cosmetics.css": {
"/css/cosmetics.css": {
"url": "/css/cosmetics.css?v=193dbe9046fb",
"url": "/css/cosmetics.css?v=193dbe9046fb",
@@ -61,9 +61,9 @@
@@ -61,9 +61,9 @@
"integrity": "sha384-soU+Q4G+XY0bwjBFTyF4Q2YSOGKmH6joFz+2JFXRg9Fg4J17LYo9DYyoDR/ekWCj"
"integrity": "sha384-soU+Q4G+XY0bwjBFTyF4Q2YSOGKmH6joFz+2JFXRg9Fg4J17LYo9DYyoDR/ekWCj"
},
},
"/css/features/channel.css": {
"/css/features/channel.css": {
- "url": "/css/features/channel.css?v=dfe3fa1b9949",
+ "url": "/css/features/channel.css?v=2f0cf7c3ec13",
"component": "styles",
"component": "styles",
- "integrity": "sha384-YALxSITW32gKIdeBKV2ohJL2eQnhJFXoVFhKQyNanfa1IarVcdlIYuB50FMI8V04"
+ "integrity": "sha384-0SIuauQ3Ziaf5eqN/Fzhp1FDRJZzXwFrHgSyVFKcn8n3ZDLwfaB9Hvme8k0qIixZ"
},
},
"/css/features/chatpage.css": {
"/css/features/chatpage.css": {
"url": "/css/features/chatpage.css?v=c5c63e94503f",
"url": "/css/features/chatpage.css?v=c5c63e94503f",
@@ -1093,6 +1093,7 @@
@@ -1093,6 +1093,7 @@
"channels[].offline_screen_url": "null",
"channels[].offline_screen_url": "null",
"channels[].offline_html": "null",
"channels[].offline_html": "null",
"channels[].offline_css": "null",
"channels[].offline_css": "null",
+ "channels[].social_links": "null",
"channels[].moderation_settings": "object",
"channels[].moderation_settings": "object",
"channels[].moderation_settings.channel_id": "number",
"channels[].moderation_settings.channel_id": "number",
"channels[].moderation_settings.slow_mode_seconds": "number",
"channels[].moderation_settings.slow_mode_seconds": "number",
@@ -3820,6 +3821,64 @@
@@ -3820,6 +3821,64 @@
"public/js/app-search.js"
"public/js/app-search.js"
]
]
},
},
+ {
+ "method": "GET",
+ "path": "/api/social/catalog",
+ "auth": "anon",
+ "status": 200,
+ "kind": "json",
+ "reads": {
+ "platforms": "array",
+ "platforms[]": "object",
+ "platforms[].kind": "string",
+ "platforms[].name": "string",
+ "platforms[].icon": "string",
+ "platforms[].color": "string",
+ "platforms[].handle": "boolean"
+ },
+ "from": [
+ "public/js/channel-socials.js"
+ ]
+ },
+ {
+ "method": "GET",
+ "path": "/api/social/catalog",
+ "auth": "user",
+ "status": 200,
+ "kind": "json",
+ "reads": {
+ "platforms": "array",
+ "platforms[]": "object",
+ "platforms[].kind": "string",
+ "platforms[].name": "string",
+ "platforms[].icon": "string",
+ "platforms[].color": "string",
+ "platforms[].handle": "boolean"
+ },
+ "from": [
+ "public/js/channel-socials.js"
+ ]
+ },
+ {
+ "method": "GET",
+ "path": "/api/social/preview/n1star/1",
+ "auth": "anon",
+ "status": 404,
+ "kind": "json",
+ "from": [
+ "public/js/channel-socials.js"
+ ]
+ },
+ {
+ "method": "GET",
+ "path": "/api/social/preview/n1star/1",
+ "auth": "user",
+ "status": 404,
+ "kind": "json",
+ "from": [
+ "public/js/channel-socials.js"
+ ]
+ },
{
{
"method": "GET",
"method": "GET",
"path": "/api/streams",
"path": "/api/streams",
@@ -3892,6 +3951,7 @@
@@ -3892,6 +3951,7 @@
"streams[].channel.offline_screen_url": "null",
"streams[].channel.offline_screen_url": "null",
"streams[].channel.offline_html": "null",
"streams[].channel.offline_html": "null",
"streams[].channel.offline_css": "null",
"streams[].channel.offline_css": "null",
+ "streams[].channel.social_links": "null",
"streams[].external_viewer_count": "number",
"streams[].external_viewer_count": "number",
"streams[].total_viewer_count": "number"
"streams[].total_viewer_count": "number"
},
},
@@ -3971,6 +4031,7 @@
@@ -3971,6 +4031,7 @@
"streams[].channel.offline_screen_url": "null",
"streams[].channel.offline_screen_url": "null",
"streams[].channel.offline_html": "null",
"streams[].channel.offline_html": "null",
"streams[].channel.offline_css": "null",
"streams[].channel.offline_css": "null",
+ "streams[].channel.social_links": "null",
"streams[].external_viewer_count": "number",
"streams[].external_viewer_count": "number",
"streams[].total_viewer_count": "number"
"streams[].total_viewer_count": "number"
},
},
@@ -4050,7 +4111,8 @@
@@ -4050,7 +4111,8 @@
"offline_screen_type": "string",
"offline_screen_type": "string",
"offline_screen_url": "null",
"offline_screen_url": "null",
"offline_html": "null",
"offline_html": "null",
- "offline_css": "null"
+ "offline_css": "null",
+ "social_links": "null"
},
},
"from": [
"from": [
"public/js/dashboard.js",
"public/js/dashboard.js",
@@ -4100,6 +4162,7 @@
@@ -4100,6 +4162,7 @@
"channel.offline_screen_url": "null",
"channel.offline_screen_url": "null",
"channel.offline_html": "null",
"channel.offline_html": "null",
"channel.offline_css": "null",
"channel.offline_css": "null",
+ "channel.social_links": "array",
"channel.username": "string",
"channel.username": "string",
"channel.display_name": "string",
"channel.display_name": "string",
"channel.avatar_url": "null",
"channel.avatar_url": "null",
@@ -4288,6 +4351,7 @@
@@ -4288,6 +4351,7 @@
"channel.offline_screen_url": "null",
"channel.offline_screen_url": "null",
"channel.offline_html": "null",
"channel.offline_html": "null",
"channel.offline_css": "null",
"channel.offline_css": "null",
+ "channel.social_links": "array",
"channel.username": "string",
"channel.username": "string",
"channel.display_name": "string",
"channel.display_name": "string",
"channel.avatar_url": "null",
"channel.avatar_url": "null",
@@ -4302,6 +4366,10 @@
@@ -4302,6 +4366,10 @@
"channel.chat_limits.tts_max_length": "number",
"channel.chat_limits.tts_max_length": "number",
"channel.mods_can_edit_about": "boolean",
"channel.mods_can_edit_about": "boolean",
"channel.viewer_can_edit_about": "boolean",
"channel.viewer_can_edit_about": "boolean",
+ "channel.social_links_editor": "object",
+ "channel.social_links_editor.connected": "array",
+ "channel.social_links_editor.restreams_without_link": "array",
+ "channel.social_links_editor.hidden_auto": "array",
"channel.ai_overview": "null",
"channel.ai_overview": "null",
"language": "object",
"language": "object",
"language.code": "string",
"language.code": "string",
@@ -4476,6 +4544,7 @@
@@ -4476,6 +4544,7 @@
"channel.offline_screen_url": "null",
"channel.offline_screen_url": "null",
"channel.offline_html": "null",
"channel.offline_html": "null",
"channel.offline_css": "null",
"channel.offline_css": "null",
+ "channel.social_links": "array",
"channel.username": "string",
"channel.username": "string",
"channel.display_name": "string",
"channel.display_name": "string",
"channel.avatar_url": "null",
"channel.avatar_url": "null",
@@ -4664,6 +4733,7 @@
@@ -4664,6 +4733,7 @@
"channel.offline_screen_url": "null",
"channel.offline_screen_url": "null",
"channel.offline_html": "null",
"channel.offline_html": "null",
"channel.offline_css": "null",
"channel.offline_css": "null",
+ "channel.social_links": "array",
"channel.username": "string",
"channel.username": "string",
"channel.display_name": "string",
"channel.display_name": "string",
"channel.avatar_url": "null",
"channel.avatar_url": "null",
@@ -4678,6 +4748,10 @@
@@ -4678,6 +4748,10 @@
"channel.chat_limits.tts_max_length": "number",
"channel.chat_limits.tts_max_length": "number",
"channel.mods_can_edit_about": "boolean",
"channel.mods_can_edit_about": "boolean",
"channel.viewer_can_edit_about": "boolean",
"channel.viewer_can_edit_about": "boolean",
+ "channel.social_links_editor": "object",
+ "channel.social_links_editor.connected": "array",
+ "channel.social_links_editor.restreams_without_link": "array",
+ "channel.social_links_editor.hidden_auto": "array",
"channel.ai_overview": "null",
"channel.ai_overview": "null",
"language": "object",
"language": "object",
"language.code": "string",
"language.code": "string",
@@ -4852,6 +4926,7 @@
@@ -4852,6 +4926,7 @@
"channel.offline_screen_url": "null",
"channel.offline_screen_url": "null",
"channel.offline_html": "null",
"channel.offline_html": "null",
"channel.offline_css": "null",
"channel.offline_css": "null",
+ "channel.social_links": "array",
"channel.username": "string",
"channel.username": "string",
"channel.display_name": "string",
"channel.display_name": "string",
"channel.avatar_url": "null",
"channel.avatar_url": "null",
@@ -5040,6 +5115,7 @@
@@ -5040,6 +5115,7 @@
"channel.offline_screen_url": "null",
"channel.offline_screen_url": "null",
"channel.offline_html": "null",
"channel.offline_html": "null",
"channel.offline_css": "null",
"channel.offline_css": "null",
+ "channel.social_links": "array",
"channel.username": "string",
"channel.username": "string",
"channel.display_name": "string",
"channel.display_name": "string",
"channel.avatar_url": "null",
"channel.avatar_url": "null",
@@ -5054,6 +5130,10 @@
@@ -5054,6 +5130,10 @@
"channel.chat_limits.tts_max_length": "number",
"channel.chat_limits.tts_max_length": "number",
"channel.mods_can_edit_about": "boolean",
"channel.mods_can_edit_about": "boolean",
"channel.viewer_can_edit_about": "boolean",
"channel.viewer_can_edit_about": "boolean",
+ "channel.social_links_editor": "object",
+ "channel.social_links_editor.connected": "array",
+ "channel.social_links_editor.restreams_without_link": "array",
+ "channel.social_links_editor.hidden_auto": "array",
"channel.ai_overview": "null",
"channel.ai_overview": "null",
"language": "object",
"language": "object",
"language.code": "string",
"language.code": "string",
@@ -5328,6 +5408,7 @@
@@ -5328,6 +5408,7 @@
"channel.offline_screen_url": "null",
"channel.offline_screen_url": "null",
"channel.offline_html": "null",
"channel.offline_html": "null",
"channel.offline_css": "null",
"channel.offline_css": "null",
+ "channel.social_links": "array",
"channel.username": "string",
"channel.username": "string",
"channel.display_name": "string",
"channel.display_name": "string",
"channel.avatar_url": "null",
"channel.avatar_url": "null",
@@ -5516,6 +5597,7 @@
@@ -5516,6 +5597,7 @@
"channel.offline_screen_url": "null",
"channel.offline_screen_url": "null",
"channel.offline_html": "null",
"channel.offline_html": "null",
"channel.offline_css": "null",
"channel.offline_css": "null",
+ "channel.social_links": "array",
"channel.username": "string",
"channel.username": "string",
"channel.display_name": "string",
"channel.display_name": "string",
"channel.avatar_url": "null",
"channel.avatar_url": "null",
@@ -5530,6 +5612,10 @@
@@ -5530,6 +5612,10 @@
"channel.chat_limits.tts_max_length": "number",
"channel.chat_limits.tts_max_length": "number",
"channel.mods_can_edit_about": "boolean",
"channel.mods_can_edit_about": "boolean",
"channel.viewer_can_edit_about": "boolean",
"channel.viewer_can_edit_about": "boolean",
+ "channel.social_links_editor": "object",
+ "channel.social_links_editor.connected": "array",
+ "channel.social_links_editor.restreams_without_link": "array",
+ "channel.social_links_editor.hidden_auto": "array",
"channel.ai_overview": "null",
"channel.ai_overview": "null",
"language": "object",
"language": "object",
"language.code": "string",
"language.code": "string",
@@ -5848,6 +5934,7 @@
@@ -5848,6 +5934,7 @@
"channel.offline_screen_url": "null",
"channel.offline_screen_url": "null",
"channel.offline_html": "null",
"channel.offline_html": "null",
"channel.offline_css": "null",
"channel.offline_css": "null",
+ "channel.social_links": "array",
"channel.username": "string",
"channel.username": "string",
"channel.display_name": "string",
"channel.display_name": "string",
"channel.avatar_url": "null",
"channel.avatar_url": "null",
@@ -6036,6 +6123,7 @@
@@ -6036,6 +6123,7 @@
"channel.offline_screen_url": "null",
"channel.offline_screen_url": "null",
"channel.offline_html": "null",
"channel.offline_html": "null",
"channel.offline_css": "null",
"channel.offline_css": "null",
+ "channel.social_links": "array",
"channel.username": "string",
"channel.username": "string",
"channel.display_name": "string",
"channel.display_name": "string",
"channel.avatar_url": "null",
"channel.avatar_url": "null",
@@ -6050,6 +6138,10 @@
@@ -6050,6 +6138,10 @@
"channel.chat_limits.tts_max_length": "number",
"channel.chat_limits.tts_max_length": "number",
"channel.mods_can_edit_about": "boolean",
"channel.mods_can_edit_about": "boolean",
"channel.viewer_can_edit_about": "boolean",
"channel.viewer_can_edit_about": "boolean",
+ "channel.social_links_editor": "object",
+ "channel.social_links_editor.connected": "array",
+ "channel.social_links_editor.restreams_without_link": "array",
+ "channel.social_links_editor.hidden_auto": "array",
"channel.ai_overview": "null",
"channel.ai_overview": "null",
"language": "object",
"language": "object",
"language.code": "string",
"language.code": "string",
@@ -6657,6 +6749,7 @@
@@ -6657,6 +6749,7 @@
"stream.channel.offline_screen_url": "null",
"stream.channel.offline_screen_url": "null",
"stream.channel.offline_html": "null",
"stream.channel.offline_html": "null",
"stream.channel.offline_css": "null",
"stream.channel.offline_css": "null",
+ "stream.channel.social_links": "null",
"stream.pip_overlay": "null"
"stream.pip_overlay": "null"
},
},
"from": [
"from": [
@@ -6735,6 +6828,7 @@
@@ -6735,6 +6828,7 @@
"stream.channel.offline_screen_url": "null",
"stream.channel.offline_screen_url": "null",
"stream.channel.offline_html": "null",
"stream.channel.offline_html": "null",
"stream.channel.offline_css": "null",
"stream.channel.offline_css": "null",
+ "stream.channel.social_links": "null",
"stream.pip_overlay": "null"
"stream.pip_overlay": "null"
},
},
"from": [
"from": [
@@ -7387,7 +7481,7 @@
@@ -7387,7 +7481,7 @@
},
},
{
{
"method": "GET",
"method": "GET",
- "path": "/shared/navbar.js?v=b3aef18429d0",
+ "path": "/shared/navbar.js?v=6e398e3b31f5",
"auth": "anon",
"auth": "anon",
"accept": "html",
"accept": "html",
"status": 200,
"status": 200,
@@ -7508,7 +7602,7 @@
@@ -7508,7 +7602,7 @@
},
},
{
{
"method": "GET",
"method": "GET",
- "path": "/shared/theme-loader.js?v=d9b9af081efe",
+ "path": "/shared/theme-loader.js?v=ee4a6754d0f7",
"auth": "anon",
"auth": "anon",
"accept": "html",
"accept": "html",
"status": 200,
"status": 200,
@@ -7563,7 +7657,7 @@
@@ -7563,7 +7657,7 @@
},
},
{
{
"method": "GET",
"method": "GET",
- "path": "/js/app-home.js?v=d88ec40a35c0",
+ "path": "/js/app-home.js?v=4bbd63911072",
"auth": "anon",
"auth": "anon",
"accept": "html",
"accept": "html",
"status": 200,
"status": 200,
@@ -7605,17 +7699,6 @@
@@ -7605,17 +7699,6 @@
"page /"
"page /"
]
]
},
},
- {
- "method": "GET",
- "path": "/js/home-tour.js?v=a5ded0950973",
- "auth": "anon",
- "accept": "html",
- "status": 200,
- "kind": "other",
- "from": [
- "page /"
- ]
- },
{
{
"method": "GET",
"method": "GET",
"path": "/shared/tooltip.js?v=096488cf4948",
"path": "/shared/tooltip.js?v=096488cf4948",
@@ -7629,7 +7712,7 @@
@@ -7629,7 +7712,7 @@
},
},
{
{
"method": "GET",
"method": "GET",
- "path": "/css/features/channel.css?v=dfe3fa1b9949",
+ "path": "/css/features/channel.css?v=2f0cf7c3ec13",
"auth": "anon",
"auth": "anon",
"accept": "html",
"accept": "html",
"status": 200,
"status": 200,
@@ -7640,7 +7723,7 @@
@@ -7640,7 +7723,7 @@
},
},
{
{
"method": "GET",
"method": "GET",
- "path": "/css/context-cards.css?v=60ad33ae3e48",
+ "path": "/css/context-cards.css?v=997aed9c5db8",
"auth": "anon",
"auth": "anon",
"accept": "html",
"accept": "html",
"status": 200,
"status": 200,
@@ -7695,7 +7778,7 @@
@@ -7695,7 +7778,7 @@
},
},
{
{
"method": "GET",
"method": "GET",
- "path": "/js/context-cards.js?v=6719bbc1c432",
+ "path": "/js/context-cards.js?v=edb4f671e62d",
"auth": "anon",
"auth": "anon",
"accept": "html",
"accept": "html",
"status": 200,
"status": 200,
@@ -7750,7 +7833,18 @@
@@ -7750,7 +7833,18 @@
},
},
{
{
"method": "GET",
"method": "GET",
- "path": "/js/app-channel.js?v=1c0b1a8e0649",
+ "path": "/js/channel-socials.js?v=3c59ca14bf8b",
+ "auth": "anon",
+ "accept": "html",
+ "status": 200,
+ "kind": "other",
+ "from": [
+ "page /@n1star"
+ ]
+ },
+ {
+ "method": "GET",
+ "path": "/js/app-channel.js?v=fb9e1528dba8",
"auth": "anon",
"auth": "anon",
"accept": "html",
"accept": "html",
"status": 200,
"status": 200,
@@ -1,7 +1,7 @@
@@ -1,7 +1,7 @@
{
{
"service": "live",
"service": "live",
- "release": "a825d8069fabbe79511df77a7f75e3568859ed4d",
- "recorded_at": "2026-09-28T23:50:44.753Z",
+ "release": "14f16182e7c2eeeb5e4d542306c50467c5eca171",
+ "recorded_at": "2026-09-29T00:16:55.917Z",
"note": "N-1 fixture: written by `npm run n-1:record`, replayed by test/n-1.test.js",
"note": "N-1 fixture: written by `npm run n-1:record`, replayed by test/n-1.test.js",
"user_version": 0,
"user_version": 0,
"schema": [
"schema": [
@@ -33,7 +33,7 @@
@@ -33,7 +33,7 @@
"CREATE TABLE channel_points (\n user_id INTEGER NOT NULL,\n streamer_id INTEGER NOT NULL,\n balance INTEGER NOT NULL DEFAULT 0,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (user_id, streamer_id)\n )",
"CREATE TABLE channel_points (\n user_id INTEGER NOT NULL,\n streamer_id INTEGER NOT NULL,\n balance INTEGER NOT NULL DEFAULT 0,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (user_id, streamer_id)\n )",
"CREATE TABLE channel_points_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n idempotency_key TEXT NOT NULL UNIQUE,\n user_id INTEGER NOT NULL,\n streamer_id INTEGER NOT NULL,\n delta INTEGER NOT NULL,\n reason TEXT,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n )",
"CREATE TABLE channel_points_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n idempotency_key TEXT NOT NULL UNIQUE,\n user_id INTEGER NOT NULL,\n streamer_id INTEGER NOT NULL,\n delta INTEGER NOT NULL,\n reason TEXT,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n )",
"CREATE TABLE channel_sounds (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n channel_owner_id INTEGER NOT NULL, -- streamer whose channel this sound belongs to\n command TEXT NOT NULL, -- trigger word (without leading '!'), lowercased\n url TEXT NOT NULL, -- served path of the audio file on disk\n mime TEXT DEFAULT 'audio/mpeg',\n duration_seconds REAL DEFAULT 0,\n created_by INTEGER, -- uploader user id (NULL if removed user)\n created_by_name TEXT DEFAULT '',\n is_approved INTEGER DEFAULT 1,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP, emote_code TEXT DEFAULT '',\n -- Multiple sounds may share a command; playback picks one at random.\n FOREIGN KEY (channel_owner_id) REFERENCES users(id) ON DELETE CASCADE\n)",
"CREATE TABLE channel_sounds (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n channel_owner_id INTEGER NOT NULL, -- streamer whose channel this sound belongs to\n command TEXT NOT NULL, -- trigger word (without leading '!'), lowercased\n url TEXT NOT NULL, -- served path of the audio file on disk\n mime TEXT DEFAULT 'audio/mpeg',\n duration_seconds REAL DEFAULT 0,\n created_by INTEGER, -- uploader user id (NULL if removed user)\n created_by_name TEXT DEFAULT '',\n is_approved INTEGER DEFAULT 1,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP, emote_code TEXT DEFAULT '',\n -- Multiple sounds may share a command; playback picks one at random.\n FOREIGN KEY (channel_owner_id) REFERENCES users(id) ON DELETE CASCADE\n)",
- "CREATE TABLE channels (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n user_id INTEGER NOT NULL UNIQUE,\n title TEXT DEFAULT 'Untitled Channel',\n description TEXT DEFAULT '',\n category TEXT DEFAULT 'irl',\n tags TEXT DEFAULT '[]',\n protocol TEXT DEFAULT 'webrtc' CHECK(protocol IN ('jsmpeg', 'webrtc', 'rtmp')),\n is_nsfw INTEGER DEFAULT 0,\n auto_record INTEGER DEFAULT 0,\n offline_banner_url TEXT,\n panels TEXT DEFAULT '[]', -- JSON array of info panels\n emote_sources TEXT DEFAULT '{\"defaults\":true,\"custom\":true,\"ffz\":true,\"bttv\":true,\"7tv\":true}',\n default_vod_visibility TEXT DEFAULT 'public' CHECK(default_vod_visibility IN ('public', 'unlisted', 'private')),\n default_clip_visibility TEXT DEFAULT 'public' CHECK(default_clip_visibility IN ('public', 'unlisted', 'private')),\n vod_recording_enabled INTEGER DEFAULT 1,\n force_vod_recording_disabled INTEGER DEFAULT 0,\n weather_zip TEXT DEFAULT NULL,\n weather_detail TEXT DEFAULT 'basic' CHECK(weather_detail IN ('off', 'basic', 'hourly', 'detailed')),\n weather_show_location INTEGER DEFAULT 0,\n control_mode TEXT DEFAULT 'open' CHECK(control_mode IN ('open','whitelist','disabled')),\n anon_controls_enabled INTEGER DEFAULT 1,\n control_rate_limit_ms INTEGER DEFAULT 100,\n video_click_enabled INTEGER DEFAULT 0,\n video_click_rate_limit_ms INTEGER DEFAULT 0,\n active_control_config_id INTEGER,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, chat_language TEXT DEFAULT 'auto', cp_name TEXT DEFAULT 'Channel Points', cp_icon TEXT DEFAULT 'fa-coins', cp_watch_interval_min INTEGER DEFAULT 5, cp_watch_amount INTEGER DEFAULT 10, cp_game_interval_min INTEGER DEFAULT 0, clips_allow_creator_delete INTEGER DEFAULT 0, hide_ai_overview INTEGER DEFAULT 0, ai_overview_pref TEXT DEFAULT 'auto', ai_derivation_enabled INTEGER DEFAULT 1, ai_category TEXT, force_nsfw INTEGER DEFAULT 0, offline_screen_type TEXT DEFAULT 'none', offline_screen_url TEXT, offline_html TEXT, offline_css TEXT,\n FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE\n)",
+ "CREATE TABLE channels (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n user_id INTEGER NOT NULL UNIQUE,\n title TEXT DEFAULT 'Untitled Channel',\n description TEXT DEFAULT '',\n category TEXT DEFAULT 'irl',\n tags TEXT DEFAULT '[]',\n protocol TEXT DEFAULT 'webrtc' CHECK(protocol IN ('jsmpeg', 'webrtc', 'rtmp')),\n is_nsfw INTEGER DEFAULT 0,\n auto_record INTEGER DEFAULT 0,\n offline_banner_url TEXT,\n panels TEXT DEFAULT '[]', -- JSON array of info panels\n emote_sources TEXT DEFAULT '{\"defaults\":true,\"custom\":true,\"ffz\":true,\"bttv\":true,\"7tv\":true}',\n default_vod_visibility TEXT DEFAULT 'public' CHECK(default_vod_visibility IN ('public', 'unlisted', 'private')),\n default_clip_visibility TEXT DEFAULT 'public' CHECK(default_clip_visibility IN ('public', 'unlisted', 'private')),\n vod_recording_enabled INTEGER DEFAULT 1,\n force_vod_recording_disabled INTEGER DEFAULT 0,\n weather_zip TEXT DEFAULT NULL,\n weather_detail TEXT DEFAULT 'basic' CHECK(weather_detail IN ('off', 'basic', 'hourly', 'detailed')),\n weather_show_location INTEGER DEFAULT 0,\n control_mode TEXT DEFAULT 'open' CHECK(control_mode IN ('open','whitelist','disabled')),\n anon_controls_enabled INTEGER DEFAULT 1,\n control_rate_limit_ms INTEGER DEFAULT 100,\n video_click_enabled INTEGER DEFAULT 0,\n video_click_rate_limit_ms INTEGER DEFAULT 0,\n active_control_config_id INTEGER,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, chat_language TEXT DEFAULT 'auto', cp_name TEXT DEFAULT 'Channel Points', cp_icon TEXT DEFAULT 'fa-coins', cp_watch_interval_min INTEGER DEFAULT 5, cp_watch_amount INTEGER DEFAULT 10, cp_game_interval_min INTEGER DEFAULT 0, clips_allow_creator_delete INTEGER DEFAULT 0, hide_ai_overview INTEGER DEFAULT 0, ai_overview_pref TEXT DEFAULT 'auto', ai_derivation_enabled INTEGER DEFAULT 1, ai_category TEXT, force_nsfw INTEGER DEFAULT 0, offline_screen_type TEXT DEFAULT 'none', offline_screen_url TEXT, offline_html TEXT, offline_css TEXT, social_links TEXT,\n FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE\n)",
"CREATE TABLE chat_ai_summaries (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n scope TEXT NOT NULL,\n subject_id INTEGER NOT NULL DEFAULT 0,\n window TEXT NOT NULL,\n overview TEXT DEFAULT '',\n memory_json TEXT DEFAULT '',\n timeline_json TEXT DEFAULT '[]',\n message_count INTEGER DEFAULT 0,\n window_message_count INTEGER DEFAULT 0,\n last_message_id INTEGER DEFAULT 0,\n window_label TEXT DEFAULT '',\n window_start DATETIME,\n window_end DATETIME,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n UNIQUE(scope, subject_id, window)\n )",
"CREATE TABLE chat_ai_summaries (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n scope TEXT NOT NULL,\n subject_id INTEGER NOT NULL DEFAULT 0,\n window TEXT NOT NULL,\n overview TEXT DEFAULT '',\n memory_json TEXT DEFAULT '',\n timeline_json TEXT DEFAULT '[]',\n message_count INTEGER DEFAULT 0,\n window_message_count INTEGER DEFAULT 0,\n last_message_id INTEGER DEFAULT 0,\n window_label TEXT DEFAULT '',\n window_start DATETIME,\n window_end DATETIME,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n UNIQUE(scope, subject_id, window)\n )",
"CREATE TABLE \"chat_messages\" (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n stream_id INTEGER,\n user_id INTEGER, -- NULL for anon\n anon_id TEXT, -- 'anon12345' format\n username TEXT,\n message TEXT NOT NULL,\n message_type TEXT DEFAULT 'chat' CHECK(message_type IN ('chat', 'system', 'donation', 'command', 'tts', 'channel-sound', 'soundboard', 'clip')),\n metadata TEXT, -- JSON sidecar for rich events (donation/goal-reached)\n is_global INTEGER DEFAULT 0,\n is_deleted INTEGER DEFAULT 0,\n is_filtered INTEGER DEFAULT 0,\n reply_to_id INTEGER REFERENCES chat_messages(id) ON DELETE SET NULL,\n source_platform TEXT,\n deleted_by INTEGER,\n deleted_at DATETIME,\n auto_delete_at DATETIME,\n timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, channel_user_id INTEGER,\n FOREIGN KEY (stream_id) REFERENCES streams(id) ON DELETE CASCADE,\n FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL\n)",
"CREATE TABLE \"chat_messages\" (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n stream_id INTEGER,\n user_id INTEGER, -- NULL for anon\n anon_id TEXT, -- 'anon12345' format\n username TEXT,\n message TEXT NOT NULL,\n message_type TEXT DEFAULT 'chat' CHECK(message_type IN ('chat', 'system', 'donation', 'command', 'tts', 'channel-sound', 'soundboard', 'clip')),\n metadata TEXT, -- JSON sidecar for rich events (donation/goal-reached)\n is_global INTEGER DEFAULT 0,\n is_deleted INTEGER DEFAULT 0,\n is_filtered INTEGER DEFAULT 0,\n reply_to_id INTEGER REFERENCES chat_messages(id) ON DELETE SET NULL,\n source_platform TEXT,\n deleted_by INTEGER,\n deleted_at DATETIME,\n auto_delete_at DATETIME,\n timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, channel_user_id INTEGER,\n FOREIGN KEY (stream_id) REFERENCES streams(id) ON DELETE CASCADE,\n FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL\n)",
"CREATE TABLE chat_timeline_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n scope TEXT NOT NULL DEFAULT 'global',\n subject_id INTEGER NOT NULL DEFAULT 0,\n ts DATETIME NOT NULL,\n label TEXT NOT NULL,\n detail TEXT DEFAULT '',\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n )",
"CREATE TABLE chat_timeline_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n scope TEXT NOT NULL DEFAULT 'global',\n subject_id INTEGER NOT NULL DEFAULT 0,\n ts DATETIME NOT NULL,\n label TEXT NOT NULL,\n detail TEXT DEFAULT '',\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n )",
@@ -257,19 +257,19 @@
@@ -257,19 +257,19 @@
"schema_migrations": [
"schema_migrations": [
{
{
"id": "001_vibes_decimal_to_bits",
"id": "001_vibes_decimal_to_bits",
- "applied_at": "2026-09-28 23:50:40",
+ "applied_at": "2026-09-29 00:16:52",
"mode": "adopted",
"mode": "adopted",
"duration_ms": 0
"duration_ms": 0
},
},
{
{
"id": "002_pastes_ai_columns",
"id": "002_pastes_ai_columns",
- "applied_at": "2026-09-28 23:50:40",
+ "applied_at": "2026-09-29 00:16:52",
"mode": "applied",
"mode": "applied",
- "duration_ms": 5
+ "duration_ms": 7
},
},
{
{
"id": "004_hot_path_indexes",
"id": "004_hot_path_indexes",
- "applied_at": "2026-09-28 23:50:40",
+ "applied_at": "2026-09-29 00:16:52",
"mode": "applied",
"mode": "applied",
"duration_ms": 1
"duration_ms": 1
}
}
@@ -826,6 +826,7 @@
@@ -826,6 +826,7 @@
"SELECT COUNT(DISTINCT user_id) AS count FROM streams WHERE user_id IS NOT NULL AND created_at >= datetime('now', ?) AND created_at < datetime('now', ?)",
"SELECT COUNT(DISTINCT user_id) AS count FROM streams WHERE user_id IS NOT NULL AND created_at >= datetime('now', ?) AND created_at < datetime('now', ?)",
"SELECT DISTINCT e.user_id FROM stream_timeline_events e JOIN users u ON u.id = e.user_id WHERE e.kind = 'speech' AND e.user_id IS NOT NULL AND e.created_at >= datetime('now', ?) AND COALESCE(u.is_banned, 0) = 0",
"SELECT DISTINCT e.user_id FROM stream_timeline_events e JOIN users u ON u.id = e.user_id WHERE e.kind = 'speech' AND e.user_id IS NOT NULL AND e.created_at >= datetime('now', ?) AND COALESCE(u.is_banned, 0) = 0",
"SELECT DISTINCT il.user_id, u.role FROM ip_log il JOIN users u ON u.id = il.user_id WHERE il.ip_address = ? AND il.user_id IS NOT NULL",
"SELECT DISTINCT il.user_id, u.role FROM ip_log il JOIN users u ON u.id = il.user_id WHERE il.ip_address = ? AND il.user_id IS NOT NULL",
+ "SELECT DISTINCT platform FROM restream_destinations WHERE user_id = ? AND platform IN ('twitch', 'youtube', 'kick')",
"SELECT DISTINCT rd.user_id FROM restream_destinations rd WHERE rd.managed_stream_id IS NULL",
"SELECT DISTINCT rd.user_id FROM restream_destinations rd WHERE rd.managed_stream_id IS NULL",
"SELECT DISTINCT s.user_id, u.stream_key, u.username, u.display_name, c.id AS channel_id, c.title AS channel_title, c.protocol AS channel_protocol, c.category AS channel_category, c.is_nsfw AS channel_is_nsfw FROM streams s JOIN users u ON s.user_id = u.id LEFT JOIN channels c ON c.user_id = s.user_id WHERE s.user_id NOT IN (SELECT user_id FROM managed_streams) AND u.stream_key IS NOT NULL",
"SELECT DISTINCT s.user_id, u.stream_key, u.username, u.display_name, c.id AS channel_id, c.title AS channel_title, c.protocol AS channel_protocol, c.category AS channel_category, c.is_nsfw AS channel_is_nsfw FROM streams s JOIN users u ON s.user_id = u.id LEFT JOIN channels c ON c.user_id = s.user_id WHERE s.user_id NOT IN (SELECT user_id FROM managed_streams) AND u.stream_key IS NOT NULL",
"SELECT DISTINCT title FROM streams WHERE user_id = ? AND duration_seconds > 0 ORDER BY started_at DESC LIMIT 8",
"SELECT DISTINCT title FROM streams WHERE user_id = ? AND duration_seconds > 0 ORDER BY started_at DESC LIMIT 8",
@@ -994,6 +995,7 @@
@@ -994,6 +995,7 @@
"SELECT payload, generated_at FROM ai_timeline_cache WHERE user_id = ?",
"SELECT payload, generated_at FROM ai_timeline_cache WHERE user_id = ?",
"SELECT payload_json, created_at FROM vibe_coding_events WHERE managed_stream_id = ? ORDER BY id DESC LIMIT ?",
"SELECT payload_json, created_at FROM vibe_coding_events WHERE managed_stream_id = ? ORDER BY id DESC LIMIT ?",
"SELECT persona_json FROM arena_profiles WHERE user_id = ?",
"SELECT persona_json FROM arena_profiles WHERE user_id = ?",
+ "SELECT platform, platform_username, channel_url FROM platform_connections WHERE user_id = ?",
"SELECT powerchat_count_views FROM restream_destinations WHERE id = ?",
"SELECT powerchat_count_views FROM restream_destinations WHERE id = ?",
"SELECT powerchat_relay FROM restream_destinations WHERE id = ?",
"SELECT powerchat_relay FROM restream_destinations WHERE id = ?",
"SELECT powerchat_username FROM powerchat_connections WHERE user_id = ?",
"SELECT powerchat_username FROM powerchat_connections WHERE user_id = ?",