Skip to content

refactor(api): simplify registration and var watcher flow#1934

Merged
elibosley merged 3 commits intomainfrom
codex/simplify-registration-watchers
Mar 19, 2026
Merged

refactor(api): simplify registration and var watcher flow#1934
elibosley merged 3 commits intomainfrom
codex/simplify-registration-watchers

Conversation

@elibosley
Copy link
Member

@elibosley elibosley commented Mar 19, 2026

Summary

  • simplify registration key watcher to a single refresh path
  • reload registration key when var.ini changes in state watcher
  • align watcher tests with the simplified flow

Notes

  • no functional changes outside watcher and watcher test files

Summary by CodeRabbit

  • Tests

    • Updated tests to validate that state/key change handling triggers two ordered refresh actions and to verify watcher behavior and ignore logic.
  • Refactor

    • Simplified reload logic to perform a single sequential refresh (removed retry/backoff).
    • Watcher now serializes file-change events and ignores non-key files while preserving key-file handling.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 19, 2026

Walkthrough

Removed fingerprint/retry logic for registration reloads, replaced with a direct two-step reload (load var state, then load registration). Added a serialized watchQueue for chokidar events and made the watcher ignore non-.key files based on stats.

Changes

Cohort / File(s) Summary
Registration watch implementation
api/src/store/watch/registration-watch.ts
Removed fingerprinting and retry/backoff; changed reloadVarIniWithRetry() to a no-arg function that dispatches loadSingleStateFile(StateFileKey.var) then loadRegistrationKey(). Added watchQueue to serialize chokidar 'all' event handling and updated ignored predicate to ignore only non-.key files when stats is a file.
State watch integration
api/src/store/watch/state-watch.ts
Imported loadRegistrationKey and dispatch it after loadSingleStateFile(StateFileKey.var) so registration data is refreshed when the var state file changes.
Tests — registration watch
api/src/__test__/store/watch/registration-watch.test.ts
Removed fake timers and previous retry/backoff tests; simplified to assert reloadVarIniWithRetry() dispatches loadSingleStateFile(StateFileKey.var) then loadRegistrationKey(). Updated chokidar mock expectations and added assertions that ignored does not ignore the watched directory nor .key files while ignoring other files.
Tests — state watch
api/src/__test__/store/watch/state-watch.test.ts
Added registration module mock and a test asserting that var.ini changes cause loadSingleStateFile(StateFileKey.var) followed by loadRegistrationKey() in order.

Sequence Diagram

sequenceDiagram
    participant FS as File System
    participant Chokidar as Chokidar Watcher
    participant Queue as watchQueue
    participant WatchHandler as Registration Watch Handler
    participant Store as Vuex Store
    participant Registration as Registration Module

    FS->>Chokidar: file event (var.ini or .key)
    Chokidar->>Queue: enqueue event (serialize)
    Queue->>WatchHandler: dequeue -> invoke reloadVarIniWithRetry()
    WatchHandler->>Store: dispatch loadSingleStateFile(StateFileKey.var)
    Store->>Store: update var state
    WatchHandler->>Store: dispatch loadRegistrationKey()
    Store->>Registration: request registration reload
    Registration-->>Store: registration data loaded
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰
I hopped upon a watchful queue,
Two steps I take — var then key,
No more retries in moonlit light,
Events march on in tidy sight,
A small clean hop, and all's set free.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: simplifying the registration and var watcher flow across multiple files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/simplify-registration-watchers
📝 Coding Plan
  • Generate coding plan for human review comments

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

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cd54fb4002

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +56 to +57
if (stateFile === StateFileKey.var) {
await store.dispatch(loadRegistrationKey());

Choose a reason for hiding this comment

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

P2 Badge Avoid reloading the registration key on every var.ini change

var.ini is the general emhttp state file, not a registration-only file: api/src/store/state-parsers/var.ts includes unrelated fields like mdResyncPos, sbEvents, and shareMoverActive, so this branch now dispatches loadRegistrationKey() for routine array/mover updates as well as real license changes. loadRegistrationKey() ultimately reads the key from paths['keyfile-base'] (/boot/config in api/src/store/modules/paths.ts:56), which means steady-state var.ini churn will keep re-reading and re-encoding the flash-resident .key file even when regFile never changed. Before this refactor we only did that work on startup or actual key-file events, so this introduces unnecessary flash I/O in production.

Useful? React with 👍 / 👎.

Copy link
Contributor

@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.

🧹 Nitpick comments (2)
api/src/store/watch/registration-watch.ts (2)

26-27: Silent error swallowing may hide failures.

The .catch(() => undefined) discards any errors from the previous queue entry, including errors from reloadVarIniWithRetry(). This could make debugging difficult if dispatches fail silently. Consider logging errors before discarding:

♻️ Suggested improvement
     watchQueue = watchQueue
-        .catch(() => undefined)
+        .catch((error) => {
+            keyServerLogger.error('Previous key watch handler failed: %o', error);
+        })
         .then(async () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/src/store/watch/registration-watch.ts` around lines 26 - 27, The current
chain assigns watchQueue = watchQueue.catch(() => undefined), which swallows
errors (including those from reloadVarIniWithRetry()) and hides failures; change
this to log the error before returning undefined so failures are visible—e.g.,
replace the empty catch with a handler that logs the caught error (using your
existing logger) and then returns undefined, referencing the watchQueue promise
and reloadVarIniWithRetry() as the implicated operations to locate the code to
update.

34-34: The return statement has no effect on chokidar behavior.

Chokidar's event handlers don't use return values. This return watchQueue; line only serves to make the handler awaitable in tests. Consider adding a brief comment to clarify the intent, so future maintainers don't remove it thinking it's dead code.

📝 Suggested clarification
+        // Return the queue promise to allow testing to await completion
         return watchQueue;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/src/store/watch/registration-watch.ts` at line 34, The return statement
"return watchQueue;" inside the chokidar event handler has no effect on chokidar
but is kept so tests can await the handler; add a short inline comment above or
beside that return explaining this intent (mentioning that chokidar ignores
handler return values and that watchQueue is returned solely to make the handler
awaitable in tests) so future maintainers won't remove it; locate the return in
the registration-watch.ts chokidar callback where watchQueue is referenced and
update the comment there.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@api/src/store/watch/registration-watch.ts`:
- Around line 26-27: The current chain assigns watchQueue = watchQueue.catch(()
=> undefined), which swallows errors (including those from
reloadVarIniWithRetry()) and hides failures; change this to log the error before
returning undefined so failures are visible—e.g., replace the empty catch with a
handler that logs the caught error (using your existing logger) and then returns
undefined, referencing the watchQueue promise and reloadVarIniWithRetry() as the
implicated operations to locate the code to update.
- Line 34: The return statement "return watchQueue;" inside the chokidar event
handler has no effect on chokidar but is kept so tests can await the handler;
add a short inline comment above or beside that return explaining this intent
(mentioning that chokidar ignores handler return values and that watchQueue is
returned solely to make the handler awaitable in tests) so future maintainers
won't remove it; locate the return in the registration-watch.ts chokidar
callback where watchQueue is referenced and update the comment there.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 584b3506-5db0-4111-8eaf-156652df0f2d

📥 Commits

Reviewing files that changed from the base of the PR and between 50726c4 and cd54fb4.

📒 Files selected for processing (4)
  • api/src/__test__/store/watch/registration-watch.test.ts
  • api/src/__test__/store/watch/state-watch.test.ts
  • api/src/store/watch/registration-watch.ts
  • api/src/store/watch/state-watch.ts

@codecov
Copy link

codecov bot commented Mar 19, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 51.52%. Comparing base (dcb3a5b) to head (bbf394e).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1934      +/-   ##
==========================================
- Coverage   51.61%   51.52%   -0.10%     
==========================================
  Files        1021     1021              
  Lines       70477    70446      -31     
  Branches     7763     7804      +41     
==========================================
- Hits        36379    36299      -80     
- Misses      33976    34025      +49     
  Partials      122      122              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Contributor

This plugin has been deployed to Cloudflare R2 and is available for testing.
Download it at this URL:

https://preview.dl.unraid.net/unraid-api/tag/PR1934/dynamix.unraid.net.plg

Copy link
Contributor

@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.

🧹 Nitpick comments (1)
api/src/__test__/store/watch/registration-watch.test.ts (1)

72-79: Assert exact dispatch count to lock the two-step refresh contract.

You already assert order; adding count assertions will also catch accidental extra dispatches in future changes.

Proposed tightening
     await reloadVarIniWithRetry();

+    expect(store.dispatch).toHaveBeenCalledTimes(2);
     expect(store.dispatch).toHaveBeenNthCalledWith(1, loadSingleStateFile(StateFileKey.var));
     expect(store.dispatch).toHaveBeenNthCalledWith(2, loadRegistrationKey());
@@
         await registrationKeyWatchHandler?.('add', '/boot/config/Pro.key');

+        expect(store.dispatch).toHaveBeenCalledTimes(2);
         expect(store.dispatch).toHaveBeenNthCalledWith(1, loadSingleStateFile(StateFileKey.var));
         expect(store.dispatch).toHaveBeenNthCalledWith(2, loadRegistrationKey());

Also applies to: 96-100

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/src/__test__/store/watch/registration-watch.test.ts` around lines 72 -
79, The tests for reloadVarIniWithRetry assert call order but not total dispatch
count, so add an exact dispatch count assertion to prevent extra dispatches:
after invoking reloadVarIniWithRetry() assert store.dispatch was called exactly
twice (e.g., expect(store.dispatch).toHaveBeenCalledTimes(2)) in the test that
currently checks order using loadSingleStateFile(StateFileKey.var) and
loadRegistrationKey(); apply the same tightening to the other similar test that
checks the two-step refresh around loadRegistrationKey to ensure both order and
exact call count.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@api/src/__test__/store/watch/registration-watch.test.ts`:
- Around line 72-79: The tests for reloadVarIniWithRetry assert call order but
not total dispatch count, so add an exact dispatch count assertion to prevent
extra dispatches: after invoking reloadVarIniWithRetry() assert store.dispatch
was called exactly twice (e.g., expect(store.dispatch).toHaveBeenCalledTimes(2))
in the test that currently checks order using
loadSingleStateFile(StateFileKey.var) and loadRegistrationKey(); apply the same
tightening to the other similar test that checks the two-step refresh around
loadRegistrationKey to ensure both order and exact call count.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fdf48828-efba-4607-89e2-09d46a8d0c2a

📥 Commits

Reviewing files that changed from the base of the PR and between 3b1033e and bbf394e.

📒 Files selected for processing (1)
  • api/src/__test__/store/watch/registration-watch.test.ts

@elibosley elibosley merged commit 70890ab into main Mar 19, 2026
12 of 13 checks passed
@elibosley elibosley deleted the codex/simplify-registration-watchers branch March 19, 2026 18: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

Development

Successfully merging this pull request may close these issues.

1 participant