From 56f8b796e6e3cd502aef1c0cd5ffa48a0953a0e5 Mon Sep 17 00:00:00 2001
From: Jack Kingsman
Date: Wed, 25 Feb 2026 16:03:47 -0800
Subject: [PATCH] Add scroll to repeater infobox on visualizer
---
frontend/src/App.tsx | 14 ++++++++
frontend/src/components/SettingsModal.tsx | 39 +++++++++++++++++++++++
2 files changed, 53 insertions(+)
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index bf60bca..26123c2 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -52,6 +52,7 @@ import { getStateKey } from './utils/conversationState';
import { appendRawPacketUnique } from './utils/rawPacketIdentity';
import { messageContainsMention } from './utils/messageParser';
import { mergeContactIntoList } from './utils/contactMerge';
+import { getLocalLabel, getContrastTextColor } from './utils/localLabel';
import { cn } from '@/lib/utils';
import type { Contact, Conversation, HealthStatus, Message, MessagePath, RawPacket } from './types';
@@ -66,6 +67,7 @@ export function App() {
const [sidebarOpen, setSidebarOpen] = useState(false);
const [showCracker, setShowCracker] = useState(false);
const [crackerRunning, setCrackerRunning] = useState(false);
+ const [localLabel, setLocalLabel] = useState(getLocalLabel);
// Defer CrackerPanel mount until first opened (lazy-loaded, but keep mounted after for state)
const crackerMounted = useRef(false);
@@ -467,6 +469,17 @@ export function App() {
return (
+ {localLabel.text && (
+
+ {localLabel.text}
+
+ )}
diff --git a/frontend/src/components/SettingsModal.tsx b/frontend/src/components/SettingsModal.tsx
index e7a43ae..969de08 100644
--- a/frontend/src/components/SettingsModal.tsx
+++ b/frontend/src/components/SettingsModal.tsx
@@ -25,6 +25,7 @@ import {
setReopenLastConversationEnabled,
} from '../utils/lastViewedConversation';
import { RADIO_PRESETS } from '../utils/radioPresets';
+import { getLocalLabel, setLocalLabel, type LocalLabel } from '../utils/localLabel';
import { SETTINGS_SECTION_LABELS, type SettingsSection } from './settingsConstants';
@@ -42,6 +43,7 @@ interface SettingsModalBaseProps {
onAdvertise: () => Promise;
onHealthRefresh: () => Promise;
onRefreshAppSettings: () => Promise;
+ onLocalLabelChange?: (label: LocalLabel) => void;
}
type SettingsModalProps = SettingsModalBaseProps &
@@ -65,6 +67,7 @@ export function SettingsModal(props: SettingsModalProps) {
onAdvertise,
onHealthRefresh,
onRefreshAppSettings,
+ onLocalLabelChange,
} = props;
const externalSidebarNav = props.externalSidebarNav === true;
const desktopSection = props.externalSidebarNav ? props.desktopSection : undefined;
@@ -118,6 +121,8 @@ export function SettingsModal(props: SettingsModalProps) {
const [reopenLastConversation, setReopenLastConversation] = useState(
getReopenLastConversationEnabled
);
+ const [localLabelText, setLocalLabelText] = useState(() => getLocalLabel().text);
+ const [localLabelColor, setLocalLabelColor] = useState(() => getLocalLabel().color);
// Advertisement interval state (displayed in hours, stored as seconds in DB)
const [advertIntervalHours, setAdvertIntervalHours] = useState('0');
@@ -1104,6 +1109,40 @@ export function SettingsModal(props: SettingsModalProps) {
+
+
+
+
+
+ {
+ const text = e.target.value;
+ setLocalLabelText(text);
+ setLocalLabel(text, localLabelColor);
+ onLocalLabelChange?.({ text, color: localLabelColor });
+ }}
+ placeholder="e.g. Home Base, Field Radio 2"
+ className="flex-1"
+ />
+ {
+ const color = e.target.value;
+ setLocalLabelColor(color);
+ setLocalLabel(localLabelText, color);
+ onLocalLabelChange?.({ text: localLabelText, color });
+ }}
+ className="w-10 h-9 rounded border border-input cursor-pointer bg-transparent p-0.5"
+ />
+
+
+ Display a colored banner at the top of the page to identify this instance. This
+ applies only to this device/browser.
+
+
+
{getSectionError('database') && (
{getSectionError('database')}
)}