@@ -8,8 +8,10 @@ import {
88 Lock ,
99 MessageCircle ,
1010 MessagesSquare ,
11+ Moon ,
1112 Plus ,
1213 Send ,
14+ Sun ,
1315 ThumbsDown ,
1416 ThumbsUp ,
1517 UserCheck ,
@@ -23,6 +25,7 @@ import { readConversationSinceBreakpoint } from "./domain";
2325
2426type View = "overview" | "forums" | "direct" | "suggestions" | "onboarding" | "gates" | "profile" ;
2527type AgentStatus = "pending" | "approved" | "suspended" ;
28+ type ThemeMode = "day" | "night" ;
2629type ForumDraft = {
2730 slug : string ;
2831 name : string ;
@@ -50,6 +53,29 @@ const emptyState: AgentCommsState = {
5053} ;
5154
5255const useDemoData = import . meta. env . DEV && new URLSearchParams ( window . location . search ) . get ( "demo" ) === "1" ;
56+ const themePreferenceKey = "agent-comms-theme-mode" ;
57+
58+ const nightModeTheme : Record < string , string > = {
59+ "--color-bg" : "#101714" ,
60+ "--color-surface" : "#18231f" ,
61+ "--color-surface-hover" : "#22312b" ,
62+ "--color-sidebar" : "#0b1210" ,
63+ "--color-sidebar-hover" : "#1b2a25" ,
64+ "--color-text" : "#edf5ee" ,
65+ "--color-text-secondary" : "#bdcbc2" ,
66+ "--color-muted" : "#93a39a" ,
67+ "--color-line" : "#33413b" ,
68+ "--color-line-strong" : "#46554e" ,
69+ "--color-accent" : "#8bc7a7" ,
70+ "--color-accent-soft" : "#294338" ,
71+ "--color-warning" : "#a98135" ,
72+ "--color-danger" : "#ef7a64" ,
73+ "--color-ink" : "#edf5ee" ,
74+ "--color-inverse" : "#edf5ee" ,
75+ "--color-inverse-muted" : "#9aada4" ,
76+ "--color-inverse-accent" : "#b9e1c9" ,
77+ "--shadow-card" : "0 1px 2px rgba(0, 0, 0, 0.22), 0 18px 34px -24px rgba(0, 0, 0, 0.82)" ,
78+ } ;
5379
5480type LiveConversationSession = {
5581 id : string ;
@@ -234,6 +260,12 @@ function shellSingleQuote(value: string) {
234260 return `'${ value . replaceAll ( "'" , "'\\''" ) } '` ;
235261}
236262
263+ function getInitialThemeMode ( ) : ThemeMode {
264+ const stored = localStorage . getItem ( themePreferenceKey ) ;
265+ if ( stored === "day" || stored === "night" ) return stored ;
266+ return window . matchMedia ?.( "(prefers-color-scheme: dark)" ) . matches ? "night" : "day" ;
267+ }
268+
237269function onboardingCorrectionPrompt ( agent : AgentIdentity ) {
238270 const authStatus = agent . onboardingAuth ?. status ?? "missing" ;
239271 const statusText = authStatus . replace ( "_" , " " ) ;
@@ -1455,11 +1487,13 @@ export function App() {
14551487 const [ mintedTokens , setMintedTokens ] = useState < Record < string , { token : string ; copied ?: boolean ; fileCopied ?: boolean } | undefined > > ( { } ) ;
14561488 const [ liveSessions , setLiveSessions ] = useState < LiveConversationSession [ ] > ( [ ] ) ;
14571489 const [ operatorToken ] = useState ( ( ) => localStorage . getItem ( "agent-comms-operator-token" ) ?? "" ) ;
1490+ const [ themeMode , setThemeMode ] = useState < ThemeMode > ( getInitialThemeMode ) ;
14581491 const [ apiStatus , setApiStatus ] = useState ( useDemoData ? "demo data" : "loading durable storage" ) ;
14591492 const [ actionStatus , setActionStatus ] = useState ( "" ) ;
14601493 const refreshSequenceRef = useRef ( 0 ) ;
14611494 const mutationEpochRef = useRef ( 0 ) ;
14621495 const activeOperatorMutationsRef = useRef ( 0 ) ;
1496+ const appTheme = themeMode === "night" ? { ...branding . theme , ...nightModeTheme } : branding . theme ;
14631497
14641498 const beginOperatorMutation = useCallback ( ( ) => {
14651499 mutationEpochRef . current += 1 ;
@@ -1684,6 +1718,11 @@ export function App() {
16841718 localStorage . setItem ( "agent-comms-read-thread-activity-ids" , JSON . stringify ( readThreadActivityIds ) ) ;
16851719 } , [ readThreadActivityIds ] ) ;
16861720
1721+ useEffect ( ( ) => {
1722+ localStorage . setItem ( themePreferenceKey , themeMode ) ;
1723+ document . documentElement . dataset . theme = themeMode ;
1724+ } , [ themeMode ] ) ;
1725+
16871726 const latestConversationMessageIds = Object . fromEntries (
16881727 state . directConversations . map ( ( conversation ) => [
16891728 conversation . id ,
@@ -2128,7 +2167,7 @@ export function App() {
21282167 } ;
21292168
21302169 return (
2131- < main className = "app-shell" style = { branding . theme } >
2170+ < main className = "app-shell" data-theme = { themeMode } style = { appTheme } >
21322171 < nav className = "sidebar" aria-label = "Main navigation" >
21332172 < div className = { branding . logoUrl ? "brand has-logo" : "brand" } >
21342173 { branding . logoUrl ? (
@@ -2161,6 +2200,16 @@ export function App() {
21612200 < p className = "eyebrow" > { branding . eyebrow } </ p >
21622201 < h1 > { branding . title } </ h1 >
21632202 </ div >
2203+ < div className = "topbar-actions" >
2204+ < button
2205+ aria-label = { themeMode === "night" ? "Switch to day mode" : "Switch to night mode" }
2206+ onClick = { ( ) => setThemeMode ( ( current ) => ( current === "night" ? "day" : "night" ) ) }
2207+ title = { themeMode === "night" ? "Switch to day mode" : "Switch to night mode" }
2208+ type = "button"
2209+ >
2210+ { themeMode === "night" ? < Sun aria-hidden = "true" /> : < Moon aria-hidden = "true" /> }
2211+ </ button >
2212+ </ div >
21642213 </ header >
21652214 < p className = "api-status" > Data source: { apiStatus } { actionStatus ? `; ${ actionStatus } ` : "" } </ p >
21662215 { view === "overview" ? (
0 commit comments