Skip to content

Fix frontend bugs/UX bucket from audit (#531)#538

Merged
nitrobass24 merged 9 commits into
developfrom
fix/frontend-bugs-bucket-531
Jun 3, 2026
Merged

Fix frontend bugs/UX bucket from audit (#531)#538
nitrobass24 merged 9 commits into
developfrom
fix/frontend-bugs-bucket-531

Conversation

@nitrobass24
Copy link
Copy Markdown
Owner

@nitrobass24 nitrobass24 commented Jun 2, 2026

Fixes the 🖥️ Frontend bugs / UX bucket of tracking issue #531 (4 issues) in one branch, the sibling to the backend bucket (#537). Each fix has its own commit and Vitest tests.

Note

Intentional grouping. This bundles 4 issues, deviating from CLAUDE.md's "one concern per branch" rule — done deliberately at the maintainer's request for a single grouped review. Commits are one-per-issue (plus review-polish commits).

How this was built

A multi-agent workflow: parallel investigation (one agent per issue, re-verifying each finding against current Angular code), sequential implementation (ordered so the two issues sharing file-list.component.ts#513, #515 — ran adjacently and conflict-free; each = fix + Vitest tests + its own commit, validated with ng test + ng lint --max-warnings 0), then parallel adversarial review. The review surfaced gaps, which were then addressed (below).

Issues fixed

# Sev Fix
#513 High Single-file actions (Queue/Stop/Extract/Validate/Delete) now surface backend failures as a NotificationService DANGER banner, and a failed action no longer leaves the row's buttons/spinner stuck (activeAction is reset).
#514 High Auth-enabled init deadlock fixed: config is bootstrapped independent of the SSE stream (the UI renders instead of hanging on "connecting"), the redacted sentinel is never sent as a credential, and reconnect uses capped exponential backoff. (The key is not persisted client-side — see the review note below; after a reload the user re-enters it in Settings.)
#515 Medium Bulk Delete Local/Remote now require the same two-click confirm (3s reset) as the per-file action — no more one-misclick data loss off the seedbox.
#516 Low JSON.parse guarded across the SSE/autoqueue handlers; log-history fetch failure shows a distinct "failed to load" state (not an empty result); path-pair enable/auto-queue toggle failures set an error banner and refresh() to reconcile the UI.

Review findings → resolution

Adversarial review flagged two items, both resolved in this PR (no follow-ups):

#513 and #515 passed review with all acceptance criteria met.

Test plan

  • ✅ Full Angular unit suite (Vitest): 464 passed (37 files), up from 430 on develop.
  • npx ng lint --max-warnings 0: clean.
  • New Vitest tests accompany every fix (happy path + failure/edge paths), including deterministic error-surfacing, reconnect backoff, the redacted-sentinel guard, two-click confirm, malformed-JSON fallback, and the distinct log-history failure state.

Security review (CodeQL)

CodeQL flagged an initial #514 approach that persisted web.api_key in sessionStorage as high-severity clear-text storage of a credential. Since there is no safe way to auto-recover the key after a reload (the auth-exempt /server/config/get redacts it, and an authenticated endpoint recreates the deadlock), the client-side persistence was dropped. #514 still fixes the core deadlock (the UI renders rather than hanging forever); a reload on an auth-enabled instance simply needs the key re-entered in Settings. No credential is stored client-side.

Closes #513. Closes #514. Closes #515. Closes #516.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added inline delete confirmation to bulk actions—click once to arm confirmation, click again to execute deletion.
    • Added visual error banner when log history fails to load.
  • Bug Fixes

    • Actions now display error notifications when they fail; UI buttons reset appropriately on errors.
    • Improved network reconnection with exponential backoff and jitter.
    • App now gracefully handles malformed data from the server without crashing; errors are logged for troubleshooting.
  • Improvements

    • Better error recovery for settings updates with automatic state reconciliation.

nitrobass24 and others added 6 commits June 2, 2026 14:25
…sist api key (#514)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The #514 fix declared two local copies of the '********' sentinel; import the
existing exported REDACTED_SENTINEL from models/config instead, so the
backend-coupled value lives in one place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-pair toggle errors

Review found the #516 commit only covered the SSE-handler JSON.parse; the issue's
other acceptance criteria were unaddressed. Complete them:
- autoqueue.service: guard the patterns JSON.parse (log + empty fallback, no throw)
- logs page: distinguish a history-fetch failure from an empty result via a
  historyLoadFailed flag + a distinct "failed to load" template state
- path-pairs: enable/auto-queue toggle failures now set an error banner and
  refresh() to reconcile the one-way-bound checkbox with the server
Tests added for each.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: df73d965-b4aa-4093-9b80-b9555f66fd04

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This pull request refactors Angular file-action handlers to emit event objects with callbacks, adds inline delete confirmations, guards SSE-payload parsing across multiple services, implements exponential backoff for stream reconnects, and ensures API key values persist without being overwritten by redaction sentinels.

Changes

File actions with event payloads and parent error handling

Layer / File(s) Summary
File action event contract and parent error handling
src/angular/src/app/pages/files/file.component.ts, src/angular/src/app/pages/files/file.component.spec.ts, src/angular/src/app/pages/files/file-list.component.ts, src/angular/src/app/pages/files/file-list.component.spec.ts
FileComponent exports FileActionEvent (file + callback) and emits it from all action outputs; FileListComponent consumes events, centralizes success/failure handling, shows danger notifications, and invokes the reset callback on failure to clear child UI state.
Bulk delete two-step inline confirmation
src/angular/src/app/pages/files/bulk-action-bar.component.ts, src/angular/src/app/pages/files/bulk-action-bar.component.spec.ts
BulkActionBarComponent now implements a two-step confirmation: first click arms the state and displays "Confirm?"; second click emits the delete event; a 3-second timeout or ngOnDestroy resets confirmation without emitting.
Logs history load failure tracking
src/angular/src/app/pages/logs/logs-page.component.ts, src/angular/src/app/pages/logs/logs-page.component.html, src/angular/src/app/pages/logs/logs-page.component.scss, src/angular/src/app/pages/logs/logs-page.component.spec.ts
LogsPageComponent tracks historyLoadFailed separately from empty results, logs fetch errors, and renders a failure message; tests verify the distinct states.
Path pair toggle error recovery
src/angular/src/app/pages/settings/path-pairs.component.ts, src/angular/src/app/pages/settings/path-pairs.component.spec.ts
Toggle handlers now share an applyToggle helper that clears prior errors on success and triggers refresh() after failed or null updates.
Malformed SSE payload guards
src/angular/src/app/services/autoqueue/autoqueue.service.ts, src/angular/src/app/services/logs/log.service.ts, src/angular/src/app/services/server/server-status.service.ts, src/angular/src/app/services/files/model-file.service.ts, src/angular/src/app/services/*/\*.spec.ts
JSON parsing is wrapped in try/catch across AutoQueue, Log, ServerStatus, and ModelFile services; errors are logged via LoggerService and state/emissions are preserved or emptied instead of throwing.

Stream backoff and configuration API key handling

Layer / File(s) Summary
Exponential backoff with jitter for stream reconnects
src/angular/src/app/services/base/stream-dispatch.service.ts, src/angular/src/app/services/base/stream-dispatch.service.spec.ts
StreamDispatchService now computes the next reconnect delay using capped exponential backoff (base × 2^attempt) plus random jitter; retryAttempt resets on successful open or setApiKey, ensuring fast reconnects after transient failures.
ConfigService API key flow and sentinel protection
src/angular/src/app/services/settings/config.service.ts, src/angular/src/app/services/utils/api-key.interceptor.ts, src/angular/src/app/services/settings/config.service.spec.ts, src/angular/src/app/services/utils/api-key.interceptor.spec.ts
ConfigService fetches config at startup, avoids overwriting stream API keys with the REDACTED_SENTINEL placeholder, and syncs real API key values on successful set; the interceptor detects the sentinel and suppresses it from X-Api-Key headers.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #516: Malformed JSON handling in SSE payloads and settings-toggle error recovery are directly addressed by guarded parsing and the applyToggle refactor.
  • #513: File-list action handlers now inspect WebReaction, call NotificationService on failures, invoke clearActiveAction callbacks, and use centralized failAction/handleActionResponse/handleActionError paths.

Possibly related PRs

  • nitrobass24/seedsync#136: Both PRs modify ConfigService initialization timing, syncStreamApiKey behavior, and handling of disconnect/config parsing around API key propagation.
  • nitrobass24/seedsync#245: Both PRs add or refine unit test coverage for StreamDispatchService reconnection, ConfigService API-key flow, and ApiKeyInterceptor header attachment behavior.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly summarizes the main change—fixing frontend bugs from an audit—and is directly supported by the changeset which spans multiple bug fixes (#513-#516) across files, styling, and test coverage.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/frontend-bugs-bucket-531

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread src/angular/src/app/services/settings/config.service.ts Fixed
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/angular/src/app/services/files/model-file.service.ts (1)

87-148: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard the entire SSE event handling in model-file.service.ts (not just JSON.parse).

modelFileFromJson iterates json.children and reads other fields without validation, so valid JSON with the wrong shape (e.g., {} for model-init, or missing new_file/old_file) can throw during the mapping/update logic. Unlike LogService and ServerStatusService (which catch both parse + *FromJson mapping), ModelFileService.parseEvent() only catches JSON.parse, and StreamDispatchService calls onEvent(...) without a try/catch, so the exception can escape the SSE listener. Wrap the event-specific processing (init/added/removed/updated) in the same try/catch and log/skip bad events.

🛡️ Proposed fix: wrap event processing in the same guard
     let parsed: unknown;
     try {
       parsed = JSON.parse(data);
     } catch (e) {
       this.logger.error('Failed to parse %s event: %O', name, e);
       return;
     }

+    try {
       if (name === this.EVENT_INIT) {
         const init = parsed as ModelFileJson[];
         // ...
       } else if (name === this.EVENT_ADDED) {
         // ...
       } else if (name === this.EVENT_REMOVED) {
         // ...
       } else if (name === this.EVENT_UPDATED) {
         // ...
       } else {
         this.logger.error('Unrecognized event:', name);
       }
+    } catch (e) {
+      this.logger.error('Failed to handle %s event: %O', name, e);
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/angular/src/app/services/files/model-file.service.ts` around lines 87 -
148, The SSE handling currently only wraps JSON.parse but not the downstream
mapping, so calls to modelFileFromJson (and subsequent logic using fileKey,
EVENT_INIT, EVENT_ADDED, EVENT_REMOVED, EVENT_UPDATED) can throw on
malformed-but-parseable payloads; update parseEvent() (or the method handling
the SSE data) to wrap the entire switch/if-block that handles name ===
EVENT_INIT / EVENT_ADDED / EVENT_REMOVED / EVENT_UPDATED in the same try/catch
used for JSON.parse, so any exception from modelFileFromJson or the map updates
is caught, logged via this.logger.error with the event name and error, and the
handler returns/continues without mutating filesSubject or letting the error
escape. Ensure logs mirror existing style and do not change behavior for valid
events.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/angular/src/app/pages/logs/logs-page.component.html`:
- Line 29: The error message paragraph (<p class="record error">Failed to load
log history. Check the connection and try again.</p>) is currently only visual;
update that element in logs-page.component.html to be announced to assistive
tech by adding an appropriate accessibility attribute such as role="alert" or
aria-live="assertive" (or aria-live="polite" if less urgent) so screen readers
will notify users when the fetch fails.

In `@src/angular/src/app/pages/logs/logs-page.component.spec.ts`:
- Around line 44-60: Add a spec that exercises the real fetch-failure path by
spying on the service method used by the component (spyOn(LogService,
'fetchHistory') or the actual service instance used in the test) to return an
observable that errors, then run the component's fetch trigger under fakeAsync
and advance time past the component's debounce interval with tick(...), call
fixture.detectChanges(), and assert that renderedText() contains 'Failed to load
log history' and that component.historyLoadFailed is true; reference the
component.fetchHistory()/ngOnInit() flow and the LogService.fetchHistory spy so
the test fails if the catchError pipeline stops setting the failure flag.
- Around line 19-29: Tests fail because LogsPageComponent now injects
LoggerService but the TestBed configuration doesn't mock it; update the
TestBed.configureTestingModule providers to include a mocked LoggerService
(useValue with vi.fn()-backed methods used by the component) so the spec uses an
explicit vi.fn() mock instead of the real root service; reference
LogsPageComponent and LoggerService in the TestBed.configureTestingModule call
and add the minimal mocked methods the component calls (e.g., log/debug/info or
any method names used) to the mock's useValue.

In `@src/angular/src/app/services/base/stream-dispatch.service.spec.ts`:
- Around line 271-282: The test currently mocks Math.random() to 1 which is
impossible in production because nextRetryDelayMs() calculates jitter as
Math.random() * RETRY_JITTER_MS; change the test to mock Math.random() to a
value just below 1 (e.g., 0.999) so jitter is < RETRY_JITTER_MS and then adjust
the timer expectations to match base delay + jitter (use service.start(),
latestEventSource(), and MockEventSource.instances to verify reconnection
timing); ensure references to nextRetryDelayMs and RETRY_JITTER_MS are
considered when computing the new advanceTimersByTime values.

In `@src/angular/src/app/services/files/model-file.service.spec.ts`:
- Around line 233-303: Add tests that cover valid-JSON but wrong-shape payloads
(not just invalid JSON) for the model-file SSE handlers: call
service.onEvent("model-init", "{}") and service.onEvent("model-init", "[]") and
assert they don't throw, that mockLogger.error was called, and that files$ state
is preserved; similarly call service.onEvent("model-added", JSON.stringify({}))
and service.onEvent("model-updated", JSON.stringify({})) and
service.onEvent("model-removed", JSON.stringify({})) (or payloads missing
new_file/old_file fields) and assert no throw, error logged, and map unchanged
(use makeFileJson to pre-seed state where needed); also add a test showing a
subsequent valid model-init/model-added still parses correctly after a
wrong-shaped payload to ensure no poisoned state.

In `@src/angular/src/app/services/settings/config.service.spec.ts`:
- Around line 122-127: The test replaces sessionStorage using
Object.defineProperty(globalThis, "sessionStorage", ...) but only saved and
restored the value (originalSessionStorage), which loses the original property
descriptor (accessor vs data property); capture the full original descriptor
with Object.getOwnPropertyDescriptor(globalThis, "sessionStorage") and use
Object.defineProperty to restore that original descriptor in afterEach; when
installing the mock, you can still define the mock via Object.defineProperty but
ensure you record the originalDescriptor variable and restore it (do the same
fix for the second hook around lines 154-159), referencing the existing
originalSessionStorage/mockSessionStorage and the
Object.defineProperty(globalThis, "sessionStorage", ...) calls to locate where
to change.
- Around line 246-259: Replace the hardcoded redaction literal "********" in
this spec with the shared constant REDACTED_SENTINEL from
src/angular/src/app/models/config.ts; update the two expectations that reference
"********" (the negative assertion on mockStreamDispatch.setApiKey and any other
literal checks in this test) to use REDACTED_SENTINEL so the test uses the
canonical sentinel, leaving other references (StorageKeys.API_KEY,
mockStreamDispatch.setApiKey, createService) unchanged.

In `@src/angular/src/app/services/settings/config.service.ts`:
- Around line 96-102: Do not persist the raw API key in client-side storage:
stop writing the real key into sessionStorage in the block that handles section
=== 'web' && option === 'api_key' by removing or changing the call to
writePersistedApiKey(realKey); instead either (a) send the real key to the
backend to mint a short-lived session credential or HttpOnly cookie and persist
that, or (b) persist only a redacted/hashed identifier (not the secret) and
continue to call setStreamApiKey(realKey) in-memory; update or remove
writePersistedApiKey so it no longer stores the unredacted key, and add
server-side logic to return a recoverable session token if reload recovery is
required.

---

Outside diff comments:
In `@src/angular/src/app/services/files/model-file.service.ts`:
- Around line 87-148: The SSE handling currently only wraps JSON.parse but not
the downstream mapping, so calls to modelFileFromJson (and subsequent logic
using fileKey, EVENT_INIT, EVENT_ADDED, EVENT_REMOVED, EVENT_UPDATED) can throw
on malformed-but-parseable payloads; update parseEvent() (or the method handling
the SSE data) to wrap the entire switch/if-block that handles name ===
EVENT_INIT / EVENT_ADDED / EVENT_REMOVED / EVENT_UPDATED in the same try/catch
used for JSON.parse, so any exception from modelFileFromJson or the map updates
is caught, logged via this.logger.error with the event name and error, and the
handler returns/continues without mutating filesSubject or letting the error
escape. Ensure logs mirror existing style and do not change behavior for valid
events.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1387d272-a733-4806-890b-bb4339a11981

📥 Commits

Reviewing files that changed from the base of the PR and between 416047c and db22d89.

📒 Files selected for processing (26)
  • src/angular/src/app/common/storage-keys.ts
  • src/angular/src/app/pages/files/bulk-action-bar.component.spec.ts
  • src/angular/src/app/pages/files/bulk-action-bar.component.ts
  • src/angular/src/app/pages/files/file-list.component.spec.ts
  • src/angular/src/app/pages/files/file-list.component.ts
  • src/angular/src/app/pages/files/file.component.spec.ts
  • src/angular/src/app/pages/files/file.component.ts
  • src/angular/src/app/pages/logs/logs-page.component.html
  • src/angular/src/app/pages/logs/logs-page.component.spec.ts
  • src/angular/src/app/pages/logs/logs-page.component.ts
  • src/angular/src/app/pages/settings/path-pairs.component.spec.ts
  • src/angular/src/app/pages/settings/path-pairs.component.ts
  • src/angular/src/app/services/autoqueue/autoqueue.service.spec.ts
  • src/angular/src/app/services/autoqueue/autoqueue.service.ts
  • src/angular/src/app/services/base/stream-dispatch.service.spec.ts
  • src/angular/src/app/services/base/stream-dispatch.service.ts
  • src/angular/src/app/services/files/model-file.service.spec.ts
  • src/angular/src/app/services/files/model-file.service.ts
  • src/angular/src/app/services/logs/log.service.spec.ts
  • src/angular/src/app/services/logs/log.service.ts
  • src/angular/src/app/services/server/server-status.service.spec.ts
  • src/angular/src/app/services/server/server-status.service.ts
  • src/angular/src/app/services/settings/config.service.spec.ts
  • src/angular/src/app/services/settings/config.service.ts
  • src/angular/src/app/services/utils/api-key.interceptor.spec.ts
  • src/angular/src/app/services/utils/api-key.interceptor.ts

Comment thread src/angular/src/app/pages/logs/logs-page.component.html Outdated
Comment thread src/angular/src/app/pages/logs/logs-page.component.spec.ts
Comment thread src/angular/src/app/pages/logs/logs-page.component.spec.ts
Comment thread src/angular/src/app/services/base/stream-dispatch.service.spec.ts
Comment thread src/angular/src/app/services/files/model-file.service.spec.ts
Comment thread src/angular/src/app/services/settings/config.service.spec.ts Outdated
Comment thread src/angular/src/app/services/settings/config.service.spec.ts Outdated
Comment thread src/angular/src/app/services/settings/config.service.ts Outdated
nitrobass24 and others added 2 commits June 2, 2026 15:39
CodeQL flagged the #514 sessionStorage persistence of web.api_key as
high-severity clear-text storage of sensitive information. There is no safe way
to auto-recover the key after a reload (the auth-exempt /server/config/get
redacts it; an authenticated endpoint recreates the deadlock), so drop the
client-side persistence entirely.

Kept: the core deadlock fix — config is still bootstrapped independent of the
SSE stream (UI renders instead of hanging), the redacted sentinel is never sent
as a credential, and reconnect backoff stays. After a reload on an auth-enabled
instance the user re-enters the key in Settings (which re-pushes it to the
stream). Removed the sessionStorage read/write, StorageKeys.API_KEY, and the
interceptor's persisted-key lookup, plus the persistence tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The #516 "failed to load" banner reused class="record error", which the
Playwright log-record selector (#logs .record) matched — so when the history
fetch errored in CI, the banner was read as a log record (and an ERROR-level
one), breaking logs E2E tests. Give it a dedicated history-load-error class so
it never collides with actual log-record queries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@nitrobass24
Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/angular/src/app/services/settings/config.service.ts (1)

102-113: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve the in-memory API key across config refreshes.

Line 105 replaces configSubject with the redacted /server/config/get payload. After set("web", "api_key", "real-key"), the next successful refresh stores REDACTED_SENTINEL in configSnapshot, so apiKeyInterceptor stops sending X-Api-Key on every later /server/* request. Lines 109-113 have the same downstream effect on transient refresh failures by dropping the snapshot to null. Keep the last known non-redacted key in memory when publishing refresh results, or store it separately from the fetched config.

Proposed fix
   private getConfig(): void {
     this.logger.debug('Getting config...');
     this.restService.sendRequest(this.CONFIG_GET_URL).subscribe({
       next: (reaction) => {
+        const previousConfig = this.configSubject.getValue();
+        const previousApiKey = previousConfig?.web?.api_key;
+
         if (reaction.success) {
           try {
             const configJson: Config = JSON.parse(reaction.data!);
-            this.configSubject.next(configJson);
-            this.syncStreamApiKey(configJson);
+            const nextConfig =
+              configJson.web.api_key === REDACTED_SENTINEL &&
+              previousApiKey &&
+              previousApiKey !== REDACTED_SENTINEL
+                ? {
+                    ...configJson,
+                    web: { ...configJson.web, api_key: previousApiKey },
+                  }
+                : configJson;
+
+            this.configSubject.next(nextConfig);
+            this.syncStreamApiKey(nextConfig);
           } catch (e) {
             this.logger.error('Failed to parse config: %O', e);
-            this.configSubject.next(null);
+            if (!previousConfig) {
+              this.configSubject.next(null);
+            }
           }
         } else {
           this.logger.error('Failed to get config: %s', reaction.errorMessage);
-          this.configSubject.next(null);
+          if (!previousConfig) {
+            this.configSubject.next(null);
+          }
         }
       },
     });
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/angular/src/app/services/settings/config.service.ts` around lines 102 -
113, The service currently overwrites the in-memory config/api key on every
refresh (in the block handling reaction.success and the else branch), causing
the real API key to be lost when the server returns REDACTED_SENTINEL or on
transient failures; modify how configSubject and syncStreamApiKey handle the api
key by preserving the lastKnownApiKey separately (e.g., a private
lastKnownApiKey field) or by changing syncStreamApiKey to only update the stored
key when configJson.web.api_key is a real value (not REDACTED_SENTINEL/null),
and on non-success responses avoid clearing the stored snapshot/key (keep the
previous configSubject value) so apiKeyInterceptor continues sending the real
X-Api-Key.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/angular/src/app/services/settings/config.service.spec.ts`:
- Around line 203-214: Add a regression test that exercises the sequence
set("web","api_key","new-key") followed by a refresh response containing
REDACTED_SENTINEL: call the service's set() helper to push "new-key", then
mockRestService.sendRequest to return a successful get with data containing the
redacted sentinel and call createService()/trigger the refresh; assert that
mockStreamDispatch.setApiKey was called with "new-key" and not with
REDACTED_SENTINEL and that the service's config snapshot (used by
apiKeyInterceptor) still retains the real key; reference the set() helper,
REDACTED_SENTINEL, mockRestService.sendRequest, createService,
mockStreamDispatch.setApiKey and the configSnapshot/apiKeyInterceptor in the
test.

---

Outside diff comments:
In `@src/angular/src/app/services/settings/config.service.ts`:
- Around line 102-113: The service currently overwrites the in-memory config/api
key on every refresh (in the block handling reaction.success and the else
branch), causing the real API key to be lost when the server returns
REDACTED_SENTINEL or on transient failures; modify how configSubject and
syncStreamApiKey handle the api key by preserving the lastKnownApiKey separately
(e.g., a private lastKnownApiKey field) or by changing syncStreamApiKey to only
update the stored key when configJson.web.api_key is a real value (not
REDACTED_SENTINEL/null), and on non-success responses avoid clearing the stored
snapshot/key (keep the previous configSubject value) so apiKeyInterceptor
continues sending the real X-Api-Key.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f6d67d38-7092-4061-8f01-d24de41beafe

📥 Commits

Reviewing files that changed from the base of the PR and between db22d89 and 257d480.

📒 Files selected for processing (6)
  • src/angular/src/app/pages/logs/logs-page.component.html
  • src/angular/src/app/pages/logs/logs-page.component.scss
  • src/angular/src/app/services/settings/config.service.spec.ts
  • src/angular/src/app/services/settings/config.service.ts
  • src/angular/src/app/services/utils/api-key.interceptor.spec.ts
  • src/angular/src/app/services/utils/api-key.interceptor.ts

Comment thread src/angular/src/app/services/settings/config.service.spec.ts
- config.service: a redacted /server/config/get refresh no longer clobbers the
  real api_key in the snapshot, which the apiKeyInterceptor reads for REST auth
  (would break authed REST calls mid-session). Preserve the real key when the
  refresh returns the sentinel. + regression test.
- model-file.service: the SSE guard wrapped only JSON.parse; a valid-JSON but
  wrong-shape event (model-init not an array, model-added missing new_file) still
  threw in the mapping. Guard the dispatch too (log + skip). + wrong-shape tests.
- logs page: role="alert" on the history-load error banner (a11y announcement).
- logs spec: mock LoggerService in TestBed; add a real debounced-pipe
  fetch-failure test (fake timers) that fails if catchError stops setting the flag.
- config spec: use the REDACTED_SENTINEL constant instead of literal "********".
- stream-dispatch spec: Math.random() mocked to 0.999 (it's [0,1), never 1).

Skipped (already resolved by the earlier persistence removal): the CodeQL
clear-text-storage alert, "don't persist the API key", and the sessionStorage
descriptor restore — that code path no longer exists.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@nitrobass24 nitrobass24 merged commit 1f0db28 into develop Jun 3, 2026
18 checks passed
@nitrobass24 nitrobass24 deleted the fix/frontend-bugs-bucket-531 branch June 3, 2026 01:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants