diff --git a/frontend-modern/src/types/responsive.ts b/frontend-modern/src/types/responsive.ts index 05e7cc9c6..b0e2b6f03 100644 --- a/frontend-modern/src/types/responsive.ts +++ b/frontend-modern/src/types/responsive.ts @@ -347,13 +347,3 @@ export const STANDARD_COLUMNS = { maxWidth: '48px', }, } satisfies Record; - -/** - * Helper to create a column config from a standard column with overrides - */ -export function createColumn( - base: ColumnConfig, - overrides?: Partial> -): ColumnConfigWithRender { - return { ...base, ...overrides }; -} diff --git a/frontend-modern/src/utils/__tests__/url.test.ts b/frontend-modern/src/utils/__tests__/url.test.ts index e6d1d1dfc..8d72ce52b 100644 --- a/frontend-modern/src/utils/__tests__/url.test.ts +++ b/frontend-modern/src/utils/__tests__/url.test.ts @@ -6,7 +6,6 @@ import { subscribeToKioskMode, getPulseBaseUrl, getPulseHostname, - getPulsePort, isPulseHttps, getPulseWebSocketUrl } from '../url'; @@ -99,10 +98,6 @@ describe('url utils', () => { expect(getPulseHostname()).toBe('localhost'); }); - it('getPulsePort returns port', () => { - expect(getPulsePort()).toBe('3000'); - }); - it('isPulseHttps returns true for https', () => { (window.location as any).protocol = 'https:'; Object.defineProperty(window.location, 'origin', { diff --git a/frontend-modern/src/utils/url.ts b/frontend-modern/src/utils/url.ts index 82b7803ea..a12c98d68 100644 --- a/frontend-modern/src/utils/url.ts +++ b/frontend-modern/src/utils/url.ts @@ -117,17 +117,6 @@ export function getPulseHostname(): string { return origin?.hostname || 'localhost'; } -export function getPulsePort(): string { - const origin = getPulseOriginUrl(); - if (!origin) { - return '7655'; - } - if (origin.port) { - return origin.port; - } - return origin.protocol === 'https:' ? '443' : '80'; -} - export function isPulseHttps(): boolean { const origin = getPulseOriginUrl(); return origin?.protocol === 'https:'; diff --git a/internal/ai/findings.go b/internal/ai/findings.go index cbf212016..755f1df11 100644 --- a/internal/ai/findings.go +++ b/internal/ai/findings.go @@ -541,8 +541,8 @@ func (s *FindingsStore) Unsnooze(id string) bool { // // Behavior by reason: // - "not_an_issue": Permanent suppression (true false positive in detection logic) -// - "expected_behavior": Acknowledged only (finding stays visible but marked as accepted) -// - "will_fix_later": Acknowledged only (user will address it later) +// - "expected_behavior": Acknowledged only (removed from active list, stays in dismissed history) +// - "will_fix_later": Acknowledged only (removed from active list, stays in dismissed history) // // Rationale: Only true false positives ("not_an_issue") should be permanently suppressed. // For "expected_behavior" and "will_fix_later", the finding stays visible (transparent) @@ -832,12 +832,14 @@ func (s *FindingsStore) ClearAll() int { return count } -// Cleanup removes old resolved findings +// Cleanup removes old resolved findings (and trims stale dismissed history). func (s *FindingsStore) Cleanup(maxAge time.Duration) int { s.mu.Lock() defer s.mu.Unlock() - cutoff := time.Now().Add(-maxAge) + now := time.Now() + cutoff := now.Add(-maxAge) + dismissedCutoff := now.Add(-30 * 24 * time.Hour) removed := 0 for id, f := range s.findings { @@ -848,9 +850,8 @@ func (s *FindingsStore) Cleanup(maxAge time.Duration) int { shouldRemove = true } - // Also remove old suppressed/dismissed findings (they're no longer relevant) - // These are findings the user marked as "fixed" or "not an issue" - after 24h they should disappear - if (f.Suppressed || f.DismissedReason != "") && f.LastSeenAt.Before(cutoff) { + // Trim stale dismissed findings, but retain suppressed ones for memory. + if f.DismissedReason != "" && !f.Suppressed && f.LastSeenAt.Before(dismissedCutoff) { shouldRemove = true } @@ -881,11 +882,6 @@ func (s *FindingsStore) GetDismissedForContext() string { var suppressed, dismissed, snoozed []string for _, f := range s.findings { - // Skip very old findings (more than 30 days) - if time.Since(f.LastSeenAt) > 30*24*time.Hour { - continue - } - // Collect suppressed findings if f.Suppressed { note := "" @@ -897,6 +893,11 @@ func (s *FindingsStore) GetDismissedForContext() string { continue } + // Skip very old findings (more than 30 days) + if time.Since(f.LastSeenAt) > 30*24*time.Hour { + continue + } + // Collect dismissed/acknowledged findings if f.DismissedReason != "" { note := "" diff --git a/internal/api/ai_handlers.go b/internal/api/ai_handlers.go index 1903e42d1..74dc9257e 100644 --- a/internal/api/ai_handlers.go +++ b/internal/api/ai_handlers.go @@ -3663,6 +3663,11 @@ func (h *AISettingsHandler) HandleDismissFinding(w http.ResponseWriter, r *http. return } + if req.Reason == "" { + http.Error(w, "reason is required", http.StatusBadRequest) + return + } + // Validate reason validReasons := map[string]bool{ "not_an_issue": true,