From 1bffceab728dce40f9ce8147d35f2481c3a86639 Mon Sep 17 00:00:00 2001 From: Vaclav Eder Date: Thu, 9 Apr 2026 11:46:32 +0200 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20verification=20flight=20quality=20g?= =?UTF-8?q?ate=20=E2=80=94=20warn=20on=20fair/poor=20data=20quality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a verification flight has poor or fair data quality (< 60/100), show a warning modal with failing sub-scores and two options: - "Fly Again" — discard and let user retry with better stick inputs - "Accept Anyway" — proceed with low-quality verification data Previously the app silently accepted any verification data regardless of quality, which could produce unreliable convergence detection and tune quality scores. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/renderer/App.tsx | 119 +++++++++++++++--- .../VerificationQualityWarning.test.tsx | 92 ++++++++++++++ .../VerificationQualityWarning.tsx | 58 +++++++++ 3 files changed, 250 insertions(+), 19 deletions(-) create mode 100644 src/renderer/components/TuningHistory/VerificationQualityWarning.test.tsx create mode 100644 src/renderer/components/TuningHistory/VerificationQualityWarning.tsx diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 739540ac..e0d605be 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -46,14 +46,26 @@ import type { PIDMetricsSummary, TransferFunctionMetricsSummary, } from '@shared/types/tuning-history.types'; +import type { DataQualityScore } from '@shared/types/analysis.types'; import { extractFilterMetrics, extractPIDMetrics, extractTransferFunctionMetrics, } from '@shared/utils/metricsExtract'; import type { TuningAction } from './components/TuningStatusBanner/TuningStatusBanner'; +import { VerificationQualityWarning } from './components/TuningHistory/VerificationQualityWarning'; import './App.css'; +/** Pending verification data held while quality warning is shown */ +interface PendingVerification { + verificationMetrics?: FilterMetricsSummary; + verificationPidMetrics?: PIDMetricsSummary; + verificationTFMetrics?: TransferFunctionMetricsSummary; + dataQuality: DataQualityScore; + historyRecordId: string | null; + isReanalyze: boolean; +} + function AppContent() { const [showProfileWizard, setShowProfileWizard] = useState(false); const [newFCSerial, setNewFCSerial] = useState(null); @@ -73,6 +85,7 @@ function AppContent() { const [fixingSettings, setFixingSettings] = useState(false); const [showBannerFixConfirm, setShowBannerFixConfirm] = useState(false); const [analyzingVerification, setAnalyzingVerification] = useState(false); + const [pendingVerification, setPendingVerification] = useState(null); const [preparingSession, setPreparingSession] = useState(false); const [verificationPickerLogId, setVerificationPickerLogId] = useState(null); const [showLogPicker, setShowLogPicker] = useState(false); @@ -475,6 +488,61 @@ function AppContent() { } }; + /** Commit verification metrics to session/history (shared by direct path and quality-gate accept) */ + const commitVerification = async ( + verificationMetrics: FilterMetricsSummary | undefined, + verificationPidMetrics: PIDMetricsSummary | undefined, + verificationTFMetrics: TransferFunctionMetricsSummary | undefined, + historyRecordId: string | null, + isReanalyzeFlow: boolean + ) => { + if (historyRecordId) { + await window.betaflight.updateHistoryVerification( + historyRecordId, + verificationMetrics, + verificationPidMetrics + ); + await tuningHistory.reload(); + } else if (isReanalyzeFlow) { + await window.betaflight.updateVerificationMetrics( + verificationMetrics, + verificationTFMetrics, + verificationPidMetrics + ); + } else { + await tuning.updatePhase(TUNING_PHASE.COMPLETED, { + verificationMetrics, + verificationTransferFunctionMetrics: verificationTFMetrics, + verificationPidMetrics, + }); + } + setErasedForPhase(null); + }; + + const handleQualityGateAccept = async () => { + if (!pendingVerification) return; + try { + setAnalyzingVerification(true); + await commitVerification( + pendingVerification.verificationMetrics, + pendingVerification.verificationPidMetrics, + pendingVerification.verificationTFMetrics, + pendingVerification.historyRecordId, + pendingVerification.isReanalyze + ); + } catch (err) { + toast.error(err instanceof Error ? err.message : 'Failed to complete verification'); + } finally { + setPendingVerification(null); + setAnalyzingVerification(false); + } + }; + + const handleQualityGateReject = () => { + setPendingVerification(null); + toast.info('Verification discarded. Fly again with more stick inputs for better data.'); + }; + const handleVerificationAnalyze = async (sessionIndex: number) => { const verLogId = verificationPickerLogId; const historyRecordId = reanalyzeHistoryRecordId; @@ -491,15 +559,18 @@ function AppContent() { let verificationMetrics: FilterMetricsSummary | undefined; let verificationPidMetrics: PIDMetricsSummary | undefined; let verificationTFMetrics: TransferFunctionMetricsSummary | undefined; + let dataQuality: DataQualityScore | undefined; if (isPidSession) { // PID Tune verification: run PID analysis (stick snaps comparison) const pidResult = await window.betaflight.analyzePID(verLogId, sessionIndex); verificationPidMetrics = extractPIDMetrics(pidResult); + dataQuality = pidResult.dataQuality; } else { // Filter Tune / Flash Tune: run filter analysis (noise/spectrogram comparison) const filterResult = await window.betaflight.analyzeFilters(verLogId, sessionIndex); verificationMetrics = extractFilterMetrics(filterResult); + dataQuality = filterResult.dataQuality; // Flash Tune: also run TF analysis on verification flight if (isFlashSession) { @@ -522,29 +593,31 @@ function AppContent() { } } - if (historyRecordId) { - // Re-analyze a historical record - await window.betaflight.updateHistoryVerification( - historyRecordId, + // Quality gate: warn user if verification flight data quality is poor/fair + if ( + dataQuality && + (dataQuality.tier === 'poor' || dataQuality.tier === 'fair') && + !historyRecordId && + !isReanalyze + ) { + setPendingVerification({ verificationMetrics, - verificationPidMetrics - ); - await tuningHistory.reload(); - } else if (isReanalyze) { - // Re-analyze — update session + history without duplicate archive - await window.betaflight.updateVerificationMetrics( - verificationMetrics, - verificationTFMetrics, - verificationPidMetrics - ); - } else { - // First-time — transition to completed (archives session) - await tuning.updatePhase(TUNING_PHASE.COMPLETED, { - verificationMetrics, - verificationTransferFunctionMetrics: verificationTFMetrics, verificationPidMetrics, + verificationTFMetrics, + dataQuality, + historyRecordId, + isReanalyze, }); + return; // Wait for user decision in VerificationQualityWarning modal } + + await commitVerification( + verificationMetrics, + verificationPidMetrics, + verificationTFMetrics, + historyRecordId, + isReanalyze + ); setErasedForPhase(null); } catch (err) { toast.error(err instanceof Error ? err.message : 'Failed to analyze verification'); @@ -873,6 +946,14 @@ function AppContent() { /> )} + {pendingVerification && ( + + )} + {showTelemetrySettings && ( setShowTelemetrySettings(false)} /> )} diff --git a/src/renderer/components/TuningHistory/VerificationQualityWarning.test.tsx b/src/renderer/components/TuningHistory/VerificationQualityWarning.test.tsx new file mode 100644 index 00000000..521a6bea --- /dev/null +++ b/src/renderer/components/TuningHistory/VerificationQualityWarning.test.tsx @@ -0,0 +1,92 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { VerificationQualityWarning } from './VerificationQualityWarning'; +import type { DataQualityScore } from '@shared/types/analysis.types'; + +const fairQuality: DataQualityScore = { + overall: 46, + tier: 'fair', + subScores: [ + { name: 'Step count', score: 20, weight: 0.3 }, + { name: 'Axis coverage', score: 0, weight: 0.3 }, + { name: 'Magnitude variety', score: 100, weight: 0.2 }, + { name: 'Hold quality', score: 100, weight: 0.2 }, + ], +}; + +const poorQuality: DataQualityScore = { + overall: 15, + tier: 'poor', + subScores: [ + { name: 'Step count', score: 0, weight: 0.3 }, + { name: 'Axis coverage', score: 0, weight: 0.3 }, + { name: 'Magnitude variety', score: 50, weight: 0.2 }, + { name: 'Hold quality', score: 25, weight: 0.2 }, + ], +}; + +describe('VerificationQualityWarning', () => { + it('renders fair quality warning with score', () => { + render( + + ); + expect(screen.getByText(/Fair \(46\/100\)/)).toBeInTheDocument(); + expect(screen.getByText('Low Verification Data Quality')).toBeInTheDocument(); + }); + + it('renders poor quality warning', () => { + render( + + ); + expect(screen.getByText(/Poor \(15\/100\)/)).toBeInTheDocument(); + }); + + it('shows failing sub-scores', () => { + render( + + ); + expect(screen.getByText('Step count: 20/100')).toBeInTheDocument(); + expect(screen.getByText('Axis coverage: 0/100')).toBeInTheDocument(); + // Good sub-scores should not be shown + expect(screen.queryByText(/Magnitude variety/)).not.toBeInTheDocument(); + expect(screen.queryByText(/Hold quality/)).not.toBeInTheDocument(); + }); + + it('calls onAccept when Accept Anyway clicked', async () => { + const onAccept = vi.fn(); + const user = userEvent.setup(); + render( + + ); + await user.click(screen.getByRole('button', { name: 'Accept Anyway' })); + expect(onAccept).toHaveBeenCalledOnce(); + }); + + it('calls onReject when Fly Again clicked', async () => { + const onReject = vi.fn(); + const user = userEvent.setup(); + render( + + ); + await user.click(screen.getByRole('button', { name: 'Fly Again' })); + expect(onReject).toHaveBeenCalledOnce(); + }); + + it('has correct dialog role', () => { + render( + + ); + expect( + screen.getByRole('dialog', { name: 'Verification quality warning' }) + ).toBeInTheDocument(); + }); +}); diff --git a/src/renderer/components/TuningHistory/VerificationQualityWarning.tsx b/src/renderer/components/TuningHistory/VerificationQualityWarning.tsx new file mode 100644 index 00000000..445c1372 --- /dev/null +++ b/src/renderer/components/TuningHistory/VerificationQualityWarning.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import type { DataQualityScore } from '@shared/types/analysis.types'; + +interface VerificationQualityWarningProps { + dataQuality: DataQualityScore; + onAccept: () => void; + onReject: () => void; +} + +export function VerificationQualityWarning({ + dataQuality, + onAccept, + onReject, +}: VerificationQualityWarningProps) { + const tierLabel = dataQuality.tier === 'poor' ? 'Poor' : 'Fair'; + const tierColor = dataQuality.tier === 'poor' ? '#e74c3c' : '#f39c12'; + + return ( +
+
+

Low Verification Data Quality

+

+ The verification flight data quality is{' '} + + {tierLabel} ({dataQuality.overall}/100) + + . This may not give reliable results. +

+ + {dataQuality.subScores && dataQuality.subScores.length > 0 && ( +
+ {dataQuality.subScores + .filter((s) => s.score < 60) + .map((s) => ( +
+ {s.name}: {s.score}/100 +
+ ))} +
+ )} + +

+ For PID Tune, include at least 8-10 sharp stick snaps across roll and pitch axes. For + Filter Tune, include a steady throttle sweep from low to high. +

+ +
+ + +
+
+
+ ); +} From 22f0dcf1fc438537fe94957ecf9a5f5af0c5c3b6 Mon Sep 17 00:00:00 2001 From: Vaclav Eder Date: Thu, 9 Apr 2026 12:35:00 +0200 Subject: [PATCH 2/3] review: tier display map, double-click guard, remove redundant call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address CodePilot feedback: - Use exhaustive tier→label/color map instead of ternary - Clear pendingVerification before await to prevent double-click - Remove redundant setErasedForPhase (already in commitVerification) Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/worktrees/agent-a0ca28f7 | 1 + .claude/worktrees/agent-a1cc40f3 | 1 + .claude/worktrees/agent-a413338e | 1 + .claude/worktrees/agent-a43be540 | 1 + .claude/worktrees/agent-a4aef94e | 1 + .claude/worktrees/agent-a62855ee | 1 + .claude/worktrees/agent-a7e06b88 | 1 + .claude/worktrees/agent-a907cf36 | 1 + .claude/worktrees/agent-a9612b97 | 1 + .claude/worktrees/agent-af1b32ef | 1 + .claude/worktrees/agent-af27586f | 1 + .claude/worktrees/agent-af72940d | 1 + .claude/worktrees/cozy-crafting-hinton | 1 + .claude/worktrees/gallant-allen | 1 + .claude/worktrees/objective-satoshi | 1 + .claude/worktrees/unruffled-goldberg | 1 + src/renderer/App.tsx | 16 ++++++++-------- .../TuningHistory/VerificationQualityWarning.tsx | 10 ++++++++-- 18 files changed, 32 insertions(+), 10 deletions(-) create mode 160000 .claude/worktrees/agent-a0ca28f7 create mode 160000 .claude/worktrees/agent-a1cc40f3 create mode 160000 .claude/worktrees/agent-a413338e create mode 160000 .claude/worktrees/agent-a43be540 create mode 160000 .claude/worktrees/agent-a4aef94e create mode 160000 .claude/worktrees/agent-a62855ee create mode 160000 .claude/worktrees/agent-a7e06b88 create mode 160000 .claude/worktrees/agent-a907cf36 create mode 160000 .claude/worktrees/agent-a9612b97 create mode 160000 .claude/worktrees/agent-af1b32ef create mode 160000 .claude/worktrees/agent-af27586f create mode 160000 .claude/worktrees/agent-af72940d create mode 160000 .claude/worktrees/cozy-crafting-hinton create mode 160000 .claude/worktrees/gallant-allen create mode 160000 .claude/worktrees/objective-satoshi create mode 160000 .claude/worktrees/unruffled-goldberg diff --git a/.claude/worktrees/agent-a0ca28f7 b/.claude/worktrees/agent-a0ca28f7 new file mode 160000 index 00000000..8edb198a --- /dev/null +++ b/.claude/worktrees/agent-a0ca28f7 @@ -0,0 +1 @@ +Subproject commit 8edb198ad9c62c881827da90681dbf323a8facb3 diff --git a/.claude/worktrees/agent-a1cc40f3 b/.claude/worktrees/agent-a1cc40f3 new file mode 160000 index 00000000..115c98dc --- /dev/null +++ b/.claude/worktrees/agent-a1cc40f3 @@ -0,0 +1 @@ +Subproject commit 115c98dc390111f0e2d7aa2307e58b3cbdc3e293 diff --git a/.claude/worktrees/agent-a413338e b/.claude/worktrees/agent-a413338e new file mode 160000 index 00000000..7c9439e4 --- /dev/null +++ b/.claude/worktrees/agent-a413338e @@ -0,0 +1 @@ +Subproject commit 7c9439e4111287ed7bd718b907be7e45e647ed51 diff --git a/.claude/worktrees/agent-a43be540 b/.claude/worktrees/agent-a43be540 new file mode 160000 index 00000000..32b3dbd3 --- /dev/null +++ b/.claude/worktrees/agent-a43be540 @@ -0,0 +1 @@ +Subproject commit 32b3dbd3bfbbe96040bb7784288f0178babf32c4 diff --git a/.claude/worktrees/agent-a4aef94e b/.claude/worktrees/agent-a4aef94e new file mode 160000 index 00000000..8625635c --- /dev/null +++ b/.claude/worktrees/agent-a4aef94e @@ -0,0 +1 @@ +Subproject commit 8625635ca9f2d4db1eb751953f72fb3630a9da27 diff --git a/.claude/worktrees/agent-a62855ee b/.claude/worktrees/agent-a62855ee new file mode 160000 index 00000000..641cd06f --- /dev/null +++ b/.claude/worktrees/agent-a62855ee @@ -0,0 +1 @@ +Subproject commit 641cd06f7afad0ecdd87cdf0805a46833f5778b3 diff --git a/.claude/worktrees/agent-a7e06b88 b/.claude/worktrees/agent-a7e06b88 new file mode 160000 index 00000000..16b1c446 --- /dev/null +++ b/.claude/worktrees/agent-a7e06b88 @@ -0,0 +1 @@ +Subproject commit 16b1c446a0a6a2b80d30ae782c01ffb2d2e50084 diff --git a/.claude/worktrees/agent-a907cf36 b/.claude/worktrees/agent-a907cf36 new file mode 160000 index 00000000..95cb0c91 --- /dev/null +++ b/.claude/worktrees/agent-a907cf36 @@ -0,0 +1 @@ +Subproject commit 95cb0c915ac9e1756911e2f39f8861040f9cf578 diff --git a/.claude/worktrees/agent-a9612b97 b/.claude/worktrees/agent-a9612b97 new file mode 160000 index 00000000..c2e28b42 --- /dev/null +++ b/.claude/worktrees/agent-a9612b97 @@ -0,0 +1 @@ +Subproject commit c2e28b42c2d5d61d59af82a60222858abc0bf92a diff --git a/.claude/worktrees/agent-af1b32ef b/.claude/worktrees/agent-af1b32ef new file mode 160000 index 00000000..f14f95db --- /dev/null +++ b/.claude/worktrees/agent-af1b32ef @@ -0,0 +1 @@ +Subproject commit f14f95db4ce12797896ad19e8a88280b8c74b709 diff --git a/.claude/worktrees/agent-af27586f b/.claude/worktrees/agent-af27586f new file mode 160000 index 00000000..5f09557b --- /dev/null +++ b/.claude/worktrees/agent-af27586f @@ -0,0 +1 @@ +Subproject commit 5f09557b7baaf21a893b78336ce03f215a28fe72 diff --git a/.claude/worktrees/agent-af72940d b/.claude/worktrees/agent-af72940d new file mode 160000 index 00000000..fd9efaaf --- /dev/null +++ b/.claude/worktrees/agent-af72940d @@ -0,0 +1 @@ +Subproject commit fd9efaaf6cfdc832dc7c4cf31dde85d5e39c4f0a diff --git a/.claude/worktrees/cozy-crafting-hinton b/.claude/worktrees/cozy-crafting-hinton new file mode 160000 index 00000000..f05861eb --- /dev/null +++ b/.claude/worktrees/cozy-crafting-hinton @@ -0,0 +1 @@ +Subproject commit f05861eb1ecbcf1bba93a16270c8af39bd537a65 diff --git a/.claude/worktrees/gallant-allen b/.claude/worktrees/gallant-allen new file mode 160000 index 00000000..d6d4c585 --- /dev/null +++ b/.claude/worktrees/gallant-allen @@ -0,0 +1 @@ +Subproject commit d6d4c58511df0f01723932e39d84b356e9bd27ca diff --git a/.claude/worktrees/objective-satoshi b/.claude/worktrees/objective-satoshi new file mode 160000 index 00000000..d26bf9eb --- /dev/null +++ b/.claude/worktrees/objective-satoshi @@ -0,0 +1 @@ +Subproject commit d26bf9eb13e7ae8cb5ce161b5a211abdc2c04d88 diff --git a/.claude/worktrees/unruffled-goldberg b/.claude/worktrees/unruffled-goldberg new file mode 160000 index 00000000..a55fb3b1 --- /dev/null +++ b/.claude/worktrees/unruffled-goldberg @@ -0,0 +1 @@ +Subproject commit a55fb3b18368ea240e42eee1a6921c93f894f698 diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index e0d605be..b1f7fd39 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -520,20 +520,21 @@ function AppContent() { }; const handleQualityGateAccept = async () => { - if (!pendingVerification) return; + const pending = pendingVerification; + if (!pending) return; + setPendingVerification(null); // Clear immediately to prevent double-click try { setAnalyzingVerification(true); await commitVerification( - pendingVerification.verificationMetrics, - pendingVerification.verificationPidMetrics, - pendingVerification.verificationTFMetrics, - pendingVerification.historyRecordId, - pendingVerification.isReanalyze + pending.verificationMetrics, + pending.verificationPidMetrics, + pending.verificationTFMetrics, + pending.historyRecordId, + pending.isReanalyze ); } catch (err) { toast.error(err instanceof Error ? err.message : 'Failed to complete verification'); } finally { - setPendingVerification(null); setAnalyzingVerification(false); } }; @@ -618,7 +619,6 @@ function AppContent() { historyRecordId, isReanalyze ); - setErasedForPhase(null); } catch (err) { toast.error(err instanceof Error ? err.message : 'Failed to analyze verification'); } finally { diff --git a/src/renderer/components/TuningHistory/VerificationQualityWarning.tsx b/src/renderer/components/TuningHistory/VerificationQualityWarning.tsx index 445c1372..237c4a68 100644 --- a/src/renderer/components/TuningHistory/VerificationQualityWarning.tsx +++ b/src/renderer/components/TuningHistory/VerificationQualityWarning.tsx @@ -7,13 +7,19 @@ interface VerificationQualityWarningProps { onReject: () => void; } +const TIER_DISPLAY: Record = { + poor: { label: 'Poor', color: '#e74c3c' }, + fair: { label: 'Fair', color: '#f39c12' }, + good: { label: 'Good', color: '#27ae60' }, + excellent: { label: 'Excellent', color: '#2ecc71' }, +}; + export function VerificationQualityWarning({ dataQuality, onAccept, onReject, }: VerificationQualityWarningProps) { - const tierLabel = dataQuality.tier === 'poor' ? 'Poor' : 'Fair'; - const tierColor = dataQuality.tier === 'poor' ? '#e74c3c' : '#f39c12'; + const { label: tierLabel, color: tierColor } = TIER_DISPLAY[dataQuality.tier]; return (
From 405e849240167d1b589cea64b388cdb3114730d4 Mon Sep 17 00:00:00 2001 From: Vaclav Eder Date: Thu, 9 Apr 2026 12:35:19 +0200 Subject: [PATCH 3/3] chore: remove accidentally committed worktree submodules Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/worktrees/agent-a0ca28f7 | 1 - .claude/worktrees/agent-a1cc40f3 | 1 - .claude/worktrees/agent-a413338e | 1 - .claude/worktrees/agent-a43be540 | 1 - .claude/worktrees/agent-a4aef94e | 1 - .claude/worktrees/agent-a62855ee | 1 - .claude/worktrees/agent-a7e06b88 | 1 - .claude/worktrees/agent-a907cf36 | 1 - .claude/worktrees/agent-a9612b97 | 1 - .claude/worktrees/agent-af1b32ef | 1 - .claude/worktrees/agent-af27586f | 1 - .claude/worktrees/agent-af72940d | 1 - .claude/worktrees/cozy-crafting-hinton | 1 - .claude/worktrees/gallant-allen | 1 - .claude/worktrees/objective-satoshi | 1 - .claude/worktrees/unruffled-goldberg | 1 - 16 files changed, 16 deletions(-) delete mode 160000 .claude/worktrees/agent-a0ca28f7 delete mode 160000 .claude/worktrees/agent-a1cc40f3 delete mode 160000 .claude/worktrees/agent-a413338e delete mode 160000 .claude/worktrees/agent-a43be540 delete mode 160000 .claude/worktrees/agent-a4aef94e delete mode 160000 .claude/worktrees/agent-a62855ee delete mode 160000 .claude/worktrees/agent-a7e06b88 delete mode 160000 .claude/worktrees/agent-a907cf36 delete mode 160000 .claude/worktrees/agent-a9612b97 delete mode 160000 .claude/worktrees/agent-af1b32ef delete mode 160000 .claude/worktrees/agent-af27586f delete mode 160000 .claude/worktrees/agent-af72940d delete mode 160000 .claude/worktrees/cozy-crafting-hinton delete mode 160000 .claude/worktrees/gallant-allen delete mode 160000 .claude/worktrees/objective-satoshi delete mode 160000 .claude/worktrees/unruffled-goldberg diff --git a/.claude/worktrees/agent-a0ca28f7 b/.claude/worktrees/agent-a0ca28f7 deleted file mode 160000 index 8edb198a..00000000 --- a/.claude/worktrees/agent-a0ca28f7 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8edb198ad9c62c881827da90681dbf323a8facb3 diff --git a/.claude/worktrees/agent-a1cc40f3 b/.claude/worktrees/agent-a1cc40f3 deleted file mode 160000 index 115c98dc..00000000 --- a/.claude/worktrees/agent-a1cc40f3 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 115c98dc390111f0e2d7aa2307e58b3cbdc3e293 diff --git a/.claude/worktrees/agent-a413338e b/.claude/worktrees/agent-a413338e deleted file mode 160000 index 7c9439e4..00000000 --- a/.claude/worktrees/agent-a413338e +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7c9439e4111287ed7bd718b907be7e45e647ed51 diff --git a/.claude/worktrees/agent-a43be540 b/.claude/worktrees/agent-a43be540 deleted file mode 160000 index 32b3dbd3..00000000 --- a/.claude/worktrees/agent-a43be540 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 32b3dbd3bfbbe96040bb7784288f0178babf32c4 diff --git a/.claude/worktrees/agent-a4aef94e b/.claude/worktrees/agent-a4aef94e deleted file mode 160000 index 8625635c..00000000 --- a/.claude/worktrees/agent-a4aef94e +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8625635ca9f2d4db1eb751953f72fb3630a9da27 diff --git a/.claude/worktrees/agent-a62855ee b/.claude/worktrees/agent-a62855ee deleted file mode 160000 index 641cd06f..00000000 --- a/.claude/worktrees/agent-a62855ee +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 641cd06f7afad0ecdd87cdf0805a46833f5778b3 diff --git a/.claude/worktrees/agent-a7e06b88 b/.claude/worktrees/agent-a7e06b88 deleted file mode 160000 index 16b1c446..00000000 --- a/.claude/worktrees/agent-a7e06b88 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 16b1c446a0a6a2b80d30ae782c01ffb2d2e50084 diff --git a/.claude/worktrees/agent-a907cf36 b/.claude/worktrees/agent-a907cf36 deleted file mode 160000 index 95cb0c91..00000000 --- a/.claude/worktrees/agent-a907cf36 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 95cb0c915ac9e1756911e2f39f8861040f9cf578 diff --git a/.claude/worktrees/agent-a9612b97 b/.claude/worktrees/agent-a9612b97 deleted file mode 160000 index c2e28b42..00000000 --- a/.claude/worktrees/agent-a9612b97 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c2e28b42c2d5d61d59af82a60222858abc0bf92a diff --git a/.claude/worktrees/agent-af1b32ef b/.claude/worktrees/agent-af1b32ef deleted file mode 160000 index f14f95db..00000000 --- a/.claude/worktrees/agent-af1b32ef +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f14f95db4ce12797896ad19e8a88280b8c74b709 diff --git a/.claude/worktrees/agent-af27586f b/.claude/worktrees/agent-af27586f deleted file mode 160000 index 5f09557b..00000000 --- a/.claude/worktrees/agent-af27586f +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5f09557b7baaf21a893b78336ce03f215a28fe72 diff --git a/.claude/worktrees/agent-af72940d b/.claude/worktrees/agent-af72940d deleted file mode 160000 index fd9efaaf..00000000 --- a/.claude/worktrees/agent-af72940d +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fd9efaaf6cfdc832dc7c4cf31dde85d5e39c4f0a diff --git a/.claude/worktrees/cozy-crafting-hinton b/.claude/worktrees/cozy-crafting-hinton deleted file mode 160000 index f05861eb..00000000 --- a/.claude/worktrees/cozy-crafting-hinton +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f05861eb1ecbcf1bba93a16270c8af39bd537a65 diff --git a/.claude/worktrees/gallant-allen b/.claude/worktrees/gallant-allen deleted file mode 160000 index d6d4c585..00000000 --- a/.claude/worktrees/gallant-allen +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d6d4c58511df0f01723932e39d84b356e9bd27ca diff --git a/.claude/worktrees/objective-satoshi b/.claude/worktrees/objective-satoshi deleted file mode 160000 index d26bf9eb..00000000 --- a/.claude/worktrees/objective-satoshi +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d26bf9eb13e7ae8cb5ce161b5a211abdc2c04d88 diff --git a/.claude/worktrees/unruffled-goldberg b/.claude/worktrees/unruffled-goldberg deleted file mode 160000 index a55fb3b1..00000000 --- a/.claude/worktrees/unruffled-goldberg +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a55fb3b18368ea240e42eee1a6921c93f894f698