diff --git a/frontend/src/components/ChannelInfoPane.tsx b/frontend/src/components/ChannelInfoPane.tsx index f2aacb5..1ebfaf1 100644 --- a/frontend/src/components/ChannelInfoPane.tsx +++ b/frontend/src/components/ChannelInfoPane.tsx @@ -112,7 +112,7 @@ export function ChannelInfoPane({ > {isFavorite(favorites, 'channel', channel.key) ? ( <> - + Remove from favorites ) : ( diff --git a/frontend/src/components/ChatHeader.tsx b/frontend/src/components/ChatHeader.tsx index 6f1be29..4f6bd20 100644 --- a/frontend/src/components/ChatHeader.tsx +++ b/frontend/src/components/ChatHeader.tsx @@ -142,7 +142,7 @@ export function ChatHeader({ } > {isFavorite(favorites, conversation.type as 'channel' | 'contact', conversation.id) ? ( - + ) : ( )} diff --git a/frontend/src/components/ContactInfoPane.tsx b/frontend/src/components/ContactInfoPane.tsx index 8528e7b..df0e242 100644 --- a/frontend/src/components/ContactInfoPane.tsx +++ b/frontend/src/components/ContactInfoPane.tsx @@ -197,7 +197,7 @@ export function ContactInfoPane({ > {isFavorite(favorites, 'contact', contact.public_key) ? ( <> - + Remove from favorites ) : ( diff --git a/frontend/src/components/CrackerPanel.tsx b/frontend/src/components/CrackerPanel.tsx index adf434c..4342f5e 100644 --- a/frontend/src/components/CrackerPanel.tsx +++ b/frontend/src/components/CrackerPanel.tsx @@ -548,7 +548,7 @@ export function CrackerPanel({ Pending: {pendingCount} - Cracked: {crackedCount} + Cracked: {crackedCount} Failed: {failedCount} @@ -613,9 +613,9 @@ export function CrackerPanel({ {crackedRooms.map((room, i) => (
- #{room.roomName} + #{room.roomName} "{room.message.slice(0, 50)} {room.message.length > 50 ? '...' : ''}" diff --git a/frontend/src/components/MessageInput.tsx b/frontend/src/components/MessageInput.tsx index 07f2360..3d32980 100644 --- a/frontend/src/components/MessageInput.tsx +++ b/frontend/src/components/MessageInput.tsx @@ -186,9 +186,9 @@ export const MessageInput = forwardRef(fu className={cn( 'tabular-nums', limitState === 'error' || limitState === 'danger' - ? 'text-red-400 font-medium' + ? 'text-destructive font-medium' : limitState === 'warning' - ? 'text-yellow-500' + ? 'text-warning' : 'text-muted-foreground' )} > @@ -196,7 +196,7 @@ export const MessageInput = forwardRef(fu {remaining < 0 && ` (${remaining})`} {warningMessage && ( - + — {warningMessage} )} @@ -209,9 +209,9 @@ export const MessageInput = forwardRef(fu className={cn( 'tabular-nums', limitState === 'error' || limitState === 'danger' - ? 'text-red-400 font-medium' + ? 'text-destructive font-medium' : limitState === 'warning' - ? 'text-yellow-500' + ? 'text-warning' : 'text-muted-foreground' )} > @@ -220,7 +220,7 @@ export const MessageInput = forwardRef(fu )} {warningMessage && ( - + — {warningMessage} )} diff --git a/frontend/src/components/PathModal.tsx b/frontend/src/components/PathModal.tsx index 8d24ad5..db264f7 100644 --- a/frontend/src/components/PathModal.tsx +++ b/frontend/src/components/PathModal.tsx @@ -360,7 +360,7 @@ function HopNode({ hop, hopNumber, prevLocation }: HopNodeProps) {
Hop {hopNumber}:{' '} {hop.prefix} - {isAmbiguous && (ambiguous)} + {isAmbiguous && (ambiguous)}
{isUnknown ? ( diff --git a/frontend/src/components/RawPacketList.tsx b/frontend/src/components/RawPacketList.tsx index 77ec8d1..deb0363 100644 --- a/frontend/src/components/RawPacketList.tsx +++ b/frontend/src/components/RawPacketList.tsx @@ -149,9 +149,9 @@ function decodePacketSummary( function getRouteTypeColor(routeType: string): string { switch (routeType) { case 'Flood': - return 'bg-blue-500/20 text-blue-400'; + return 'bg-info/20 text-info'; case 'Direct': - return 'bg-green-500/20 text-green-400'; + return 'bg-success/20 text-success'; case 'Transport Flood': return 'bg-purple-500/20 text-purple-400'; case 'Transport Direct': diff --git a/frontend/src/components/RepeaterDashboard.tsx b/frontend/src/components/RepeaterDashboard.tsx index 53ad10b..6d96f51 100644 --- a/frontend/src/components/RepeaterDashboard.tsx +++ b/frontend/src/components/RepeaterDashboard.tsx @@ -95,7 +95,7 @@ export function RepeaterDashboard({ size="sm" onClick={loadAll} disabled={anyLoading} - className="text-xs border-green-400 text-green-400 hover:bg-green-400/10 hover:text-green-400" + className="text-xs border-success text-success hover:bg-success/10 hover:text-success" > {anyLoading ? 'Loading...' : 'Load All'} @@ -115,7 +115,7 @@ export function RepeaterDashboard({ aria-label={isFav ? 'Remove from favorites' : 'Add to favorites'} > {isFav ? ( - + ) : ( )} diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 376c9ff..baea922 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -411,8 +411,8 @@ export function Sidebar({ className={cn( 'text-[10px] font-semibold px-1.5 py-0.5 rounded-full min-w-[18px] text-center', row.isMention - ? 'bg-destructive text-destructive-foreground' - : 'bg-primary/90 text-primary-foreground' + ? 'bg-badge-mention text-badge-mention-foreground' + : 'bg-badge-unread/90 text-badge-unread-foreground' )} aria-label={`${row.unreadCount} unread message${row.unreadCount !== 1 ? 's' : ''}`} > diff --git a/frontend/src/components/StatusBar.tsx b/frontend/src/components/StatusBar.tsx index c56df47..50c4adb 100644 --- a/frontend/src/components/StatusBar.tsx +++ b/frontend/src/components/StatusBar.tsx @@ -76,8 +76,8 @@ export function StatusBar({ className={cn( 'w-2 h-2 rounded-full transition-colors', connected - ? 'bg-primary shadow-[0_0_6px_hsl(var(--primary)/0.5)]' - : 'bg-muted-foreground' + ? 'bg-status-connected shadow-[0_0_6px_hsl(var(--status-connected)/0.5)]' + : 'bg-status-disconnected' )} aria-hidden="true" /> @@ -109,7 +109,7 @@ export function StatusBar({ diff --git a/frontend/src/components/repeater/RepeaterAclPane.tsx b/frontend/src/components/repeater/RepeaterAclPane.tsx index af22e78..da9b0ad 100644 --- a/frontend/src/components/repeater/RepeaterAclPane.tsx +++ b/frontend/src/components/repeater/RepeaterAclPane.tsx @@ -15,9 +15,9 @@ export function AclPane({ }) { const permColor: Record = { 0: 'bg-muted text-muted-foreground', - 1: 'bg-blue-500/10 text-blue-500', - 2: 'bg-green-400/10 text-green-400', - 3: 'bg-amber-500/10 text-amber-500', + 1: 'bg-info/10 text-info', + 2: 'bg-success/10 text-success', + 3: 'bg-warning/10 text-warning', }; return ( diff --git a/frontend/src/components/repeater/RepeaterConsolePane.tsx b/frontend/src/components/repeater/RepeaterConsolePane.tsx index 87e45ce..12a1caf 100644 --- a/frontend/src/components/repeater/RepeaterConsolePane.tsx +++ b/frontend/src/components/repeater/RepeaterConsolePane.tsx @@ -39,7 +39,7 @@ export function ConsolePane({
@@ -48,11 +48,11 @@ export function ConsolePane({ )} {history.map((entry, i) => entry.outgoing ? ( -
+
> {entry.command}
) : ( -
+
{entry.response}
) diff --git a/frontend/src/components/repeater/RepeaterNeighborsPane.tsx b/frontend/src/components/repeater/RepeaterNeighborsPane.tsx index 6013102..3d51732 100644 --- a/frontend/src/components/repeater/RepeaterNeighborsPane.tsx +++ b/frontend/src/components/repeater/RepeaterNeighborsPane.tsx @@ -101,7 +101,7 @@ export function NeighborsPane({ const dist = n.distance; const snrStr = n.snr >= 0 ? `+${n.snr.toFixed(1)}` : n.snr.toFixed(1); const snrColor = - n.snr >= 6 ? 'text-green-400' : n.snr >= 0 ? 'text-yellow-500' : 'text-red-400'; + n.snr >= 6 ? 'text-success' : n.snr >= 0 ? 'text-warning' : 'text-destructive'; return ( {n.name || n.pubkey_prefix} diff --git a/frontend/src/components/repeater/RepeaterRadioSettingsPane.tsx b/frontend/src/components/repeater/RepeaterRadioSettingsPane.tsx index 8f9a51e..0f8d297 100644 --- a/frontend/src/components/repeater/RepeaterRadioSettingsPane.tsx +++ b/frontend/src/components/repeater/RepeaterRadioSettingsPane.tsx @@ -66,7 +66,7 @@ export function RadioSettingsPane({ (drift: {clockDrift.text}) @@ -88,7 +88,7 @@ export function RadioSettingsPane({ 'p-1 rounded transition-colors disabled:opacity-50', disabled || advertState.loading ? 'text-muted-foreground' - : 'text-green-500 hover:bg-accent hover:text-green-400' + : 'text-success hover:bg-accent hover:text-success' )} title="Refresh Advert Intervals" > diff --git a/frontend/src/components/repeater/repeaterPaneShared.tsx b/frontend/src/components/repeater/repeaterPaneShared.tsx index 8e725d6..3fad438 100644 --- a/frontend/src/components/repeater/repeaterPaneShared.tsx +++ b/frontend/src/components/repeater/repeaterPaneShared.tsx @@ -112,7 +112,7 @@ export function RepeaterPane({ 'p-1 rounded transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', disabled || state.loading ? 'text-muted-foreground' - : 'text-green-500 hover:bg-accent hover:text-green-400' + : 'text-success hover:bg-accent hover:text-success' )} title="Refresh" aria-label={`Refresh ${title}`} diff --git a/frontend/src/components/settings/SettingsBotSection.tsx b/frontend/src/components/settings/SettingsBotSection.tsx index 7198c88..0d48239 100644 --- a/frontend/src/components/settings/SettingsBotSection.tsx +++ b/frontend/src/components/settings/SettingsBotSection.tsx @@ -151,23 +151,23 @@ export function SettingsBotSection({ return (
-
-

+

+

Experimental: This is an alpha feature and introduces automated message sending to your radio; unexpected behavior may occur. Use with caution, and please report any bugs!

-
-

+

+

Security Warning: This feature executes arbitrary Python code on the server. Only run trusted code, and be cautious of arbitrary usage of message parameters.

-
-

+

+

Don't wreck the mesh! Bots process ALL messages, including their own. Be careful of creating infinite loops!

@@ -286,7 +286,7 @@ export function SettingsBotSection({
+
Loading editor...
} diff --git a/frontend/src/components/settings/SettingsConnectivitySection.tsx b/frontend/src/components/settings/SettingsConnectivitySection.tsx index d4b575d..fedbb82 100644 --- a/frontend/src/components/settings/SettingsConnectivitySection.tsx +++ b/frontend/src/components/settings/SettingsConnectivitySection.tsx @@ -82,14 +82,14 @@ export function SettingsConnectivitySection({ {health?.connection_info ? (
-
+
{health.connection_info}
) : (
-
+
Not connected
)} @@ -123,7 +123,7 @@ export function SettingsConnectivitySection({ variant="outline" onClick={handleReboot} disabled={rebooting || busy} - className="w-full border-red-500/50 text-red-400 hover:bg-red-500/10" + className="w-full border-destructive/50 text-destructive hover:bg-destructive/10" > {rebooting ? 'Rebooting...' : 'Reboot Radio'} diff --git a/frontend/src/components/settings/SettingsDatabaseSection.tsx b/frontend/src/components/settings/SettingsDatabaseSection.tsx index 2f29352..50e869d 100644 --- a/frontend/src/components/settings/SettingsDatabaseSection.tsx +++ b/frontend/src/components/settings/SettingsDatabaseSection.tsx @@ -171,7 +171,7 @@ export function SettingsDatabaseSection({ variant="outline" onClick={handleCleanup} disabled={cleaning} - className="border-red-500/50 text-red-400 hover:bg-red-500/10" + className="border-destructive/50 text-destructive hover:bg-destructive/10" > {cleaning ? 'Deleting...' : 'Permanently Delete'} @@ -194,7 +194,7 @@ export function SettingsDatabaseSection({ variant="outline" onClick={handlePurgeDecryptedRawPackets} disabled={purgingDecryptedRaw} - className="w-full border-yellow-500/50 text-yellow-400 hover:bg-yellow-500/10" + className="w-full border-warning/50 text-warning hover:bg-warning/10" > {purgingDecryptedRaw ? 'Purging Archival Raw Packets...' : 'Purge Archival Raw Packets'} diff --git a/frontend/src/components/settings/SettingsIdentitySection.tsx b/frontend/src/components/settings/SettingsIdentitySection.tsx index 0856489..7474769 100644 --- a/frontend/src/components/settings/SettingsIdentitySection.tsx +++ b/frontend/src/components/settings/SettingsIdentitySection.tsx @@ -175,7 +175,7 @@ export function SettingsIdentitySection({ diff --git a/frontend/src/components/settings/SettingsMqttSection.tsx b/frontend/src/components/settings/SettingsMqttSection.tsx index 67e40d0..ad42b6a 100644 --- a/frontend/src/components/settings/SettingsMqttSection.tsx +++ b/frontend/src/components/settings/SettingsMqttSection.tsx @@ -90,21 +90,21 @@ export function SettingsMqttSection({ return (
-
+
MQTT support is an experimental feature in open beta. All publishing uses QoS 0 (at-most-once delivery). Please report any bugs on the{' '} GitHub issues page .
-
+
Outgoing messages (DMs and group messages) will be reported to private MQTT brokers in decrypted/plaintext form. The raw outgoing packets will NOT be reported to any MQTT broker, private or community. This means that{' '} @@ -138,7 +138,7 @@ export function SettingsMqttSection({ className={cn( 'w-2 h-2 rounded-full transition-colors', health?.mqtt_status === 'connected' - ? 'bg-primary shadow-[0_0_6px_hsl(var(--primary)/0.5)]' + ? 'bg-status-connected shadow-[0_0_6px_hsl(var(--status-connected)/0.5)]' : 'bg-muted-foreground' )} /> @@ -323,7 +323,7 @@ export function SettingsMqttSection({ className={cn( 'w-2 h-2 rounded-full transition-colors', health?.community_mqtt_status === 'connected' - ? 'bg-primary shadow-[0_0_6px_hsl(var(--primary)/0.5)]' + ? 'bg-status-connected shadow-[0_0_6px_hsl(var(--status-connected)/0.5)]' : 'bg-muted-foreground' )} /> diff --git a/frontend/src/components/settings/SettingsStatisticsSection.tsx b/frontend/src/components/settings/SettingsStatisticsSection.tsx index 3513344..18812e2 100644 --- a/frontend/src/components/settings/SettingsStatisticsSection.tsx +++ b/frontend/src/components/settings/SettingsStatisticsSection.tsx @@ -88,12 +88,12 @@ export function SettingsStatisticsSection({ className }: { className?: string }) {stats.total_packets}
- Decrypted - {stats.decrypted_packets} + Decrypted + {stats.decrypted_packets}
- Undecrypted - {stats.undecrypted_packets} + Undecrypted + {stats.undecrypted_packets}
diff --git a/frontend/src/components/ui/dialog.tsx b/frontend/src/components/ui/dialog.tsx index 58332bc..802b213 100644 --- a/frontend/src/components/ui/dialog.tsx +++ b/frontend/src/components/ui/dialog.tsx @@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef< (({ className, ...props }, ref) => ( { cancelButton: 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground', // Muted error style - dark red-tinted background with readable text error: - 'group-[.toaster]:bg-[#2a1a1a] group-[.toaster]:text-[#e8a0a0] group-[.toaster]:border-[#4a2a2a] [&_[data-description]]:text-[#e8b0b0]', + 'group-[.toaster]:bg-toast-error group-[.toaster]:text-toast-error-foreground group-[.toaster]:border-toast-error-border [&_[data-description]]:text-toast-error-foreground', }, }} {...props} diff --git a/frontend/src/index.css b/frontend/src/index.css index 8acd741..ac6f493 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -28,6 +28,52 @@ /* Semantic colors for specific contexts */ --msg-outgoing: 152 30% 14%; --msg-incoming: 224 12% 15%; + + /* Status indicator pips */ + --status-connected: 142 71% 45%; + --status-disconnected: 220 9% 46%; + + /* Semantic feedback colors */ + --warning: 38 92% 50%; + --warning-foreground: 0 0% 100%; + --success: 142 71% 45%; + --success-foreground: 0 0% 100%; + --info: 217 91% 60%; + --info-foreground: 0 0% 100%; + + /* Favorites */ + --favorite: 43 96% 56%; + + /* Console / terminal */ + --console: 142 69% 58%; + --console-command: 142 76% 73%; + --console-bg: 0 0% 0%; + + /* Unread badges */ + --badge-unread: var(--primary); + --badge-unread-foreground: var(--primary-foreground); + --badge-mention: var(--destructive); + --badge-mention-foreground: var(--destructive-foreground); + + /* Error toast */ + --toast-error: 0 30% 14%; + --toast-error-foreground: 0 56% 77%; + --toast-error-border: 0 27% 23%; + + /* Code editor */ + --code-editor-bg: 220 13% 18%; + + /* Overlay / backdrop */ + --overlay: 0 0% 0%; + + /* Typography */ + --font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + --font-mono: + ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace; + + /* Scrollbar */ + --scrollbar: 224 11% 22%; + --scrollbar-hover: 224 11% 28%; } } @@ -37,12 +83,7 @@ } body { @apply bg-background text-foreground; - font-family: - system-ui, - -apple-system, - BlinkMacSystemFont, - 'Segoe UI', - sans-serif; + font-family: var(--font-sans); font-size: 14px; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -58,17 +99,17 @@ background: transparent; } ::-webkit-scrollbar-thumb { - background: hsl(224 11% 22%); + background: hsl(var(--scrollbar)); border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { - background: hsl(224 11% 28%); + background: hsl(var(--scrollbar-hover)); } /* Firefox scrollbar */ * { scrollbar-width: thin; - scrollbar-color: hsl(224 11% 22%) transparent; + scrollbar-color: hsl(var(--scrollbar)) transparent; } /* Message highlight animation for jump-to-message */ diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index afccb05..33d5972 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -43,6 +43,43 @@ export default { }, "msg-outgoing": "hsl(var(--msg-outgoing))", "msg-incoming": "hsl(var(--msg-incoming))", + "status-connected": "hsl(var(--status-connected))", + "status-disconnected": "hsl(var(--status-disconnected))", + warning: { + DEFAULT: "hsl(var(--warning))", + foreground: "hsl(var(--warning-foreground))", + }, + success: { + DEFAULT: "hsl(var(--success))", + foreground: "hsl(var(--success-foreground))", + }, + info: { + DEFAULT: "hsl(var(--info))", + foreground: "hsl(var(--info-foreground))", + }, + favorite: "hsl(var(--favorite))", + console: "hsl(var(--console))", + "console-command": "hsl(var(--console-command))", + "console-bg": "hsl(var(--console-bg))", + "code-editor-bg": "hsl(var(--code-editor-bg))", + overlay: "hsl(var(--overlay))", + "badge-unread": { + DEFAULT: "hsl(var(--badge-unread))", + foreground: "hsl(var(--badge-unread-foreground))", + }, + "badge-mention": { + DEFAULT: "hsl(var(--badge-mention))", + foreground: "hsl(var(--badge-mention-foreground))", + }, + "toast-error": { + DEFAULT: "hsl(var(--toast-error))", + foreground: "hsl(var(--toast-error-foreground))", + border: "hsl(var(--toast-error-border))", + }, + }, + fontFamily: { + sans: ["var(--font-sans)"], + mono: ["var(--font-mono)"], }, borderRadius: { lg: "var(--radius)",