Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/main/ipc/handlers/tuningHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1067,7 +1067,8 @@ export function registerTuningHandlers(deps: HandlerDependencies): void {
_event,
recordId: string,
verificationMetrics?: FilterMetricsSummary,
verificationPidMetrics?: PIDMetricsSummary
verificationPidMetrics?: PIDMetricsSummary,
verificationTransferFunctionMetrics?: TransferFunctionMetricsSummary
): Promise<IPCResponse<void>> => {
try {
if (!tuningHistoryManager || !profileManager) {
Expand All @@ -1082,7 +1083,8 @@ export function registerTuningHandlers(deps: HandlerDependencies): void {
profileId,
recordId,
verificationMetrics,
verificationPidMetrics
verificationPidMetrics,
verificationTransferFunctionMetrics
);
if (!updated) {
return createResponse<void>(undefined, `History record not found: ${recordId}`);
Expand Down
24 changes: 24 additions & 0 deletions src/main/storage/TuningHistoryManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,30 @@ describe('TuningHistoryManager', () => {
expect(record.verificationMetrics).toBeNull();
});

it('archives verificationTransferFunctionMetrics and recommendationTraces', async () => {
const session: TuningSession = {
profileId: 'profile-1',
phase: TUNING_PHASE.COMPLETED,
tuningType: TUNING_TYPE.FLASH,
startedAt: '2026-01-15T10:00:00.000Z',
updatedAt: '2026-01-15T11:00:00.000Z',
verificationTransferFunctionMetrics: {
roll: { bandwidthHz: 45, phaseMarginDeg: 55, dcGainDb: -0.5 },
pitch: { bandwidthHz: 42, phaseMarginDeg: 50, dcGainDb: -0.8 },
yaw: { bandwidthHz: 30, phaseMarginDeg: 60, dcGainDb: -1.2 },
} as any,
recommendationTraces: [
{ ruleId: 'P-OS-P-roll', setting: 'pid_roll_p', from: 45, to: 40, confidence: 'high' },
] as any,
};

const record = await manager.archiveSession(session);
expect(record.verificationTransferFunctionMetrics).toEqual(
session.verificationTransferFunctionMetrics
);
expect(record.recommendationTraces).toEqual(session.recommendationTraces);
});

it('rejects non-completed sessions', async () => {
const session: TuningSession = {
profileId: 'profile-1',
Expand Down
8 changes: 7 additions & 1 deletion src/main/storage/TuningHistoryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,15 @@ export class TuningHistoryManager {
verificationMetrics: session.verificationMetrics ?? null,
verificationPidMetrics: session.verificationPidMetrics ?? null,
transferFunctionMetrics: session.transferFunctionMetrics ?? null,
verificationTransferFunctionMetrics: session.verificationTransferFunctionMetrics ?? null,
bfPidProfileIndex: session.bfPidProfileIndex,
appVersion: APP_VERSION,
ratesConfig: session.ratesConfig,
autoReportId: session.autoReportId,
convergence: session.convergence,
verificationSimilarity: session.verificationSimilarity,
iterationCount: session.iterationCount,
recommendationTraces: session.recommendationTraces,
};

const existing = await this.loadRecords(session.profileId);
Expand Down Expand Up @@ -123,14 +125,18 @@ export class TuningHistoryManager {
profileId: string,
recordId: string,
verificationMetrics?: FilterMetricsSummary,
verificationPidMetrics?: PIDMetricsSummary
verificationPidMetrics?: PIDMetricsSummary,
verificationTransferFunctionMetrics?: TransferFunctionMetricsSummary
): Promise<boolean> {
const records = await this.loadRecords(profileId);
const record = records.find((r) => r.id === recordId);
if (!record) return false;

if (verificationMetrics) record.verificationMetrics = verificationMetrics;
if (verificationPidMetrics) record.verificationPidMetrics = verificationPidMetrics;
if (verificationTransferFunctionMetrics) {
record.verificationTransferFunctionMetrics = verificationTransferFunctionMetrics;
}
await this.saveRecords(profileId, records);
logger.info(`Updated verification metrics on history record ${recordId}`);
return true;
Expand Down
6 changes: 4 additions & 2 deletions src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -823,13 +823,15 @@ const betaflightAPI: BetaflightAPI = {
async updateHistoryVerification(
recordId: string,
verificationMetrics?: FilterMetricsSummary,
verificationPidMetrics?: PIDMetricsSummary
verificationPidMetrics?: PIDMetricsSummary,
verificationTransferFunctionMetrics?: TransferFunctionMetricsSummary
): Promise<void> {
const response = await ipcRenderer.invoke(
IPCChannel.TUNING_UPDATE_HISTORY_VERIFICATION,
recordId,
verificationMetrics,
verificationPidMetrics
verificationPidMetrics,
verificationTransferFunctionMetrics
);
if (!response.success) {
throw new Error(response.error || 'Failed to update history verification');
Expand Down
21 changes: 17 additions & 4 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ function AppContent() {
.catch(() => setAvailableLogIds(new Set()));
};

// Clear stale verification state when session changes (disconnect, reset, new session)
const sessionPhase = tuning.session?.phase;
useEffect(() => {
setPendingVerification(null);

Copilot AI Apr 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new sessionPhase effect only clears pendingVerification, but other verification-related UI state can remain set across session/profile changes (e.g., verificationPickerLogId keeps VerificationSessionModal open, and reanalyzeHistoryRecordId/isReanalyze can leak into the next flow). Consider clearing verificationPickerLogId, reanalyzeHistoryRecordId, and isReanalyze in the same effect so any session change fully resets verification UI state.

Suggested change
setPendingVerification(null);
setPendingVerification(null);
setVerificationPickerLogId(null);
setReanalyzeHistoryRecordId(null);
setIsReanalyze(false);

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3851eab — useEffect now clears all verification state together.

}, [sessionPhase]);

// Clear erased state when flash has new data (post-flight reconnect via cache push)
const flashUsedSize = fcState.blackboxInfo?.usedSize ?? null;
const storageType = fcState.blackboxInfo?.storageType ?? 'flash';
Expand Down Expand Up @@ -417,6 +423,9 @@ function AppContent() {
case 'dismiss':
try {
setErasedForPhase(null);
setPendingVerification(null);
setVerificationPickerLogId(null);
setIsReanalyze(false);

Copilot AI Apr 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the 'dismiss' action you clear pendingVerification/verificationPickerLogId/isReanalyze, but reanalyzeHistoryRecordId is left intact. If the user previously started a history reanalysis (sets reanalyzeHistoryRecordId) and then dismisses/resets, the next verification analysis can incorrectly be treated as a history update and overwrite the wrong record. Clear reanalyzeHistoryRecordId here as part of the reset.

Suggested change
setIsReanalyze(false);
setIsReanalyze(false);
setReanalyzeHistoryRecordId(null);

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3851eab — reanalyzeHistoryRecordId added to dismiss cleanup.

await tuning.resetSession();
} catch (err) {
toast.error(err instanceof Error ? err.message : 'Failed to reset session');
Expand Down Expand Up @@ -500,7 +509,8 @@ function AppContent() {
await window.betaflight.updateHistoryVerification(
historyRecordId,
verificationMetrics,
verificationPidMetrics
verificationPidMetrics,
verificationTFMetrics
);
await tuningHistory.reload();
} else if (isReanalyzeFlow) {
Expand Down Expand Up @@ -547,8 +557,10 @@ function AppContent() {
const handleVerificationAnalyze = async (sessionIndex: number) => {
const verLogId = verificationPickerLogId;
const historyRecordId = reanalyzeHistoryRecordId;
const isReanalyzeFlow = isReanalyze;
setVerificationPickerLogId(null);
setReanalyzeHistoryRecordId(null);
setIsReanalyze(false);
if (!verLogId) return;

try {
Expand Down Expand Up @@ -599,15 +611,15 @@ function AppContent() {
dataQuality &&
(dataQuality.tier === 'poor' || dataQuality.tier === 'fair') &&
!historyRecordId &&
!isReanalyze
!isReanalyzeFlow
) {
setPendingVerification({
verificationMetrics,
verificationPidMetrics,
verificationTFMetrics,
dataQuality,
historyRecordId,
isReanalyze,
isReanalyze: isReanalyzeFlow,
});
return; // Wait for user decision in VerificationQualityWarning modal
}
Expand All @@ -617,7 +629,7 @@ function AppContent() {
verificationPidMetrics,
verificationTFMetrics,
historyRecordId,
isReanalyze
isReanalyzeFlow
);
} catch (err) {
toast.error(err instanceof Error ? err.message : 'Failed to analyze verification');
Expand Down Expand Up @@ -781,6 +793,7 @@ function AppContent() {
mode={wizardMode}
onExit={() => {
setActiveLogId(null);
setPendingVerification(null);
document.querySelector('.app-main')?.scrollTo({ top: 0 });
}}
onApplyComplete={handleApplyComplete}
Expand Down
3 changes: 2 additions & 1 deletion src/shared/types/ipc.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,8 @@ export interface BetaflightAPI {
updateHistoryVerification(
recordId: string,
verificationMetrics?: FilterMetricsSummary,
verificationPidMetrics?: PIDMetricsSummary
verificationPidMetrics?: PIDMetricsSummary,
verificationTransferFunctionMetrics?: TransferFunctionMetricsSummary
): Promise<void>;

// Telemetry
Expand Down
Loading