Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
25 changes: 21 additions & 4 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ 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);
Copy link

Copilot AI Apr 11, 2026

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.

setVerificationPickerLogId(null);
setReanalyzeHistoryRecordId(null);
setIsReanalyze(false);
}, [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 +426,10 @@ function AppContent() {
case 'dismiss':
try {
setErasedForPhase(null);
setPendingVerification(null);
setVerificationPickerLogId(null);
setReanalyzeHistoryRecordId(null);
setIsReanalyze(false);
Copy link

Copilot AI Apr 11, 2026

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 +513,8 @@ function AppContent() {
await window.betaflight.updateHistoryVerification(
historyRecordId,
verificationMetrics,
verificationPidMetrics
verificationPidMetrics,
verificationTFMetrics
);
await tuningHistory.reload();
} else if (isReanalyzeFlow) {
Expand Down Expand Up @@ -547,8 +561,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 +615,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 +633,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 +797,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