Skip to content

Feat sunrise sunset delay#2474

Open
cicoub13 wants to merge 8 commits intoGladysAssistant:masterfrom
cicoub13:feat-sunrise-sunset-delay
Open

Feat sunrise sunset delay#2474
cicoub13 wants to merge 8 commits intoGladysAssistant:masterfrom
cicoub13:feat-sunrise-sunset-delay

Conversation

@cicoub13
Copy link
Copy Markdown
Contributor

@cicoub13 cicoub13 commented Mar 2, 2026

Pull Request check-list

To ensure your Pull Request can be accepted as fast as possible, make sure to review and check all of these items:

  • If your changes affect the code, did you write the tests?
  • Are tests passing? (npm test on both front/server)
  • Is the linter passing? (npm run eslint on both front/server)
  • Did you run prettier? (npm run prettier on both front/server)
  • Did you test this pull request in real life? With real devices? If this development is a big feature or a new service, we recommend that you provide a Docker image to the community (forum) for testing before merging.
  • If you are adding a new features/services which needs explanation, did you modify the user documentation? See the GitHub repo and the website.

Add offset before/after sunrise/sunset in scene trigger

Capture d’écran 2026-03-02 à 09 06 42 Capture d’écran 2026-03-02 à 09 06 48

Summary by CodeRabbit

  • New Features

    • Sunrise/sunset triggers support configurable minute offsets (exact/before/after) with UI controls and localizations added (EN/DE/FR).
  • Bug Fixes / Improvements

    • Scheduler now creates offset-aware jobs, deduplicates by offset, and includes offset in emitted events.
    • Trigger matching requires offsets to match to prevent incorrect activations.
  • Tests

    • Added coverage for scheduling, deduplication, offset validation, and offset-sensitive trigger matching.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds offset-aware sunrise/sunset triggers: frontend UI to select direction and minutes, trigger schema gains integer offset, scheduler creates per-(house,type,offset) jobs and emits events with offset, matcher requires offset equality, translations and multiple tests updated.

Changes

Cohort / File(s) Summary
Translations
front/src/config/i18n/de.json, front/src/config/i18n/en.json, front/src/config/i18n/fr.json
Added triggersCard.sunriseSunsetTrigger keys: offsetLabel, atExactTime, before, after, minutes.
Frontend Sunrise/Sunset UI
front/src/routes/scene/edit-scene/triggers/SunriseSunsetTrigger.jsx
Added offsetMinutesInput state, onOffsetDirectionChange and onOffsetMinutesChange handlers; renders direction selector and conditional minutes input; persists trigger offset.
Trigger Schema
server/models/scene.js
Added optional integer offset to trigger validation schema.
Daily Scheduling
server/lib/scene/scene.dailyUpdate.js
Now builds timezone-aware sunrise/sunset bases, collects distinct offsets per house/type, schedules one job per (house,type,offset) using base+offset, logs per-offset scheduling and includes offset in emitted events.
Trigger Matching
server/lib/scene/scene.triggers.js
Matching for EVENTS.TIME.SUNRISE/SUNSET now requires house selector AND normalized offsets to be equal `(event.offset
Scene add/destroy hooks
server/lib/scene/scene.addScene.js, server/lib/scene/scene.destroy.js
Added hasSunriseSunsetTrigger helper and call dailyUpdate() when a scene with (or previously with) sunrise/sunset triggers is added/removed.
Tests — removed
server/test/lib/scene/scene.checkTrigger.test.js
Removed two legacy tests that previously asserted sunrise/sunset triggers fired without offset matching.
Tests — added/updated
server/test/lib/scene/scene.dailyUpdate.test.js, server/test/lib/scene/scene.addScene.test.js, server/test/lib/scene/scene.destroy.test.js, server/test/lib/scene/triggers/scene.trigger.sunriseSunset.test.js
Added extensive tests for per-offset scheduling, emission payload offsets, invalid/out-of-range offsets, deduplication, dailyUpdate invocation on add/destroy, and trigger matching behavior (offset equality).

Sequence Diagram

sequenceDiagram
    participant User as User
    participant UI as Frontend UI
    participant Scene as Scene Model
    participant Scheduler as Daily Scheduler
    participant EventBus as Event Bus
    participant Matcher as Trigger Matcher
    participant Queue as Action Queue

    User->>UI: Configure trigger (direction + minutes)
    UI->>Scene: Save trigger with offset
    Scheduler->>Scene: Collect active triggers & offsets
    Scheduler->>Scheduler: Deduplicate offsets per house/type
    loop For each (house, type, offset)
        Scheduler->>Scheduler: Compute base time + offset
        Scheduler->>Scheduler: Schedule job
        Scheduler->>EventBus: Emit sunrise/sunset event (includes offset)
        EventBus->>Matcher: Deliver event
        Matcher->>Matcher: Check house selector AND offset match
        alt offsets match
            Matcher->>Queue: Enqueue trigger actions
        else mismatch
            Matcher-->>EventBus: Skip
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped at dawn and twitched my nose,

I count the minutes, before or close,
Jobs wake up with tiny shifts,
Offsets tucked in morning drifts,
Scenes dance when the sunlight knows.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Feat sunrise sunset delay' is partially related to the changeset, referring to a real aspect of the change (sunrise/sunset offset/delay feature), but uses vague terminology ('delay' instead of 'offset') and doesn't convey the full scope of the changes (which include multi-language i18n, scheduling logic, and validation). Consider a more descriptive title like 'Add sunrise/sunset trigger offset support' or 'Support configurable delays for sunrise/sunset triggers' to better convey the feature scope.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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

🧹 Nitpick comments (5)
server/models/scene.js (1)

119-119: Consider bounding the offset range.

Line 119 accepts any integer, including extreme values that can shift execution far outside expected sunrise/sunset behavior. A bounded range (for example ±1440 minutes) would make this safer.

♻️ Suggested schema guard
-    offset: Joi.number().integer(),
+    offset: Joi.number().integer().min(-1440).max(1440),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/models/scene.js` at line 119, The Joi schema for the scene model
currently allows any integer for the offset field (symbol: offset in
server/models/scene.js); restrict its range by updating the schema validation to
a bounded integer (for example use .min(-1440).max(1440)) so offsets cannot
exceed ±1440 minutes, and run/update any tests that exercise scene validation to
ensure the new bounds are enforced without breaking callers expecting the
previous behavior.
server/test/lib/scene/scene.dailyUpdate.test.js (1)

185-192: Use shared constants in the dedup test trigger type.

Line 185 and Line 191 use 'time.sunrise' directly. Prefer EVENTS.TIME.SUNRISE for consistency and safer refactors.

🔧 Small consistency patch
-      triggers: [{ type: 'time.sunrise', house: 'house-1', offset: 30 }],
+      triggers: [{ type: EVENTS.TIME.SUNRISE, house: 'house-1', offset: 30 }],
...
-      triggers: [{ type: 'time.sunrise', house: 'house-1', offset: 30 }],
+      triggers: [{ type: EVENTS.TIME.SUNRISE, house: 'house-1', offset: 30 }],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/test/lib/scene/scene.dailyUpdate.test.js` around lines 185 - 192,
Replace the literal trigger type strings 'time.sunrise' in the dedup tests by
using the shared constant EVENTS.TIME.SUNRISE to keep consistency and enable
safe refactors; locate the two addScene calls on the test where triggers are
defined (the ones passing triggers: [{ type: 'time.sunrise', ... }]) and update
the type property to EVENTS.TIME.SUNRISE, ensuring EVENTS is imported or
available in the test scope alongside the sceneManager/addScene usage.
server/lib/scene/scene.dailyUpdate.js (1)

52-57: Normalize offsets before adding to sets to avoid mixed-type duplicates.

Line 52 currently accepts raw trigger.offset values as-is. Normalizing to finite integers makes scheduling more robust against malformed persisted values.

🛡️ Defensive normalization
-          const offset = trigger.offset || 0;
+          const parsedOffset = Number(trigger.offset ?? 0);
+          if (!Number.isFinite(parsedOffset)) {
+            return;
+          }
+          const offset = Math.trunc(parsedOffset);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/lib/scene/scene.dailyUpdate.js` around lines 52 - 57, Normalize
trigger.offset to a finite integer before adding to sunriseOffsets or
sunsetOffsets to avoid mixed-type duplicates and malformed values: inside the
conditional that checks trigger.type against EVENTS.TIME.SUNRISE /
EVENTS.TIME.SUNSET and trigger.house === house.selector, coerce trigger.offset
(e.g., via Number.parseInt or Math.trunc after Number()) and validate
Number.isFinite on the result (default to 0 or skip if invalid), then add that
normalized integer to sunriseOffsets or sunsetOffsets instead of the raw
trigger.offset.
server/test/lib/scene/scene.checkTrigger.test.js (1)

540-577: Add the positive sunset negative-offset match case.

You already test offset=-15 mismatch at Line 540. Add the matching case (event.offset = -15) to complete the regression matrix.

✅ Suggested companion test
+  it('should execute scene, sunset trigger with offset=-15 when event has offset=-15', async () => {
+    sceneManager.addScene({
+      selector: 'my-scene',
+      active: true,
+      actions: [[{ type: ACTIONS.LIGHT.TURN_OFF, devices: ['light-1'] }]],
+      triggers: [{ type: EVENTS.TIME.SUNSET, house: 'house-1', offset: -15 }],
+    });
+    sceneManager.checkTrigger({
+      type: EVENTS.TIME.SUNSET,
+      house: { selector: 'house-1' },
+      offset: -15,
+    });
+    return new Promise((resolve, reject) => {
+      sceneManager.queue.start(() => {
+        try {
+          assert.calledOnce(device.setValue);
+          resolve();
+        } catch (e) {
+          reject(e);
+        }
+      });
+    });
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/test/lib/scene/scene.checkTrigger.test.js` around lines 540 - 577, Add
a positive test case mirroring the existing negative-offset mismatch: create a
scene via sceneManager.addScene with a sunset trigger offset: -15 (same as the
current test), then call sceneManager.checkTrigger with type:
EVENTS.TIME.SUNSET, house: { selector: 'house-1' }, and offset: -15 (matching
the scene). In the returned Promise where sceneManager.queue.start(...) runs,
assert that device.setValue was called (or calledOnce) to confirm the scene
executed; reuse the same structure and identifiers (sceneManager.addScene,
sceneManager.checkTrigger, sceneManager.queue.start, device.setValue) as the
existing test.
front/src/routes/scene/edit-scene/triggers/SunriseSunsetTrigger.jsx (1)

56-62: Keep offsetMinutesInput synchronized when trigger.offset changes externally.

Line 58 initializes offsetMinutesInput once from props. If this.props.trigger.offset is later replaced by parent state updates, the input can display stale minutes.

♻️ Suggested sync guard
+  componentDidUpdate(prevProps) {
+    if (prevProps.trigger.offset !== this.props.trigger.offset) {
+      const nextMinutes = Math.abs(this.props.trigger.offset || 0);
+      this.setState({
+        offsetMinutesInput: nextMinutes > 0 ? String(nextMinutes) : '30',
+      });
+    }
+  }
+
   componentDidMount() {
     this.getHouses();
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/src/routes/scene/edit-scene/triggers/SunriseSunsetTrigger.jsx` around
lines 56 - 62, The constructor currently sets state.offsetMinutesInput from
props.trigger.offset only once, causing the input to become stale if
props.change later; add a synchronization guard (e.g.,
componentDidUpdate(prevProps)) that compares prevProps.trigger.offset to
this.props.trigger.offset and, if different, recomputes initialMinutes =
Math.abs(this.props.trigger.offset || 0) and updates state.offsetMinutesInput
(but only if the existing state.offsetMinutesInput does not already equal the
desired string value to avoid clobbering user edits). Target the class
constructor and add componentDidUpdate in the SunriseSunsetTrigger component,
referencing offsetMinutesInput and props.trigger.offset.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@front/src/config/i18n/fr.json`:
- Line 2618: Update the French translation for the atExactTime key to use the
correct accented capital A: replace the value "A l'heure exacte" with "À l'heure
exacte" (ensure the file is saved with UTF-8 encoding so the accent is preserved
and tests/linting pick up the change).

---

Nitpick comments:
In `@front/src/routes/scene/edit-scene/triggers/SunriseSunsetTrigger.jsx`:
- Around line 56-62: The constructor currently sets state.offsetMinutesInput
from props.trigger.offset only once, causing the input to become stale if
props.change later; add a synchronization guard (e.g.,
componentDidUpdate(prevProps)) that compares prevProps.trigger.offset to
this.props.trigger.offset and, if different, recomputes initialMinutes =
Math.abs(this.props.trigger.offset || 0) and updates state.offsetMinutesInput
(but only if the existing state.offsetMinutesInput does not already equal the
desired string value to avoid clobbering user edits). Target the class
constructor and add componentDidUpdate in the SunriseSunsetTrigger component,
referencing offsetMinutesInput and props.trigger.offset.

In `@server/lib/scene/scene.dailyUpdate.js`:
- Around line 52-57: Normalize trigger.offset to a finite integer before adding
to sunriseOffsets or sunsetOffsets to avoid mixed-type duplicates and malformed
values: inside the conditional that checks trigger.type against
EVENTS.TIME.SUNRISE / EVENTS.TIME.SUNSET and trigger.house === house.selector,
coerce trigger.offset (e.g., via Number.parseInt or Math.trunc after Number())
and validate Number.isFinite on the result (default to 0 or skip if invalid),
then add that normalized integer to sunriseOffsets or sunsetOffsets instead of
the raw trigger.offset.

In `@server/models/scene.js`:
- Line 119: The Joi schema for the scene model currently allows any integer for
the offset field (symbol: offset in server/models/scene.js); restrict its range
by updating the schema validation to a bounded integer (for example use
.min(-1440).max(1440)) so offsets cannot exceed ±1440 minutes, and run/update
any tests that exercise scene validation to ensure the new bounds are enforced
without breaking callers expecting the previous behavior.

In `@server/test/lib/scene/scene.checkTrigger.test.js`:
- Around line 540-577: Add a positive test case mirroring the existing
negative-offset mismatch: create a scene via sceneManager.addScene with a sunset
trigger offset: -15 (same as the current test), then call
sceneManager.checkTrigger with type: EVENTS.TIME.SUNSET, house: { selector:
'house-1' }, and offset: -15 (matching the scene). In the returned Promise where
sceneManager.queue.start(...) runs, assert that device.setValue was called (or
calledOnce) to confirm the scene executed; reuse the same structure and
identifiers (sceneManager.addScene, sceneManager.checkTrigger,
sceneManager.queue.start, device.setValue) as the existing test.

In `@server/test/lib/scene/scene.dailyUpdate.test.js`:
- Around line 185-192: Replace the literal trigger type strings 'time.sunrise'
in the dedup tests by using the shared constant EVENTS.TIME.SUNRISE to keep
consistency and enable safe refactors; locate the two addScene calls on the test
where triggers are defined (the ones passing triggers: [{ type: 'time.sunrise',
... }]) and update the type property to EVENTS.TIME.SUNRISE, ensuring EVENTS is
imported or available in the test scope alongside the sceneManager/addScene
usage.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8db6b0d and b80970e.

📒 Files selected for processing (9)
  • front/src/config/i18n/de.json
  • front/src/config/i18n/en.json
  • front/src/config/i18n/fr.json
  • front/src/routes/scene/edit-scene/triggers/SunriseSunsetTrigger.jsx
  • server/lib/scene/scene.dailyUpdate.js
  • server/lib/scene/scene.triggers.js
  • server/models/scene.js
  • server/test/lib/scene/scene.checkTrigger.test.js
  • server/test/lib/scene/scene.dailyUpdate.test.js

@relativeci
Copy link
Copy Markdown

relativeci bot commented Mar 2, 2026

#4118 Bundle Size — 11.44MiB (+0.03%).

88d35a8(current) vs 198578d master#4106(baseline)

Warning

Bundle contains 2 duplicate packages – View duplicate packages

Bundle metrics  Change 2 changes Regression 1 regression
                 Current
#4118
     Baseline
#4106
Regression  Initial JS 6.42MiB(+0.06%) 6.42MiB
No change  Initial CSS 310.54KiB 310.54KiB
Change  Cache Invalidation 56% 0%
No change  Chunks 51 51
No change  Assets 179 179
No change  Modules 1643 1643
No change  Duplicate Modules 21 21
No change  Duplicate Code 0.94% 0.94%
No change  Packages 136 136
No change  Duplicate Packages 2 2
Bundle size by type  Change 1 change Regression 1 regression
                 Current
#4118
     Baseline
#4106
Regression  JS 8.32MiB (+0.04%) 8.32MiB
No change  IMG 2.68MiB 2.68MiB
No change  CSS 328.39KiB 328.39KiB
No change  Fonts 93.55KiB 93.55KiB
No change  Other 18.82KiB 18.82KiB
No change  HTML 13.58KiB 13.58KiB

Bundle analysis reportBranch cicoub13:feat-sunrise-sunset-del...Project dashboard


Generated by RelativeCIDocumentationReport issue

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 2, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.80%. Comparing base (6cffbc0) to head (88d35a8).
⚠️ Report is 5 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2474      +/-   ##
==========================================
- Coverage   98.80%   98.80%   -0.01%     
==========================================
  Files        1007     1009       +2     
  Lines       17609    17681      +72     
==========================================
+ Hits        17399    17469      +70     
- Misses        210      212       +2     

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

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/lib/scene/scene.dailyUpdate.js`:
- Around line 56-67: The code currently uses trigger.offset without validation
before adding to sunriseBase/sunsetBase and scheduling via
sunriseOffsets/sunsetOffsets; validate that trigger.offset is a finite number
and clamp or reject values that would schedule outside the same calendar day
(e.g., ensure resulting time >= startOfDay and < startOfNextDay) before adding
to sunriseOffsets/sunsetOffsets. Update the logic that builds
sunriseOffsets/sunsetOffsets (where trigger.offset is read) to coerce to Number,
check isFinite, and bound the offset (or compute
sunriseBase.add(offset,'minute') and only include it if the resulting Date falls
within the intended day) so the scheduling loops that use sunriseBase/sunsetBase
and sunriseOffsets.forEach / sunsetOffsets.forEach only receive safe offsets.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b80970e and 2fd46d1.

📒 Files selected for processing (3)
  • front/src/config/i18n/fr.json
  • server/lib/scene/scene.dailyUpdate.js
  • server/test/lib/scene/scene.dailyUpdate.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • front/src/config/i18n/fr.json
  • server/test/lib/scene/scene.dailyUpdate.test.js

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.

🧹 Nitpick comments (1)
server/test/lib/scene/scene.dailyUpdate.test.js (1)

120-148: Tighten emitted offset assertions to exact expectations.

Current include checks can still pass if extra/wrong offsets are emitted. Assert the full emitted set per scenario.

✅ Suggested assertion hardening
-    expect(emittedOffsets).to.include(0);
-    expect(emittedOffsets).to.include(30);
+    expect(emittedOffsets.sort((a, b) => a - b)).to.deep.equal([0, 0, 30]);
...
-    expect(emittedOffsets).to.include(0);
-    expect(emittedOffsets).to.include(-15);
+    expect(emittedOffsets.sort((a, b) => a - b)).to.deep.equal([-15, 0, 0]);

Also applies to: 150-177

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

In `@server/test/lib/scene/scene.dailyUpdate.test.js` around lines 120 - 148, The
test currently uses loose include assertions on emittedOffsets; replace them
with exact expectations by asserting the array contains exactly the expected
offsets (including duplicates) for the scheduled jobs produced by
sceneManager.dailyUpdate and executed via sceneManager.jobs callbacks.
Specifically, after collecting emittedOffsets from the overridden event.emit,
assert emittedOffsets.length === 3 and compare emittedOffsets (or a sorted copy)
to the exact expected array [0,0,30] (or the scenario-specific expected values)
so the test fails on extra/missing/wrong offsets; update the same pattern in the
other test(s) around the same block (150-177) as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@server/test/lib/scene/scene.dailyUpdate.test.js`:
- Around line 120-148: The test currently uses loose include assertions on
emittedOffsets; replace them with exact expectations by asserting the array
contains exactly the expected offsets (including duplicates) for the scheduled
jobs produced by sceneManager.dailyUpdate and executed via sceneManager.jobs
callbacks. Specifically, after collecting emittedOffsets from the overridden
event.emit, assert emittedOffsets.length === 3 and compare emittedOffsets (or a
sorted copy) to the exact expected array [0,0,30] (or the scenario-specific
expected values) so the test fails on extra/missing/wrong offsets; update the
same pattern in the other test(s) around the same block (150-177) as well.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2fd46d1 and 81e5be9.

📒 Files selected for processing (1)
  • server/test/lib/scene/scene.dailyUpdate.test.js

Copy link
Copy Markdown
Contributor

@Pierre-Gilles Pierre-Gilles left a comment

Choose a reason for hiding this comment

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

Thanks a lot for this PR! Many people have been asking for this, so it’s a really great addition.

I’ve added a few comments. The one from CodeRabbit is also very relevant.

Let me know if you need any help with anything 🙂

@cicoub13 cicoub13 force-pushed the feat-sunrise-sunset-delay branch from 3f97b0d to d601476 Compare March 30, 2026 19:36
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

♻️ Duplicate comments (1)
server/lib/scene/scene.dailyUpdate.js (1)

56-58: ⚠️ Potential issue | 🟠 Major

Reject offsets against the computed sunrise/sunset day, not just ±24h.

Math.abs(offset) > 24 * 60 still accepts values that spill into the previous/next day for the actual event time (for example, +1000 after a 06:00 sunrise). Those jobs can then be canceled by the next dailyUpdate() before they ever fire. Number(trigger.offset) || 0 also turns non-numeric values into the exact-time bucket instead of rejecting them.

Suggested hardening
-          const offset = Number(trigger.offset) || 0;
-          if (!Number.isInteger(offset) || Math.abs(offset) > 24 * 60) {
+          const offset = Number(trigger.offset ?? 0);
+          const base =
+            trigger.type === EVENTS.TIME.SUNRISE
+              ? sunriseBase
+              : trigger.type === EVENTS.TIME.SUNSET
+                ? sunsetBase
+                : null;
+          if (!base || !Number.isInteger(offset) || !base.add(offset, 'minute').isSame(base, 'day')) {
+            logger.warn(`Ignoring invalid ${trigger.type} offset "${trigger.offset}" for house "${house.selector}"`);
             return;
           }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/lib/scene/scene.dailyUpdate.js` around lines 56 - 58, The code
currently uses Number(trigger.offset) || 0 and only checks Math.abs(offset) >
24*60 which lets non-numeric offsets become 0 and allows offsets that push the
scheduled minute into the previous/next day; update dailyUpdate() to (1) parse
trigger.offset with Number/parseInt and reject when it's not a finite numeric
value (don't coerce non-numerics to 0), and (2) after computing the base event
minute (the computed sunrise/sunset minute for that day), validate that
baseMinute + offset remains within the same day (e.g., 0 <= baseMinute + offset
< 24*60); if either check fails, return/cancel the trigger. Reference:
trigger.offset, offset, and the dailyUpdate() scheduling flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/lib/scene/scene.destroy.js`:
- Around line 47-53: Reorder the sunrise/sunset check to mirror addScene.js by
evaluating hadSunriseSunset from this.scenes[selector] via
hasSunriseSunsetTrigger(...) before calling this.cancelTriggers(selector), then
call await this.dailyUpdate() (not bare this.dailyUpdate()) so destroy()
properly waits for job rescheduling; update the destroy() function to compute
hadSunriseSunset first, then cancelTriggers, delete this.scenes[selector], and
finally await this.dailyUpdate().

---

Duplicate comments:
In `@server/lib/scene/scene.dailyUpdate.js`:
- Around line 56-58: The code currently uses Number(trigger.offset) || 0 and
only checks Math.abs(offset) > 24*60 which lets non-numeric offsets become 0 and
allows offsets that push the scheduled minute into the previous/next day; update
dailyUpdate() to (1) parse trigger.offset with Number/parseInt and reject when
it's not a finite numeric value (don't coerce non-numerics to 0), and (2) after
computing the base event minute (the computed sunrise/sunset minute for that
day), validate that baseMinute + offset remains within the same day (e.g., 0 <=
baseMinute + offset < 24*60); if either check fails, return/cancel the trigger.
Reference: trigger.offset, offset, and the dailyUpdate() scheduling flow.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 05bc0d75-47b4-4612-b226-f53b97efab94

📥 Commits

Reviewing files that changed from the base of the PR and between 3f97b0d and d601476.

📒 Files selected for processing (14)
  • front/src/config/i18n/de.json
  • front/src/config/i18n/en.json
  • front/src/config/i18n/fr.json
  • front/src/routes/scene/edit-scene/triggers/SunriseSunsetTrigger.jsx
  • server/lib/scene/scene.addScene.js
  • server/lib/scene/scene.dailyUpdate.js
  • server/lib/scene/scene.destroy.js
  • server/lib/scene/scene.triggers.js
  • server/models/scene.js
  • server/test/lib/scene/scene.addScene.test.js
  • server/test/lib/scene/scene.checkTrigger.test.js
  • server/test/lib/scene/scene.dailyUpdate.test.js
  • server/test/lib/scene/scene.destroy.test.js
  • server/test/lib/scene/triggers/scene.trigger.sunriseSunset.test.js
💤 Files with no reviewable changes (1)
  • server/test/lib/scene/scene.checkTrigger.test.js
✅ Files skipped from review due to trivial changes (5)
  • server/models/scene.js
  • server/lib/scene/scene.addScene.js
  • front/src/config/i18n/de.json
  • front/src/config/i18n/en.json
  • front/src/config/i18n/fr.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • server/lib/scene/scene.triggers.js
  • server/test/lib/scene/scene.dailyUpdate.test.js
  • front/src/routes/scene/edit-scene/triggers/SunriseSunsetTrigger.jsx

@cicoub13 cicoub13 force-pushed the feat-sunrise-sunset-delay branch 6 times, most recently from 3e1a36d to d4eeaba Compare March 31, 2026 19:04
cicoub13 added 6 commits April 1, 2026 10:05
- Check offset range to avoid out of day scheduled job
- Move tests from sunset/sunrise triggers into a dedicated file
- Trigger dailyUpdate when scene containing sunrise/sunset triggers is modified
@cicoub13 cicoub13 force-pushed the feat-sunrise-sunset-delay branch from d4eeaba to 9c773c2 Compare April 1, 2026 08:06
@cicoub13
Copy link
Copy Markdown
Contributor Author

cicoub13 commented Apr 1, 2026

Last commit optimized init to run the dailyUpdate once at boot

Before

2026-04-01T10:14:00+0200 <info> scene.dailyUpdate.js:37 () Sunrise today is at 7:45, in your timezone = Europe/Paris
2026-04-01T10:14:00+0200 <info> scene.dailyUpdate.js:42 () Sunset today is at 20:28, in your timezone = Europe/Paris
2026-04-01T10:14:00+0200 <info> scene.dailyUpdate.js:79 () Sunrise (offset 0min): time is in the past, not scheduling for today.
2026-04-01T10:14:00+0200 <info> scene.dailyUpdate.js:79 () Sunrise (offset 17min): time is in the past, not scheduling for today.
2026-04-01T10:14:00+0200 <info> scene.dailyUpdate.js:76 () Sunset (offset 0min) is scheduled, in 10 hours.
2026-04-01T10:14:00+0200 <info> scene.dailyUpdate.js:76 () Sunset (offset -22min) is scheduled, in 10 hours.
2026-04-01T10:14:00+0200 <info> scene.dailyUpdate.js:37 () Sunrise today is at 7:45, in your timezone = Europe/Paris
2026-04-01T10:14:00+0200 <info> scene.dailyUpdate.js:42 () Sunset today is at 20:28, in your timezone = Europe/Paris
2026-04-01T10:14:00+0200 <info> scene.dailyUpdate.js:79 () Sunrise (offset 0min): time is in the past, not scheduling for today.
2026-04-01T10:14:00+0200 <info> scene.dailyUpdate.js:79 () Sunrise (offset 17min): time is in the past, not scheduling for today.
2026-04-01T10:14:00+0200 <info> scene.dailyUpdate.js:76 () Sunset (offset 0min) is scheduled, in 10 hours.
2026-04-01T10:14:00+0200 <info> scene.dailyUpdate.js:76 () Sunset (offset -22min) is scheduled, in 10 hours.
2026-04-01T10:14:00+0200 <info> scene.dailyUpdate.js:37 () Sunrise today is at 7:45, in your timezone = Europe/Paris
2026-04-01T10:14:00+0200 <info> scene.dailyUpdate.js:42 () Sunset today is at 20:28, in your timezone = Europe/Paris
2026-04-01T10:14:00+0200 <info> scene.dailyUpdate.js:79 () Sunrise (offset 0min): time is in the past, not scheduling for today.
2026-04-01T10:14:00+0200 <info> scene.dailyUpdate.js:79 () Sunrise (offset 17min): time is in the past, not scheduling for today.
2026-04-01T10:14:00+0200 <info> scene.dailyUpdate.js:76 () Sunset (offset 0min) is scheduled, in 10 hours.
2026-04-01T10:14:00+0200 <info> scene.dailyUpdate.js:76 () Sunset (offset -22min) is scheduled, in 10 hours.

After

2026-04-01T10:16:22+0200 <info> scene.dailyUpdate.js:37 () Sunrise today is at 7:45, in your timezone = Europe/Paris
2026-04-01T10:16:22+0200 <info> scene.dailyUpdate.js:42 () Sunset today is at 20:28, in your timezone = Europe/Paris
2026-04-01T10:16:22+0200 <info> scene.dailyUpdate.js:77 () Sunrise (offset 0min): time is in the past, not scheduling for today.
2026-04-01T10:16:22+0200 <info> scene.dailyUpdate.js:77 () Sunrise (offset 17min): time is in the past, not scheduling for today.
2026-04-01T10:16:22+0200 <info> scene.dailyUpdate.js:74 () Sunset (offset 0min) is scheduled, in 10 hours.
2026-04-01T10:16:22+0200 <info> scene.dailyUpdate.js:74 () Sunset (offset -22min) is scheduled, in 10 hours.

@cicoub13 cicoub13 requested a review from Pierre-Gilles April 1, 2026 08:16
Copy link
Copy Markdown
Contributor

@Pierre-Gilles Pierre-Gilles left a comment

Choose a reason for hiding this comment

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

Thanks for the fix, very nice PR!
I've spotted a minor issue, but otherwise it looks good :)

I need to try it now!

@cicoub13 cicoub13 requested a review from Pierre-Gilles April 4, 2026 13:16
this.scenes[scene.selector] = scene;
this.brain.addNamedEntity('scene', scene.selector, scene.name);
if (!skipDailyUpdate && (hasSunriseSunsetTrigger(scene) || hadSunriseSunset)) {
this.dailyUpdate().catch((e) => logger.error(e));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this really the behavior we want? Shouldn’t addScene only resolve once dailyUpdate has completed?

I know addScene is currently synchronous, but is there any issue with making it async?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I made the dailyUpdate call behind an await + addScene async.
You can check impacts in all callers in the commit.
I decided to keep the call from scene.init.js without await (if you have 100 scenes, it can cause some latency, even if dailyUpdate is called once at the end).

Copy link
Copy Markdown
Contributor

@Pierre-Gilles Pierre-Gilles left a comment

Choose a reason for hiding this comment

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

I’m not sure I see this case handled in the code, but I might be missing something.

What happens if I create a scene with a “sunrise” trigger, then update the scene to remove that trigger?

Is the previously scheduled trigger properly cancelled?

@cicoub13
Copy link
Copy Markdown
Contributor Author

What happens if I create a scene with a “sunrise” trigger, then update the scene to remove that trigger?

Is the previously scheduled trigger properly cancelled?

  1. scene.update.js:57 → await this.addScene(plainScene)

  2. scene.addScene.js:50-51: We compute hadSunriseSunset from the previous version:

const previousScene = this.scenes[scene.selector];
const hadSunriseSunset = previousScene && hasSunriseSunsetTrigger(previousScene);
  1. scene.addScene.js:144: The scene is replaced by the new version (without the sunrise trigger).

  2. scene.addScene.js:146-148: The condition is hasSunriseSunsetTrigger(scene) || hadSunriseSunset — so even if the new scene no longer has a sunrise/sunset trigger, the fact that the old one had one is enough to trigger dailyUpdate().

  3. scene.dailyUpdate.js:19-22: all Sun jobs are canceled (this.jobs.forEach(j => j.cancel())) — this is not per-scene, it's global.

  4. scene.dailyUpdate.js:49-64: the offsets are re-collected from the current state of this.scenes (which no longer contains the removed trigger), and only what is still relevant is rescheduled.

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.

2 participants