From 404350a32f26c892fa7b3b65f222ebc955be8a36 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sun, 2 Aug 2026 16:52:41 -0700 Subject: Chat behind a header button, full-screen; move-panel polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chat leaves the tab bar (which returns to its four evenly spaced tabs) for an icon button in the header next to the wallet selector. It now takes over the whole screen — no balance card, no tabs — with a back button on the chat home view, and the logo still leads back to the wallet. The generic tabs/tabContent seams this reverts to unused are removed again. Move panel: the direction toggle stretches across the card, and the "X sats in Spending" line is gone — the balance sits right above it. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi --- src/app.js | 14 ++++++++------ src/features/ark.js | 2 +- src/features/messages.js | 27 +++++++++++++++++++++------ src/i18n.js | 1 - src/style.css | 4 +++- 5 files changed, 33 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/app.js b/src/app.js index 67a82df..a7ce84d 100644 --- a/src/app.js +++ b/src/app.js @@ -333,7 +333,7 @@ wallet.subscribe(scheduleRender); // buttons (and Android/system back) move between screens we've actually viewed. // We snapshot only the navigation-relevant `ui` fields, so incidental re-renders // (typing, polling, balance updates) don't create history entries. -const NAV_FIELDS = ['screen', 'tab', 'txDetail', 'bump', 'giftMode', 'claimStep', 'msgView', 'msgCommunity', 'msgPeer', 'settingsPage']; +const NAV_FIELDS = ['screen', 'tab', 'txDetail', 'bump', 'giftMode', 'claimStep', 'chatOpen', 'msgView', 'msgCommunity', 'msgPeer', 'settingsPage']; function navSnapshot() { const s = {}; for (const f of NAV_FIELDS) s[f] = ui[f] ?? null; @@ -1358,9 +1358,12 @@ function brandHeader(withLock) { { class: 'brand', style: 'cursor:pointer', title: t('home'), onClick: goHome }, h('div', { class: 'logo-full', 'aria-label': 'coinos', role: 'img', html: '' }) ), - withLock && - h('button', { class: 'btn-sm', onClick: () => { ui.screen = 'accounts'; render(); } }, - acc ? acc.label : t('accounts')) + withLock + ? h('div', { class: 'row gap6' }, + ...featureAll('headerButtons'), + h('button', { class: 'btn-sm', onClick: () => { ui.screen = 'accounts'; render(); } }, + acc ? acc.label : t('accounts'))) + : null ); } @@ -1592,6 +1595,7 @@ function goHome() { // so the logo is always a way back to your wallet. if (activeAccount()) { ui.screen = 'wallet'; + ui.chatOpen = false; // the logo always leads back to the wallet, chat included ui.tab = wallet.offline ? 'settings' : 'receive'; ui.draft = null; ui.sendResult = null; @@ -1978,7 +1982,6 @@ function tabsBar() { // Watch-only wallets show Send too — it prompts to load the seed to spend. ['send', t('tabSend')], ['history', t('tabHistory')], - ...featureAll('tabs'), // feature-provided tabs (e.g. messages) sit before Settings ['settings', t('tabSettings')], ]; return h( @@ -2008,7 +2011,6 @@ function tabContent() { case 'send': return sendTab(); case 'history': return historyTab(); case 'settings': return settingsTab(); - default: return featureHook('tabContent', ui.tab); } } diff --git a/src/features/ark.js b/src/features/ark.js index d98e92e..4d9242c 100644 --- a/src/features/ark.js +++ b/src/features/ark.js @@ -1372,7 +1372,7 @@ export function arkFeature(ctx) { h('span', { class: 'spinner sm' }), h('span', { class: 'small muted' }, t('arkConnecting')))) : h('div', { class: 'col', style: 'gap:8px' }, h('p', { class: 'small muted', style: 'margin:0' }, t('moveToSavingDesc')), - h('div', { class: 'small faint' }, t('moveAvailable', { n: fmtAmount(b ? b.spendableSat : 0) + ' ' + unitLabel() })), + // no "X sats in Spending" line — the balance sits right above on this card h('button', { class: 'btn-block', disabled: !b || !b.spendableSat, onClick: () => { ui.arkMoveOpen = false; ui.arkExitPage = true; ui.arkError = ''; render(); }, diff --git a/src/features/messages.js b/src/features/messages.js index 2484b46..25e6f7a 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -40,6 +40,7 @@ const COMMUNITY = { }; const EPOCH = 0; +const CHAT_ICON = ''; const DM_RELAYS = ['wss://relay.coinos.io', 'wss://nos.lol']; const APP_BASE = 'https://v3.coinos.io'; const CACHE_MAX = 50; // messages kept per channel / per DM thread in feature state @@ -104,7 +105,7 @@ export function messagesFeature(ctx) { if (repaintTimer) return; repaintTimer = setTimeout(() => { repaintTimer = null; - if (ui.screen === 'wallet' && ui.tab === 'messages') render(); + if (ui.screen === 'wallet' && ui.chatOpen) render(); }, 80); }; @@ -785,6 +786,11 @@ export function messagesFeature(ctx) { const kids = []; + // Chat takes the whole screen, so home carries the way back to the wallet. + kids.push(h('div', { class: 'row gap6', style: 'align-items:center' }, + backBtn(() => { ui.chatOpen = false; render(); }), + h('h3', { style: 'margin:0' }, t('tabMessages')))); + if (pendingLink) kids.push(linkInviteCard()); for (const [rid, inv] of pendingDirect) kids.push(directInviteCard(rid, inv)); @@ -1052,15 +1058,24 @@ export function messagesFeature(ctx) { return { id: 'messages', - tabs: () => [['messages', t('tabMessages')]], - tabContent(tab) { - if (tab !== 'messages') return null; - return messagesTab(); + // Chat lives behind a header button and takes over the whole screen — + // no balance card, no tabs; each view carries its own way back. + headerButtons() { + return [h('button', { + class: 'btn-sm', title: t('tabMessages'), + onClick: () => { ui.chatOpen = true; render(); }, + }, h('span', { html: CHAT_ICON }))]; + }, + screenView() { + if (!ui.chatOpen || ui.screen !== 'wallet') return null; + return h('div', { class: 'col', style: 'gap:16px' }, + ctx.brandHeader(false), + messagesTab()); }, init() { if (urlInvite && !pendingLink) { loadLinkInvite(urlInvite); - setTimeout(() => { ui.tab = 'messages'; ui.msgView = 'home'; render(); }, 0); + setTimeout(() => { ui.chatOpen = true; ui.msgView = 'home'; render(); }, 0); } startDMs(); syncLists().catch(() => {}); diff --git a/src/i18n.js b/src/i18n.js index 4f008d4..ed3cdbc 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -145,7 +145,6 @@ const STR = { moveToSaving: 'Into Savings', moveToSavingDesc: 'Park money in ordinary on-chain Bitcoin.', moveToSavingBtn: 'Move to Savings', - moveAvailable: '{n} in Spending', spendingLabel: 'Spending', savingLabel: 'Savings', movingLabel: 'Moving…', diff --git a/src/style.css b/src/style.css index 2afabac..05e7103 100644 --- a/src/style.css +++ b/src/style.css @@ -316,7 +316,7 @@ hr.divider { margin: 16px 0; } .chat-head { padding: 14px 16px 10px; border-bottom: 1px solid var(--line); } .chat-title { font-weight: 650; } .chat-log { - height: min(52vh, 460px); overflow-y: auto; padding: 12px 14px; + height: min(62vh, 640px); overflow-y: auto; padding: 12px 14px; display: flex; flex-direction: column; gap: 10px; background: var(--surface2); } .chat-row { display: flex; gap: 9px; align-items: flex-start; } @@ -385,3 +385,5 @@ hr.divider { margin: 16px 0; } /* Inline move-money panel on the balance card. */ .balance-move { gap: 10px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line); text-align: left; } +.balance-move .seg { display: flex; width: 100%; } +.balance-move .seg button { flex: 1; } -- cgit v1.2.3