diff --git a/src/app/components/message/content/PollContent.tsx b/src/app/components/message/content/PollContent.tsx
index 721c74dcb..86b8152b8 100644
--- a/src/app/components/message/content/PollContent.tsx
+++ b/src/app/components/message/content/PollContent.tsx
@@ -216,6 +216,7 @@ export function PollContent({
return (
handleVote(id) : undefined}
style={{
padding: '7px 12px',
diff --git a/src/app/components/upload-card/UploadCardRenderer.tsx b/src/app/components/upload-card/UploadCardRenderer.tsx
index 4e251cc75..98ef56dec 100644
--- a/src/app/components/upload-card/UploadCardRenderer.tsx
+++ b/src/app/components/upload-card/UploadCardRenderer.tsx
@@ -188,6 +188,7 @@ export function UploadCardRenderer({
placeholder="Add a caption… (optional)"
value={metadata.caption ?? ''}
onChange={(e) => setMetadata(fileItem, { ...metadata, caption: e.target.value })}
+ data-caption-input
style={{
marginTop: '6px',
width: '100%',
@@ -199,6 +200,7 @@ export function UploadCardRenderer({
color: 'var(--text-primary)',
outline: 'none',
boxSizing: 'border-box',
+ transition: 'border-color 0.15s, box-shadow 0.15s',
}}
/>
)}
diff --git a/src/app/features/room/message/ForwardMessageDialog.tsx b/src/app/features/room/message/ForwardMessageDialog.tsx
index 8decc44c5..7e656710a 100644
--- a/src/app/features/room/message/ForwardMessageDialog.tsx
+++ b/src/app/features/room/message/ForwardMessageDialog.tsx
@@ -1,6 +1,7 @@
-import React, { ChangeEvent, useState } from 'react';
+import React, { ChangeEvent, useCallback, useState } from 'react';
import FocusTrap from 'focus-trap-react';
import {
+ Avatar,
Box,
config,
Input,
@@ -11,11 +12,69 @@ import {
OverlayBackdrop,
OverlayCenter,
Scroll,
+ Spinner,
Text,
} from 'folds';
-import { MatrixEvent } from 'matrix-js-sdk';
+import { MatrixEvent, Room } from 'matrix-js-sdk';
+import { useAtomValue } from 'jotai';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { stopPropagation } from '../../../utils/keyboard';
+import { mDirectAtom } from '../../../state/mDirectList';
+import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
+import { mxcUrlToHttp } from '../../../utils/matrix';
+import { RoomAvatar, RoomIcon } from '../../../components/room-avatar';
+
+type RoomRowProps = {
+ room: Room;
+ dm: boolean;
+ useAuthentication: boolean;
+ onClick: () => void;
+ sending: boolean;
+};
+function RoomRow({ room, dm, useAuthentication, onClick, sending }: RoomRowProps) {
+ const mx = useMatrixClient();
+ const avatarMxc = room.getMxcAvatarUrl();
+ const avatarUrl = avatarMxc
+ ? (mxcUrlToHttp(mx, avatarMxc, useAuthentication, 48, 48, 'crop') ?? undefined)
+ : undefined;
+
+ return (
+
+ );
+}
type Props = {
mEvent: MatrixEvent;
@@ -24,7 +83,10 @@ type Props = {
export function ForwardMessageDialog({ mEvent, onClose }: Props) {
const mx = useMatrixClient();
+ const directs = useAtomValue(mDirectAtom);
+ const useAuthentication = useMediaAuthentication();
const [query, setQuery] = useState('');
+ const [sending, setSending] = useState(false);
const [sentTo, setSentTo] = useState(null);
const allRooms = mx
@@ -36,14 +98,23 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
? allRooms.filter((r) => r.name.toLowerCase().includes(query.toLowerCase()))
: allRooms;
- const forward = (roomId: string, roomName: string) => {
- const fwdContent: Record = { ...mEvent.getContent() };
- delete fwdContent['m.relates_to'];
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (mx as any).sendEvent(roomId, mEvent.getType(), fwdContent);
- setSentTo(roomName);
- setTimeout(onClose, 1200);
- };
+ const forward = useCallback(
+ async (room: Room) => {
+ if (sending) return;
+ setSending(true);
+ const fwdContent: Record = { ...mEvent.getContent() };
+ delete fwdContent['m.relates_to'];
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ await (mx as any).sendEvent(room.roomId, mEvent.getType(), fwdContent);
+ setSentTo(room.name);
+ setTimeout(onClose, 1400);
+ } catch {
+ setSending(false);
+ }
+ },
+ [mx, mEvent, onClose, sending],
+ );
return (
}>
@@ -59,7 +130,7 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
Forward message
- ) => setQuery(e.target.value)}
- />
+ {!sentTo && (
+ ) => setQuery(e.target.value)}
+ />
+ )}
{sentTo ? (
@@ -88,6 +161,7 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
grow="Yes"
alignItems="Center"
justifyContent="Center"
+ gap="300"
style={{ padding: config.space.S400 }}
>
✓ Forwarded to {sentTo}
@@ -97,16 +171,14 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
{filtered.slice(0, 60).map((room) => (
-
+ room={room}
+ dm={directs.has(room.roomId)}
+ useAuthentication={useAuthentication}
+ onClick={() => forward(room)}
+ sending={sending}
+ />
))}
{filtered.length === 0 && (
+ {sending && (
+
+
+
+ )}
)}
diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx
index 95db1db20..325498b0f 100644
--- a/src/app/features/room/message/Message.tsx
+++ b/src/app/features/room/message/Message.tsx
@@ -96,7 +96,7 @@ function DeliveryStatus({
label = 'Failed to send';
colorStyle = lotusTerminal ? '#FF3B3B' : color.Critical.Main;
} else if (status === EventStatus.QUEUED) {
- icon = '⏳';
+ icon = '⟳';
label = 'Queued';
colorStyle = lotusTerminal ? 'rgba(0,212,255,0.45)' : color.Secondary.Main;
} else if (status === EventStatus.SENDING || status === EventStatus.ENCRYPTING) {
diff --git a/src/index.css b/src/index.css
index 89b44f309..89a7f0206 100644
--- a/src/index.css
+++ b/src/index.css
@@ -141,6 +141,17 @@ textarea {
word-spacing: inherit;
}
+/* Caption input focus ring — replaces stripped outline */
+[data-caption-input]:focus-visible {
+ border-color: rgba(0, 100, 200, 0.55);
+ box-shadow: 0 0 0 2px rgba(0, 100, 200, 0.18);
+}
+.dark-theme [data-caption-input]:focus-visible,
+.butter-theme [data-caption-input]:focus-visible {
+ border-color: rgba(100, 160, 255, 0.60);
+ box-shadow: 0 0 0 2px rgba(100, 160, 255, 0.18);
+}
+
audio:not([controls]) {
display: none !important;
}
diff --git a/src/lotus-boot.ts b/src/lotus-boot.ts
index 65c22a716..8187b0ad3 100644
--- a/src/lotus-boot.ts
+++ b/src/lotus-boot.ts
@@ -55,8 +55,25 @@ export function runLotusBootSequence(force = false): void {
'line-height:1.7',
'white-space:pre-wrap',
'overflow:hidden',
+ 'flex:1',
].join(';');
overlay.appendChild(pre);
+
+ const escHint = document.createElement('div');
+ escHint.style.cssText = [
+ 'position:absolute',
+ 'bottom:1.5rem',
+ 'right:2rem',
+ 'font-size:0.68rem',
+ 'color:rgba(0,255,136,0.35)',
+ "font-family:'JetBrains Mono','Courier New',monospace",
+ 'letter-spacing:0.08em',
+ 'user-select:none',
+ 'pointer-events:none',
+ ].join(';');
+ escHint.textContent = '[ ESC ] skip';
+ overlay.appendChild(escHint);
+
document.body.appendChild(overlay);
const dismiss = (): void => {
diff --git a/src/lotus-terminal.css.ts b/src/lotus-terminal.css.ts
index 1bd9f3934..651953b63 100644
--- a/src/lotus-terminal.css.ts
+++ b/src/lotus-terminal.css.ts
@@ -878,3 +878,55 @@ globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} ._13tt0gb6:
background: 'rgba(0,98,184,0.08) !important' as any,
color: '#0062b8 !important' as any,
});
+
+// ── Poll card TDS ─────────────────────────────────────────────────────────────
+globalStyle(`body.${lotusTerminalBodyClass} [data-poll-content] [data-poll-answer]`, {
+ background: 'rgba(0,212,255,0.04) !important' as any,
+ border: '1px solid rgba(0,212,255,0.22) !important' as any,
+ color: '#c4d9ee !important' as any,
+ transition: 'border-color 0.15s, background 0.15s, box-shadow 0.15s',
+});
+globalStyle(`body.${lotusTerminalBodyClass} [data-poll-content] [data-poll-answer]:hover`, {
+ background: 'rgba(0,212,255,0.08) !important' as any,
+ borderColor: 'rgba(0,212,255,0.40) !important' as any,
+ boxShadow: '0 0 8px rgba(0,212,255,0.08)',
+});
+globalStyle(
+ `body.${lotusTerminalBodyClass} [data-poll-content] [data-poll-answer][data-selected="true"]`,
+ {
+ background: 'rgba(255,107,0,0.10) !important' as any,
+ border: '1px solid rgba(255,107,0,0.55) !important' as any,
+ boxShadow: '0 0 10px rgba(255,107,0,0.10)',
+ },
+);
+globalStyle(`body.${lotusTerminalBodyClass} [data-poll-content] [data-poll-content-label]`, {
+ color: 'rgba(0,212,255,0.60) !important' as any,
+ opacity: '1 !important' as any,
+ textShadow: '0 0 8px rgba(0,212,255,0.25)',
+});
+// light TDS
+globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} [data-poll-content] [data-poll-answer]`, {
+ background: 'rgba(0,98,184,0.04) !important' as any,
+ border: '1px solid rgba(0,98,184,0.22) !important' as any,
+ color: '#111827 !important' as any,
+});
+globalStyle(
+ `html[data-theme="light"] body.${lotusTerminalBodyClass} [data-poll-content] [data-poll-answer][data-selected="true"]`,
+ {
+ background: 'rgba(196,78,0,0.08) !important' as any,
+ border: '1px solid rgba(196,78,0,0.50) !important' as any,
+ },
+);
+
+// ── Caption input TDS focus ring ──────────────────────────────────────────────
+globalStyle(`body.${lotusTerminalBodyClass} [data-caption-input]:focus-visible`, {
+ borderColor: 'rgba(255,107,0,0.65) !important' as any,
+ boxShadow: '0 0 0 2px rgba(255,107,0,0.14), 0 0 6px rgba(255,107,0,0.10) !important' as any,
+});
+globalStyle(
+ `html[data-theme="light"] body.${lotusTerminalBodyClass} [data-caption-input]:focus-visible`,
+ {
+ borderColor: 'rgba(196,78,0,0.60) !important' as any,
+ boxShadow: '0 0 0 2px rgba(196,78,0,0.12) !important' as any,
+ },
+);