Fixing the logging to ouput debug window

- Route-scoped refresh
- Mock TDelegationContext
- notistack for log copy errors
- main-thread Tauri emit for log viewer events.
This commit is contained in:
Tommy Verrall
2026-05-06 14:55:56 +02:00
parent f3f927bdbb
commit 330a300710
7 changed files with 69 additions and 39 deletions
+8 -4
View File
@@ -62,10 +62,14 @@ pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerE
message: record.args().to_string(),
level: record.level().into(),
};
// Tauri 2: target the log webview explicitly so the dedicated window receives events.
if let Some(log_win) = log_window_app.get_webview_window("log") {
let _ = log_win.emit("log://log", msg);
}
let app = log_window_app.clone();
let app_for_emit = app.clone();
// Logging callbacks often run off the UI thread; WebView event delivery must run on main (macOS).
let _ = app.run_on_main_thread(move || {
if let Some(log_win) = app_for_emit.get_webview_window("log") {
let _ = log_win.emit("log://log", msg);
}
});
}));
base_config
@@ -17,6 +17,7 @@ import {
useTheme,
} from '@mui/material';
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
import { useSnackbar } from 'notistack';
import { helpLogToggleWindow } from 'src/requests/logging';
import { Console } from 'src/utils/console';
@@ -96,6 +97,7 @@ interface RecordPayload {
export const LogViewer: FC = () => {
const theme = useTheme();
const { enqueueSnackbar } = useSnackbar();
const unlisten = useRef<UnlistenFn | null>(null);
const [messages, setMessages] = useState<RecordPayload[]>([]);
const [messageCount, setMessageCount] = useState(0);
@@ -154,6 +156,7 @@ export const LogViewer: FC = () => {
await writeText(chronological.map(formatLine).join('\n'));
} catch (e) {
Console.error(e);
enqueueSnackbar('Could not copy logs to the clipboard', { variant: 'error' });
}
};
@@ -164,6 +167,7 @@ export const LogViewer: FC = () => {
await writeText(formatLine(latest));
} catch (e) {
Console.error(e);
enqueueSnackbar('Could not copy to the clipboard', { variant: 'error' });
}
};
+10 -18
View File
@@ -17,11 +17,6 @@ import { Console } from 'src/utils/console';
import { AppContext } from 'src/context/main';
import { delegationQueryKeys, fetchDelegationSummaryQuery } from './delegationQuery';
export type TDelegationRefreshOptions = {
/** When true, do not flip the global loading state (keeps cached list visible during refetch). */
background?: boolean;
};
export type TDelegationContext = {
delegationItemErrors?: { nodeId: string; errors: string };
isLoading: boolean;
@@ -33,7 +28,7 @@ export type TDelegationContext = {
totalDelegations?: string;
totalRewards?: string;
totalDelegationsAndRewards?: string;
refresh: (opts?: TDelegationRefreshOptions) => Promise<void>;
refresh: () => Promise<void>;
addDelegation: (
data: { mix_id: number; amount: DecCoin },
tokenPool: TPoolOption,
@@ -62,7 +57,7 @@ export const DelegationContext = createContext<TDelegationContext>({
isFetching: false,
isError: false,
lastUpdatedAtMs: 0,
refresh: async (_opts?: TDelegationRefreshOptions) => undefined,
refresh: async () => undefined,
addDelegation: async () => {
throw new Error('Not implemented');
},
@@ -108,17 +103,14 @@ export const DelegationContextProvider: FC<{
const bundle = clientAddress && onDelegationRoute ? query.data : undefined;
const refresh = useCallback(
async (_opts?: TDelegationRefreshOptions) => {
if (!clientAddress) {
return;
}
await queryClient.invalidateQueries({
queryKey: delegationQueryKeys.summary(clientAddress),
});
},
[clientAddress, queryClient],
);
const refresh = useCallback(async () => {
if (!clientAddress || !onDelegationRoute) {
return;
}
await queryClient.invalidateQueries({
queryKey: delegationQueryKeys.summary(clientAddress),
});
}, [clientAddress, onDelegationRoute, queryClient]);
const addDelegation = async (data: { mix_id: number; amount: DecCoin }, tokenPool: TPoolOption, fee?: FeeDetails) => {
try {
+36 -9
View File
@@ -7,7 +7,7 @@ import {
FeeDetails,
TransactionExecuteResult,
} from '@nymproject/types';
import { DelegationContext, TDelegationRefreshOptions, TDelegationTransaction } from '../delegations';
import { DelegationContext, TDelegationTransaction } from '../delegations';
import { mockSleep } from './utils';
import { TPoolOption } from '../../components';
@@ -71,6 +71,9 @@ export const MockDelegationContextProvider: FCWithChildren = ({ children }) => {
const [error, setError] = useState<string>();
const [delegations, setDelegations] = useState<undefined | DelegationWithEverything[]>();
const [totalDelegations, setTotalDelegations] = useState<undefined | string>();
const [totalRewards, setTotalRewards] = useState<undefined | string>();
const [totalDelegationsAndRewards, setTotalDelegationsAndRewards] = useState<undefined | string>();
const [lastUpdatedAtMs, setLastUpdatedAtMs] = useState(0);
const [delegationItemErrors, setDelegationItemErrors] = useState<{ nodeId: string; errors: string }>();
const triggerStateUpdate = () => setTrigger(new Date());
@@ -81,8 +84,16 @@ export const MockDelegationContextProvider: FCWithChildren = ({ children }) => {
const recalculate = async () => {
const newDelegations = await getDelegations();
const newTotalDelegations = `${newDelegations.length * 100} NYM`;
const rewardsSum = newDelegations.reduce((acc, d) => {
const n = parseFloat(d.unclaimed_rewards?.amount ?? '0');
return acc + (Number.isFinite(n) ? n : 0);
}, 0);
const newTotalRewards = `${rewardsSum} nym`;
setDelegations(newDelegations);
setTotalDelegations(newTotalDelegations);
setTotalRewards(newTotalRewards);
setTotalDelegationsAndRewards(`${newTotalDelegations} + ${newTotalRewards}`);
setLastUpdatedAtMs(Date.now());
};
const addDelegation = async (
@@ -193,8 +204,7 @@ export const MockDelegationContextProvider: FCWithChildren = ({ children }) => {
};
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const undelegateVesting = async (mix_id: number, _fee?: FeeDetails) => ({
const undelegateVesting = async (_mix_id: number) => ({
msg_responses_json: '',
data_json: '',
logs_json: '',
@@ -210,10 +220,13 @@ export const MockDelegationContextProvider: FCWithChildren = ({ children }) => {
setIsLoading(true);
setError(undefined);
setTotalDelegations(undefined);
setTotalRewards(undefined);
setTotalDelegationsAndRewards(undefined);
setLastUpdatedAtMs(0);
setDelegations([]);
};
const refresh = useCallback(async (_opts?: TDelegationRefreshOptions) => {
const refresh = useCallback(async () => {
resetState();
setTimeout(async () => {
try {
@@ -239,18 +252,32 @@ export const MockDelegationContextProvider: FCWithChildren = ({ children }) => {
isLoading,
isFetching: isLoading,
isError: Boolean(error),
lastUpdatedAtMs: 0,
error,
lastUpdatedAtMs,
delegations,
pendingDelegations: [],
totalDelegations,
totalRewards,
totalDelegationsAndRewards,
refresh,
getDelegations,
addDelegation,
updateDelegation,
undelegate,
undelegateVesting,
}),
[isLoading, error, delegations, totalDelegations, trigger],
[
isLoading,
error,
delegations,
totalDelegations,
totalRewards,
totalDelegationsAndRewards,
lastUpdatedAtMs,
refresh,
addDelegation,
undelegate,
undelegateVesting,
delegationItemErrors,
trigger,
],
);
return <DelegationContext.Provider value={memoizedValue}>{children}</DelegationContext.Provider>;
+3 -3
View File
@@ -1,13 +1,13 @@
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { FeeDetails, TransactionExecuteResult } from '@nymproject/types';
import { type TDelegationRefreshOptions, useDelegationContext } from './delegations';
import { useDelegationContext } from './delegations';
import { claimDelegatorRewards } from '../requests';
type TRewardsContext = {
isLoading: boolean;
error?: string;
totalRewards?: string;
refresh: (opts?: TDelegationRefreshOptions) => Promise<void>;
refresh: () => Promise<void>;
claimRewards: (mixId: number, fee?: FeeDetails) => Promise<TransactionExecuteResult[]>;
};
@@ -18,7 +18,7 @@ export type TRewardsTransaction = {
export const RewardsContext = createContext<TRewardsContext>({
isLoading: false,
refresh: async (_opts?: TDelegationRefreshOptions) => undefined,
refresh: async () => undefined,
claimRewards: async () => {
throw new Error('Not implemented');
},
+6 -3
View File
@@ -1,15 +1,18 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import { ErrorBoundary } from 'react-error-boundary';
import { SnackbarProvider } from 'notistack';
import { LogViewer } from './components/LogViewer';
import { ErrorFallback } from './components';
import { NymWalletTheme } from './theme';
const Log = () => (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<NymWalletTheme>
<LogViewer />
</NymWalletTheme>
<SnackbarProvider anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}>
<NymWalletTheme>
<LogViewer />
</NymWalletTheme>
</SnackbarProvider>
</ErrorBoundary>
);
+2 -2
View File
@@ -114,7 +114,7 @@ export const DelegationPage: FC = () => {
const doMigrateNow = async () => {
setShowVestingMigrationProgressModal(true);
await migrateVestedDelegations();
await refreshDelegations(undefined);
await refreshDelegations();
setShowVestingMigrationProgressModal(false);
};
@@ -132,7 +132,7 @@ export const DelegationPage: FC = () => {
});
}
prevConfirmationModalProps.current = confirmationModalProps;
}, [confirmationModalProps]);
}, [confirmationModalProps, refreshWithIntervalUpdate]);
const handleDelegationItemActionClick = (item: DelegationWithEverything, action: DelegationListItemActions) => {
if (