Skip to content

feat: 7 free-sleep beta features — version, CRUD, snooze, water level, ambient light#193

Merged
ng merged 8 commits intodevfrom
feat/beta-features
Mar 16, 2026
Merged

feat: 7 free-sleep beta features — version, CRUD, snooze, water level, ambient light#193
ng merged 8 commits intodevfrom
feat/beta-features

Conversation

@ng
Copy link
Copy Markdown
Contributor

@ng ng commented Mar 16, 2026

Summary

Implements 7 features from free-sleep beta, credited to @Geczy, @throwaway31265, and nikita.

Test plan

  • pnpm tsc passes
  • Version endpoint returns build info after pnpm build
  • Sleep record PUT recalculates duration correctly
  • Snooze clears alarm and re-triggers after timeout
  • Water level readings accumulate from DeviceStateSync polling
  • Ambient light endpoints return data when environment monitor writes to DB

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Water level monitoring with alerts and trend analysis
    • Ambient light readings and historical summaries
    • Alarm snooze functionality with customizable duration (1–30 minutes)
    • Prime completion notifications with dismissal option
    • Sleep record management—update and delete capabilities
    • System version endpoint displaying build information

…cation, ambient light, water level monitoring

Implements 7 features from free-sleep beta (credit: @Geczy, @throwaway31265, nikita):

- #184: GET /system/version — build info from .git-info (generated at prebuild)
- #186: PUT/DELETE /biometrics/sleep-records — edit/delete with duration recalc
- #188: Prime completion notification — tracks isPriming transitions, dismiss endpoint
- #183: POST /device/alarm/snooze — snooze with configurable duration, auto-restart
- #185: Ambient light sensor — new table + GET/summary/latest endpoints
- #181: Water level monitoring — historical readings (60s rate-limited from DeviceStateSync),
        trend analysis, leak alerts with dismiss
- Wires waterLevel router into app, adds biometrics migration for new tables

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

coderabbitai bot commented Mar 16, 2026

Warning

Rate limit exceeded

@ng has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 16 minutes and 44 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ae090fe9-f85c-4957-82d5-777f795713e1

📥 Commits

Reviewing files that changed from the base of the PR and between 1e9cf61 and 491413a.

📒 Files selected for processing (17)
  • modules/calibrator/main.py
  • modules/common/calibration.py
  • scripts/generate-git-info.mjs
  • src/db/biometrics-migrations/0004_peaceful_mordo.sql
  • src/db/biometrics-migrations/0005_gorgeous_harpoon.sql
  • src/db/biometrics-migrations/meta/0005_snapshot.json
  • src/db/biometrics-migrations/meta/_journal.json
  • src/db/biometrics-schema.ts
  • src/hardware/dacMonitor.instance.ts
  • src/hardware/gestureActionHandler.ts
  • src/hardware/primeNotification.ts
  • src/hardware/snoozeManager.ts
  • src/hardware/tests/primeNotification.test.ts
  • src/hardware/tests/snoozeManager.test.ts
  • src/server/routers/biometrics.ts
  • src/server/routers/device.ts
  • src/server/routers/waterLevel.ts
📝 Walkthrough

Walkthrough

This PR introduces build-time git info generation, extends the biometrics database with water level and ambient light monitoring, implements alarm snooze functionality with prime notifications, and adds corresponding API endpoints for version info, water level management, and sleep record operations.

Changes

Cohort / File(s) Summary
Build System & Git Info
.gitignore, package.json, scripts/generate-git-info.mjs
Added prebuild script that generates a .git-info file containing branch, commit hash, commit title, and build date at build time; .git-info is now gitignored.
Database Migrations & Schema
src/db/biometrics-migrations/0004_peaceful_mordo.sql, src/db/biometrics-migrations/meta/0004_snapshot.json, src/db/biometrics-migrations/meta/_journal.json, src/db/biometrics-schema.ts
Added three new database tables: ambient_light (with unique timestamp index), water_level_readings (with timestamp index), and water_level_alerts; migration snapshot and journal updated accordingly.
Hardware State & Monitoring
src/hardware/dacMonitor.instance.ts, src/hardware/deviceStateSync.ts, src/hardware/primeNotification.ts, src/hardware/snoozeManager.ts
Integrated prime completion tracking into DAC monitor; added water level recording to device state sync (60-second throttle); introduced prime notification state management; implemented per-side alarm snooze with timeout-based auto-restart.
API Route Composition
src/server/openapi.ts, src/server/routers/app.ts
Added "Water Level" tag to OpenAPI documentation; registered new waterLevelRouter as top-level route in appRouter.
Endpoint Implementations
src/server/routers/biometrics.ts, src/server/routers/device.ts, src/server/routers/system.ts, src/server/routers/environment.ts, src/server/routers/waterLevel.ts
Added sleep record update/delete endpoints; extended device router with snooze and prime notification dismissal endpoints; introduced system version endpoint reading .git-info; added ambient light query endpoints (history, latest, summary); created comprehensive waterLevel router with history, latest, trend, alerts, and alert dismissal endpoints.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related issues

  • Add alarm snooze endpoint #183: Implements the alarm snooze feature at both the hardware management and API endpoint levels with timeout-based restart logic.

Possibly related PRs

Poem

🐰 The water whispers, alarms now snooze,
Build info captured—we won't lose,
A rabbit tracks each gentle stream,
Prime notifications, a sleepy dream,
Sips of data flowing true! 💧✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the seven main features added: version endpoint, CRUD operations, snooze, water level monitoring, and ambient light sensor. It accurately reflects the primary changes in the changeset.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/beta-features
📝 Coding Plan
  • Generate coding plan for human review comments

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

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds several “free-sleep beta” API + hardware features to the core controller, spanning new REST/tRPC endpoints, in-memory notification/snooze state, and biometrics DB schema/migrations.

Changes:

  • Adds water level monitoring endpoints + DB tables/migration, and writes periodic readings from DeviceStateSync.
  • Adds ambient light endpoints + ambient_light biometrics table/migration.
  • Adds alarm snooze support and prime-completion notification surfaced via /device/status, plus a dismiss endpoint.
  • Adds /system/version endpoint backed by a build-time .git-info artifact generated during prebuild.

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/server/routers/waterLevel.ts New water level history/latest/trend/alerts endpoints.
src/server/routers/system.ts Adds /system/version endpoint reading .git-info.
src/server/routers/environment.ts Adds ambient light endpoints and queries.
src/server/routers/device.ts Adds snooze + prime notification exposure/dismiss; cancels snooze on clear.
src/server/routers/biometrics.ts Adds sleep record update/delete endpoints with duration recalculation.
src/server/routers/app.ts Registers waterLevelRouter in the app router.
src/server/openapi.ts Adds “Water Level” to OpenAPI tags.
src/hardware/snoozeManager.ts New in-memory snooze timer manager that re-triggers alarms.
src/hardware/primeNotification.ts New in-memory prime completion notification tracker.
src/hardware/deviceStateSync.ts Writes water level readings to biometrics DB (rate-limited).
src/hardware/dacMonitor.instance.ts Hooks priming state tracking into status updates.
src/db/biometrics-schema.ts Adds water_level_readings, water_level_alerts, ambient_light tables.
src/db/biometrics-migrations/meta/_journal.json Registers new biometrics migration.
src/db/biometrics-migrations/meta/0004_snapshot.json Snapshot reflecting new biometrics tables.
src/db/biometrics-migrations/0004_peaceful_mordo.sql Migration creating the new biometrics tables/indexes.
scripts/generate-git-info.mjs Generates .git-info at build time.
package.json Runs git-info generation on prebuild.
.gitignore Ignores .git-info build artifact.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +91 to +100
const now = Date.now()
if (now - this.lastWaterLevelWrite < 60_000) return
this.lastWaterLevelWrite = now

const level = status.waterLevel === 'low' ? 'low' as const : 'ok' as const
try {
biometricsDb
.insert(waterLevelReadings)
.values({ timestamp: new Date(now), level })
.run()
Comment thread src/server/routers/waterLevel.ts Outdated
import { publicProcedure, router } from '@/src/server/trpc'
import { biometricsDb } from '@/src/db'
import { waterLevelReadings, waterLevelAlerts } from '@/src/db/biometrics-schema'
import { eq, and, gte, lte, desc, isNull, count, sql } from 'drizzle-orm'
Comment on lines +16 to +38
export function snoozeAlarm(
side: Side,
durationSeconds: number,
config: SnoozeState['config'],
): Date {
// Cancel any existing snooze for this side
cancelSnooze(side)

const snoozeUntil = new Date(Date.now() + durationSeconds * 1000)

const timeoutId = setTimeout(async () => {
activeSnoozes.delete(side)
try {
const client = getSharedHardwareClient()
await client.setAlarm(side, config)
}
catch (err) {
console.error(`[Snooze] Failed to restart alarm for ${side}:`, err)
}
}, durationSeconds * 1000)

activeSnoozes.set(side, { timeoutId, snoozeUntil, config })
return snoozeUntil
Comment on lines 325 to 329
.mutation(async ({ input }) => {
return withHardwareClient(async (client) => {
await client.clearAlarm(input.side)
cancelSnooze(input.side)

Comment on lines +10 to +15
export function trackPrimingState(isPriming: boolean): void {
if (wasPriming && !isPriming) {
primeCompletedAt = new Date()
}
wasPriming = isPriming
}
Comment thread src/server/routers/biometrics.ts Outdated
}

const entered = updates.enteredBedAt ?? existing.enteredBedAt
const left = updates.leftBedAt ?? existing.leftBedAt
Comment on lines +421 to +424
try {
const raw = await readFile('.git-info', 'utf-8')
return JSON.parse(raw)
}
Comment thread scripts/generate-git-info.mjs Outdated
Comment on lines +10 to +17
writeFileSync('.git-info', JSON.stringify({
branch: run('git rev-parse --abbrev-ref HEAD'),
commitHash: run('git rev-parse --short HEAD'),
commitTitle: run('git log -1 --format=%s'),
buildDate: new Date().toISOString(),
}))

console.log('Generated .git-info')
- Move lastWaterLevelWrite update after successful DB insert
- Remove unused imports (count, sql) from waterLevel router
- Cancel pending snooze when setting a new alarm (prevents stale re-trigger)
- Clear prime notification when new priming cycle starts
- Validate leftBedAt > enteredBedAt in sleep record update
- Validate .git-info fields before returning (malformed file fallback)
- Wrap .git-info write in try/catch so build doesn't fail on read-only FS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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: 4

🧹 Nitpick comments (8)
src/db/biometrics-schema.ts (1)

80-95: Consider adding an index on dismissedAt for the alerts table.

Querying active (undismissed) alerts will filter by dismissedAt IS NULL. Without an index, this scan could become slow as the alerts table grows.

💡 Suggested index addition
 export const waterLevelAlerts = sqliteTable('water_level_alerts', {
   id: integer('id').primaryKey({ autoIncrement: true }),
   type: text('type', { enum: ['low_sustained', 'rapid_change', 'leak_suspected'] }).notNull(),
   startedAt: integer('started_at', { mode: 'timestamp' }).notNull(),
   dismissedAt: integer('dismissed_at', { mode: 'timestamp' }),
   message: text('message'),
   createdAt: integer('created_at', { mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),
-})
+}, t => [
+  index('idx_water_level_alerts_dismissed').on(t.dismissedAt),
+])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/db/biometrics-schema.ts` around lines 80 - 95, The alerts table
(waterLevelAlerts) lacks an index on dismissedAt, causing full table scans when
querying active alerts (WHERE dismissedAt IS NULL); add an index on the
dismissedAt column (e.g., idx_water_level_alerts_dismissed_at) in the
sqliteTable definition for waterLevelAlerts so queries filtering by dismissedAt
use the index and improve performance.
src/server/routers/biometrics.ts (1)

544-547: Type safety concern with dynamic setValues object.

Using Record<string, unknown> loses type safety with Drizzle's .set() method. If column names drift from schema, TypeScript won't catch it.

💡 Type-safe alternative using Drizzle's inferred types
-      const setValues: Record<string, unknown> = {}
-      if (updates.enteredBedAt) setValues.enteredBedAt = updates.enteredBedAt
-      if (updates.leftBedAt) setValues.leftBedAt = updates.leftBedAt
-      if (updates.timesExitedBed !== undefined) setValues.timesExitedBed = updates.timesExitedBed
+      const setValues: Partial<typeof sleepRecords.$inferInsert> = {}
+      if (updates.enteredBedAt) setValues.enteredBedAt = updates.enteredBedAt
+      if (updates.leftBedAt) setValues.leftBedAt = updates.leftBedAt
+      if (updates.timesExitedBed !== undefined) setValues.timesExitedBed = updates.timesExitedBed

This ensures setValues keys are validated against the actual schema columns.

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

In `@src/server/routers/biometrics.ts` around lines 544 - 547, The current
setValues variable is typed as Record<string, unknown>, losing compile-time
validation for keys passed to Drizzle's .set() and risking mismatches if schema
changes; change its type to a type derived from the actual table columns (e.g.,
Partial<Record<keyof typeof <tableName>, unknown>> or better,
Partial<InferModel<typeof <tableName>, "update">>) so TypeScript verifies keys
like enteredBedAt, leftBedAt, timesExitedBed, then build setValues from updates
and pass it to .set() — update the import to bring in the table symbol and
InferModel (or use keyof typeof <tableName>) and replace Record<string, unknown>
with the schema-derived type to restore type safety for .set().
src/hardware/deviceStateSync.ts (1)

94-94: Simplify: status.waterLevel is already correctly typed.

Per DeviceStatus in src/hardware/types.ts, waterLevel is typed as 'low' | 'ok', which matches the schema enum exactly.

💡 Simplified version
-    const level = status.waterLevel === 'low' ? 'low' as const : 'ok' as const
+    const level = status.waterLevel
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hardware/deviceStateSync.ts` at line 94, The assignment unnecessarily
re-casts status.waterLevel; replace the line that sets const level =
status.waterLevel === 'low' ? 'low' as const : 'ok' as const with a direct
assignment const level = status.waterLevel to use the existing DeviceStatus
typing (refer to the variable level and the status.waterLevel usage in
deviceStateSync.ts), removing the redundant conditional and as const casts.
src/db/biometrics-migrations/0004_peaceful_mordo.sql (2)

8-15: Add an index for active-alert reads.

getAlerts filters by dismissed_at IS NULL and sorts by created_at DESC; indexing these columns will avoid full scans as alert volume grows.

Suggested index
 CREATE TABLE `water_level_alerts` (
 	`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
 	`type` text NOT NULL,
 	`started_at` integer NOT NULL,
 	`dismissed_at` integer,
 	`message` text,
 	`created_at` integer NOT NULL
 );
 --> statement-breakpoint
+CREATE INDEX `idx_water_level_alerts_active_created`
+  ON `water_level_alerts` (`dismissed_at`, `created_at`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/db/biometrics-migrations/0004_peaceful_mordo.sql` around lines 8 - 15,
Add a composite index to speed up `getAlerts` queries on the
`water_level_alerts` table which filter by `dismissed_at IS NULL` and order by
`created_at DESC`; create an index referencing `water_level_alerts(dismissed_at,
created_at)` (choose a clear name like
`idx_water_level_alerts_active_created_at`) so reads filter and sort without
full table scans and update the migration `0004_peaceful_mordo.sql` to include
the CREATE INDEX statement.

17-21: Constrain water_level_readings.level to valid domain values.

Line 20 currently allows arbitrary text, which can silently corrupt trend classification logic.

Suggested migration adjustment
 CREATE TABLE `water_level_readings` (
 	`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
 	`timestamp` integer NOT NULL,
-	`level` text NOT NULL
+	`level` text NOT NULL CHECK(`level` IN ('ok', 'low'))
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/db/biometrics-migrations/0004_peaceful_mordo.sql` around lines 17 - 21,
The water_level_readings table currently allows arbitrary text in column
`level`, which can corrupt trend logic; update the migration that creates
`water_level_readings` to constrain `level` to an explicit domain (e.g., replace
the loose `level` text type with a constraint such as a CHECK on `level` IN
('low','normal','high') or your actual allowed set) by editing the CREATE TABLE
for `water_level_readings` to include that CHECK clause on `level`; ensure the
chosen allowed values match downstream code that reads `level` and, if needed,
add a follow-up migration to coerce or validate existing values before applying
the constraint.
src/hardware/snoozeManager.ts (1)

16-25: Add an internal guard for durationSeconds in exported API.

Line 16–25 assumes callers always validate duration; adding a local check makes this safer for future call sites.

Suggested guard
 export function snoozeAlarm(
   side: Side,
   durationSeconds: number,
   config: SnoozeState['config'],
 ): Date {
+  if (!Number.isInteger(durationSeconds) || durationSeconds < 1) {
+    throw new Error(`Invalid snooze duration: ${durationSeconds}`)
+  }
+
   // Cancel any existing snooze for this side
   cancelSnooze(side)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hardware/snoozeManager.ts` around lines 16 - 25, Add a local guard at the
start of the exported function snoozeAlarm to validate durationSeconds is a
finite positive number (and optionally not absurdly large); if it is not a
finite number or <= 0, throw a RangeError (or TypeError) with a clear message,
and if it exceeds a defined MAX_SNOOZE_SECONDS clamp it down or throw—this
ensures callers can't pass invalid durations before calling cancelSnooze and
computing snoozeUntil; introduce a MAX_SNOOZE_SECONDS constant if needed and
reference snoozeAlarm, cancelSnooze, and snoozeUntil in the change.
src/db/biometrics-migrations/meta/0004_snapshot.json (1)

705-756: Consider adding an index on water_level_alerts for querying active alerts.

The water_level_alerts table has no indexes beyond the primary key. If the application frequently queries for active (undismissed) alerts using WHERE dismissed_at IS NULL, performance may degrade as the table grows.

Consider adding a partial index or a composite index depending on query patterns:

-- Option: Index on type and dismissed_at for filtering active alerts by type
CREATE INDEX idx_water_level_alerts_type_dismissed ON water_level_alerts(type, dismissed_at);

This is a minor concern if the alerts table remains small.

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

In `@src/db/biometrics-migrations/meta/0004_snapshot.json` around lines 705 - 756,
Add an index to speed up queries for active alerts on the water_level_alerts
table by creating an index on the dismissed_at column (and include type if you
filter by type) — e.g., add a migration that creates
idx_water_level_alerts_type_dismissed on water_level_alerts(type, dismissed_at)
or a partial index on dismissed_at IS NULL if your DB supports it; update the
migrations/meta snapshot to include the new index entry for water_level_alerts
so future schema checks recognize the index.
src/server/routers/device.ts (1)

349-351: Consider expanding the docstring for snoozeAlarm.

Other procedures in this file have detailed documentation covering behavior, timing, and edge cases. The snooze procedure would benefit from similar treatment, particularly around:

  • What happens if no alarm is currently active
  • Behavior when snooze is called multiple times
  • How the re-triggered alarm differs from the original (uses provided parameters, not original alarm's)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/routers/device.ts` around lines 349 - 351, The docstring for
snoozeAlarm lacks detail; update the comment above the snoozeAlarm function to
explain behavior when no alarm is active (e.g., no-op or returns specific
error), define behavior for repeated calls (whether it resets the snooze timer
or is ignored), and clarify that the re-triggered alarm uses the provided snooze
parameters (duration/other args) rather than the original alarm configuration;
reference the snoozeAlarm function name and include expected return
values/side-effects (stops vibration immediately, schedules restart after
duration) and any edge-case behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/generate-git-info.mjs`:
- Around line 5-8: The run function currently places multiple statements on a
single line, violating the stylistic rule; refactor the run arrow function so
each statement is on its own line: expand the try block and its return (calling
execSync with { encoding: 'utf-8' } and .trim()), place the catch on its own
line (optionally capture the error as e) and return 'unknown' inside the catch
block; update the run declaration and references to use execSync and run exactly
as named to locate and fix the issue.

In `@src/hardware/snoozeManager.ts`:
- Line 11: The type literal for the config member uses semicolons which the
linter flags; replace the semicolons with commas so the declaration reads
config: { vibrationIntensity: number, vibrationPattern: 'double' | 'rise',
duration: number } and apply the same change to the other type/member reported
on line 49 (use commas between members instead of semicolons) so both type
literals conform to the lint rule.

In `@src/server/routers/system.ts`:
- Around line 420-434: The code calls readFile('.git-info') inside the anonymous
async query (the .query handler) which relies on the process CWD; change this to
compute an absolute path for '.git-info' based on the server file location
before calling readFile (e.g., resolve the path using the module file location /
__dirname or fileURLToPath(import.meta.url) + path.dirname and
path.resolve/join) so readFile reads a deterministic absolute path instead of a
relative one; update the readFile('.git-info', 'utf-8') call to use that
resolvedPath and ensure the deployment bundles the .git-info file alongside the
app.

In `@src/server/routers/waterLevel.ts`:
- Around line 91-110: The trend logic currently treats a single reading as
'stable'; change it to return 'unknown' when there are fewer than 2 readings by
checking the computed total (rows.length) before inferring trend: if total < 2
set trend = 'unknown' (or return { ..., trend: 'unknown' }) instead of
proceeding to compute mid/recentRate/olderRate; update the block that declares
and assigns trend (variable named trend) and the early-return for rows.length
=== 0 to ensure it also handles total === 1.

---

Nitpick comments:
In `@src/db/biometrics-migrations/0004_peaceful_mordo.sql`:
- Around line 8-15: Add a composite index to speed up `getAlerts` queries on the
`water_level_alerts` table which filter by `dismissed_at IS NULL` and order by
`created_at DESC`; create an index referencing `water_level_alerts(dismissed_at,
created_at)` (choose a clear name like
`idx_water_level_alerts_active_created_at`) so reads filter and sort without
full table scans and update the migration `0004_peaceful_mordo.sql` to include
the CREATE INDEX statement.
- Around line 17-21: The water_level_readings table currently allows arbitrary
text in column `level`, which can corrupt trend logic; update the migration that
creates `water_level_readings` to constrain `level` to an explicit domain (e.g.,
replace the loose `level` text type with a constraint such as a CHECK on `level`
IN ('low','normal','high') or your actual allowed set) by editing the CREATE
TABLE for `water_level_readings` to include that CHECK clause on `level`; ensure
the chosen allowed values match downstream code that reads `level` and, if
needed, add a follow-up migration to coerce or validate existing values before
applying the constraint.

In `@src/db/biometrics-migrations/meta/0004_snapshot.json`:
- Around line 705-756: Add an index to speed up queries for active alerts on the
water_level_alerts table by creating an index on the dismissed_at column (and
include type if you filter by type) — e.g., add a migration that creates
idx_water_level_alerts_type_dismissed on water_level_alerts(type, dismissed_at)
or a partial index on dismissed_at IS NULL if your DB supports it; update the
migrations/meta snapshot to include the new index entry for water_level_alerts
so future schema checks recognize the index.

In `@src/db/biometrics-schema.ts`:
- Around line 80-95: The alerts table (waterLevelAlerts) lacks an index on
dismissedAt, causing full table scans when querying active alerts (WHERE
dismissedAt IS NULL); add an index on the dismissedAt column (e.g.,
idx_water_level_alerts_dismissed_at) in the sqliteTable definition for
waterLevelAlerts so queries filtering by dismissedAt use the index and improve
performance.

In `@src/hardware/deviceStateSync.ts`:
- Line 94: The assignment unnecessarily re-casts status.waterLevel; replace the
line that sets const level = status.waterLevel === 'low' ? 'low' as const : 'ok'
as const with a direct assignment const level = status.waterLevel to use the
existing DeviceStatus typing (refer to the variable level and the
status.waterLevel usage in deviceStateSync.ts), removing the redundant
conditional and as const casts.

In `@src/hardware/snoozeManager.ts`:
- Around line 16-25: Add a local guard at the start of the exported function
snoozeAlarm to validate durationSeconds is a finite positive number (and
optionally not absurdly large); if it is not a finite number or <= 0, throw a
RangeError (or TypeError) with a clear message, and if it exceeds a defined
MAX_SNOOZE_SECONDS clamp it down or throw—this ensures callers can't pass
invalid durations before calling cancelSnooze and computing snoozeUntil;
introduce a MAX_SNOOZE_SECONDS constant if needed and reference snoozeAlarm,
cancelSnooze, and snoozeUntil in the change.

In `@src/server/routers/biometrics.ts`:
- Around line 544-547: The current setValues variable is typed as Record<string,
unknown>, losing compile-time validation for keys passed to Drizzle's .set() and
risking mismatches if schema changes; change its type to a type derived from the
actual table columns (e.g., Partial<Record<keyof typeof <tableName>, unknown>>
or better, Partial<InferModel<typeof <tableName>, "update">>) so TypeScript
verifies keys like enteredBedAt, leftBedAt, timesExitedBed, then build setValues
from updates and pass it to .set() — update the import to bring in the table
symbol and InferModel (or use keyof typeof <tableName>) and replace
Record<string, unknown> with the schema-derived type to restore type safety for
.set().

In `@src/server/routers/device.ts`:
- Around line 349-351: The docstring for snoozeAlarm lacks detail; update the
comment above the snoozeAlarm function to explain behavior when no alarm is
active (e.g., no-op or returns specific error), define behavior for repeated
calls (whether it resets the snooze timer or is ignored), and clarify that the
re-triggered alarm uses the provided snooze parameters (duration/other args)
rather than the original alarm configuration; reference the snoozeAlarm function
name and include expected return values/side-effects (stops vibration
immediately, schedules restart after duration) and any edge-case behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 710033ee-a3e4-413b-bba1-4e6b1b1e8c49

📥 Commits

Reviewing files that changed from the base of the PR and between a435904 and 1e9cf61.

📒 Files selected for processing (18)
  • .gitignore
  • package.json
  • scripts/generate-git-info.mjs
  • src/db/biometrics-migrations/0004_peaceful_mordo.sql
  • src/db/biometrics-migrations/meta/0004_snapshot.json
  • src/db/biometrics-migrations/meta/_journal.json
  • src/db/biometrics-schema.ts
  • src/hardware/dacMonitor.instance.ts
  • src/hardware/deviceStateSync.ts
  • src/hardware/primeNotification.ts
  • src/hardware/snoozeManager.ts
  • src/server/openapi.ts
  • src/server/routers/app.ts
  • src/server/routers/biometrics.ts
  • src/server/routers/device.ts
  • src/server/routers/environment.ts
  • src/server/routers/system.ts
  • src/server/routers/waterLevel.ts

Comment thread scripts/generate-git-info.mjs
Comment thread src/hardware/snoozeManager.ts Outdated
Comment on lines +420 to +434
.query(async () => {
const fallback = { branch: 'unknown', commitHash: 'unknown', commitTitle: 'unknown', buildDate: 'unknown' }
try {
const raw = await readFile('.git-info', 'utf-8')
const parsed = JSON.parse(raw)
return {
branch: typeof parsed.branch === 'string' ? parsed.branch : 'unknown',
commitHash: typeof parsed.commitHash === 'string' ? parsed.commitHash : 'unknown',
commitTitle: typeof parsed.commitTitle === 'string' ? parsed.commitTitle : 'unknown',
buildDate: typeof parsed.buildDate === 'string' ? parsed.buildDate : 'unknown',
}
}
catch {
return fallback
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how other files in the project resolve paths relative to project root
rg -n "readFile\s*\(" --type ts -g '!node_modules' | head -20

Repository: sleepypod/core

Length of output: 147


🏁 Script executed:

cat package.json | head -30

Repository: sleepypod/core

Length of output: 1128


🏁 Script executed:

fd -e "git-info" . --type f

Repository: sleepypod/core

Length of output: 40


🏁 Script executed:

rg "\.git-info" --type ts --type json -g '!node_modules' -B 2 -A 2

Repository: sleepypod/core

Length of output: 706


🏁 Script executed:

head -50 src/server/routers/system.ts

Repository: sleepypod/core

Length of output: 1557


🏁 Script executed:

# Check if there are any build scripts or config that creates .git-info
rg "git-info" --type json --type js --type ts -g '!node_modules' -B 2 -A 2

Repository: sleepypod/core

Length of output: 912


🏁 Script executed:

cat scripts/generate-git-info.mjs

Repository: sleepypod/core

Length of output: 655


🏁 Script executed:

# Check Next.js config for output directory or other deployment-related settings
fd next.config -e js -e ts -e mjs

Repository: sleepypod/core

Length of output: 73


🏁 Script executed:

# Check if .git-info is in .gitignore or other config files
cat .gitignore | head -50

Repository: sleepypod/core

Length of output: 573


🏁 Script executed:

# Look for any other references to process.cwd() or __dirname in server code
rg "process\.cwd\(\)|__dirname|process\.env\..*HOME|path\.resolve" src/server --type ts -B 2 -A 2

Repository: sleepypod/core

Length of output: 270


🏁 Script executed:

cat next.config.mjs

Repository: sleepypod/core

Length of output: 527


🏁 Script executed:

# Check if there's any special handling in Next.js for output directory
rg "output|distDir|outputFileTracing" --type js --type ts -g '!node_modules'

Repository: sleepypod/core

Length of output: 4597


Relative path .git-info may fail in production deployments.

The relative path '.git-info' depends on the process working directory being the project root. This works in development, but .git-info is not bundled into the Next.js .next directory and may not be present in standalone or containerized deployments unless explicitly deployed alongside the application.

Consider using an absolute path to make the dependency explicit:

💡 Suggested fix
+import { fileURLToPath } from 'node:url'
+import { dirname, join } from 'node:path'
+
+const __dirname = dirname(fileURLToPath(import.meta.url))
+const GIT_INFO_PATH = join(__dirname, '../../..', '.git-info')
+
 // Then in the query:
-        const raw = await readFile('.git-info', 'utf-8')
+        const raw = await readFile(GIT_INFO_PATH, 'utf-8')

This resolves the path relative to the server file's location, making it work consistently across deployment environments. Ensure .git-info is deployed with your application (e.g., via Docker COPY, or deployment configuration).

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

In `@src/server/routers/system.ts` around lines 420 - 434, The code calls
readFile('.git-info') inside the anonymous async query (the .query handler)
which relies on the process CWD; change this to compute an absolute path for
'.git-info' based on the server file location before calling readFile (e.g.,
resolve the path using the module file location / __dirname or
fileURLToPath(import.meta.url) + path.dirname and path.resolve/join) so readFile
reads a deterministic absolute path instead of a relative one; update the
readFile('.git-info', 'utf-8') call to use that resolvedPath and ensure the
deployment bundles the .git-info file alongside the app.

Comment thread src/server/routers/waterLevel.ts Outdated
ng and others added 3 commits March 15, 2026 18:14
Consensus fixes from Optimizer + Skeptic review:

- Cancel active snoozes + reset prime state in shutdownDacMonitor (ghost timer leak)
- Wrap trackPrimingState in error boundary (prevents uncaught exception crash)
- Wrap updateSleepRecord select+update in transaction (TOCTOU race)
- Replace getTrend in-memory row scan with SQL COUNT aggregation (10K row perf fix)
- Change waterLevelReadings to uniqueIndex (consistency with other time-series tables)
- Add index on waterLevelAlerts.dismissedAt for active-alert queries
- Add try/catch to dismissAlert (pattern consistency)
- Update migration SQL to match schema changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add snooze.left/snooze.right to device status response so iOS can
  recover snooze UI state after reload
- Fix reportVitalsBatch to return actual inserted count via .returning()
  instead of input rows.length (pre-existing inaccuracy)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Expand try/catch to separate lines (max-statements-per-line lint rule)
- Return trend: 'unknown' when fewer than 2 water level readings exist

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

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds several “free-sleep beta” features across the API, hardware runtime, and biometrics DB to expose build/version info, support new device behaviors (snooze + priming completion notification), and add new environment/water-level data surfaces.

Changes:

  • Add build metadata generation (.git-info) at build time and expose it via GET /system/version.
  • Add alarm snooze scheduling + prime-completion notification into device status and new device endpoints.
  • Extend biometrics DB + routers for ambient light + water level data, and add sleep-record update/delete.

Reviewed changes

Copilot reviewed 19 out of 20 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/server/routers/waterLevel.ts New router exposing water level history/latest/trend + alert listing/dismiss
src/server/routers/system.ts Adds GET /system/version reading .git-info
src/server/routers/environment.ts Adds ambient light history/latest/summary endpoints
src/server/routers/device.ts Adds snooze endpoint + prime notification fields + dismiss endpoint; extends status payload
src/server/routers/biometrics.ts Adds sleep record update/delete; changes vitals batch “written” count to actual inserted rows
src/server/routers/app.ts Registers waterLevel router in app router
src/server/openapi.ts Adds “Water Level” tag to OpenAPI doc
src/hardware/snoozeManager.ts New in-memory snooze scheduler (timeouts) per side
src/hardware/primeNotification.ts New in-memory prime completion notification state machine
src/hardware/deviceStateSync.ts Writes rate-limited water level readings into biometrics DB
src/hardware/dacMonitor.instance.ts Tracks priming transitions; cancels snoozes/resets notification on shutdown
src/db/biometrics-schema.ts Adds ambient_light, water_level_readings, water_level_alerts tables
src/db/biometrics-migrations/meta/_journal.json Adds migration journal entry for 0004
src/db/biometrics-migrations/meta/0004_snapshot.json New snapshot for biometrics migration 0004
src/db/biometrics-migrations/0004_peaceful_mordo.sql Migration creating new ambient light + water level tables/indexes
scripts/generate-git-info.mjs New prebuild script generating .git-info from git metadata
package.json Adds prebuild hook to generate .git-info
modules/common/calibration.py Adds CapSense2 calibration + presence detection helper
modules/calibrator/main.py Prefers CapSense2 calibration records when available
.gitignore Ignores .git-info build artifact

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +114 to +117
.where(and(
gte(waterLevelReadings.timestamp, midpoint),
sql`${waterLevelReadings.level} = 'low'`,
))
"columns": [
"timestamp"
],
"isUnique": false
Comment on lines +16 to +35
export function snoozeAlarm(
side: Side,
durationSeconds: number,
config: SnoozeState['config'],
): Date {
// Cancel any existing snooze for this side
cancelSnooze(side)

const snoozeUntil = new Date(Date.now() + durationSeconds * 1000)

const timeoutId = setTimeout(async () => {
activeSnoozes.delete(side)
try {
const client = getSharedHardwareClient()
await client.setAlarm(side, config)
}
catch (err) {
console.error(`[Snooze] Failed to restart alarm for ${side}:`, err)
}
}, durationSeconds * 1000)
Comment on lines +10 to +27
export function trackPrimingState(isPriming: boolean): void {
if (!wasPriming && isPriming) {
// New priming cycle — clear stale notification
primeCompletedAt = null
}
else if (wasPriming && !isPriming) {
primeCompletedAt = new Date()
}
wasPriming = isPriming
}

export function getPrimeCompletedAt(): number | null {
return primeCompletedAt ? Math.floor(primeCompletedAt.getTime() / 1000) : null
}

export function dismissPrimeNotification(): void {
primeCompletedAt = null
}
Comment on lines +355 to +360
snoozeAlarm: publicProcedure
.meta({ openapi: { method: 'POST', path: '/device/alarm/snooze', protect: false, tags: ['Device'] } })
.input(
z.object({
side: sideSchema,
duration: z.number().int().min(60).max(1800).default(300),
Comment on lines +13 to +18
getHistory: publicProcedure
.meta({ openapi: { method: 'GET', path: '/water-level/history', protect: false, tags: ['Water Level'] } })
.input(z.object({
startDate: z.date().optional(),
endDate: z.date().optional(),
limit: z.number().int().min(1).max(10000).default(1440),
Comment on lines +291 to +336
LOOKBACK_HOURS = 6
MIN_WINDOW_S = 300 # 5 minutes at ~2 Hz = ~600 samples
MIN_STD = 0.05 # floats in the tens, not ints in the hundreds
# Sensing channel pairs: (name, index_a, index_b)
SENSE_PAIRS = (("A", 0, 1), ("B", 2, 3), ("C", 4, 5))
REF_PAIR = ("REF", 6, 7)

def calibrate(self, records: list, side: str) -> CalibrationResult:
"""Find the quietest 5-min window and compute per-channel baselines."""
if not records:
raise ValueError("No capSense2 records available for calibration")

timestamps = []
channels = {name: [] for name, _, _ in self.SENSE_PAIRS}
channels["REF"] = []

for rec in records:
data = rec.get(side, {})
vals = data.get("values") if data else None
if not vals or len(vals) < 8:
continue
ts = float(rec.get("ts", 0))
timestamps.append(ts)
for name, ia, ib in self.SENSE_PAIRS:
channels[name].append((vals[ia] + vals[ib]) / 2.0)
rn, ria, rib = self.REF_PAIR
channels["REF"].append((vals[ria] + vals[rib]) / 2.0)

if len(timestamps) < 60:
raise ValueError(
f"Insufficient capSense2 data: {len(timestamps)} samples (need >= 60)"
)

# Find quietest 5-min window across sensing channels only
window = min(self.MIN_WINDOW_S, len(timestamps))
best_start = 0
best_variance = float("inf")
sense_names = [name for name, _, _ in self.SENSE_PAIRS]

for i in range(len(timestamps) - window + 1):
total_var = 0.0
for name in sense_names:
seg = channels[name][i:i + window]
m = sum(seg) / len(seg)
total_var += sum((x - m) ** 2 for x in seg) / len(seg)
if total_var < best_variance:
Comment on lines +16 to +55
export function snoozeAlarm(
side: Side,
durationSeconds: number,
config: SnoozeState['config'],
): Date {
// Cancel any existing snooze for this side
cancelSnooze(side)

const snoozeUntil = new Date(Date.now() + durationSeconds * 1000)

const timeoutId = setTimeout(async () => {
activeSnoozes.delete(side)
try {
const client = getSharedHardwareClient()
await client.setAlarm(side, config)
}
catch (err) {
console.error(`[Snooze] Failed to restart alarm for ${side}:`, err)
}
}, durationSeconds * 1000)

activeSnoozes.set(side, { timeoutId, snoozeUntil, config })
return snoozeUntil
}

export function cancelSnooze(side: Side): void {
const existing = activeSnoozes.get(side)
if (existing) {
clearTimeout(existing.timeoutId)
activeSnoozes.delete(side)
}
}

export function getSnoozeStatus(side: Side): { active: boolean; snoozeUntil: number | null } {
const state = activeSnoozes.get(side)
return {
active: !!state,
snoozeUntil: state ? Math.floor(state.snoozeUntil.getTime() / 1000) : null,
}
}
Comment on lines +438 to +444
dismissPrimeNotification: publicProcedure
.meta({ openapi: { method: 'POST', path: '/device/prime/dismiss', protect: false, tags: ['Device'] } })
.input(z.object({}))
.output(z.object({ success: z.boolean() }))
.mutation(() => {
dismissPrimeNotification()
return { success: true }
Comment on lines +530 to +536
updateSleepRecord: publicProcedure
.meta({ openapi: { method: 'PUT', path: '/biometrics/sleep-records', protect: false, tags: ['Biometrics'] } })
.input(
z.object({
id: idSchema,
enteredBedAt: z.date().optional(),
leftBedAt: z.date().optional(),
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements a bundle of “free-sleep beta” features across the server API, hardware monitor layer, and biometrics DB schema/migrations (version reporting, snooze + prime completion notifications, ambient light + water level telemetry, and sleep record update/delete).

Changes:

  • Add new REST/tRPC endpoints for system version, sleep record update/delete, ambient light history/summary/latest, and water level history/trend/alerts + dismiss.
  • Add hardware-side in-memory managers for alarm snooze scheduling and prime completion notifications; integrate into device status and monitor shutdown.
  • Extend biometrics schema + migrations with ambient_light, water_level_readings, and water_level_alerts, and write water level readings from DeviceStateSync.

Reviewed changes

Copilot reviewed 19 out of 20 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/server/routers/waterLevel.ts New water level endpoints (history/latest/trend + alerts/dismiss).
src/server/routers/system.ts Adds GET /system/version reading build info from .git-info.
src/server/routers/environment.ts Adds ambient light endpoints backed by ambient_light table.
src/server/routers/device.ts Extends device status with snooze + prime completion notification; adds snooze + prime-dismiss endpoints; cancels snooze on alarm changes.
src/server/routers/biometrics.ts Adds sleep record update/delete mutations; adjusts vitals batch insert reporting.
src/server/routers/app.ts Registers the new waterLevel router.
src/server/openapi.ts Adds “Water Level” OpenAPI tag.
src/hardware/snoozeManager.ts New in-memory snooze timeout manager that re-triggers alarms after a delay.
src/hardware/primeNotification.ts New in-memory prime completion notification tracker + reset/dismiss helpers.
src/hardware/deviceStateSync.ts Writes water level readings to biometrics DB (rate-limited).
src/hardware/dacMonitor.instance.ts Tracks priming transitions; cancels snoozes and resets priming state on shutdown.
src/db/biometrics-schema.ts Adds Drizzle table definitions for ambient light + water level readings/alerts.
src/db/biometrics-migrations/meta/_journal.json Registers biometrics migration 0004.
src/db/biometrics-migrations/meta/0004_snapshot.json Snapshot for migration 0004 (schema state).
src/db/biometrics-migrations/0004_peaceful_mordo.sql Migration creating the 3 new biometrics tables + indexes.
scripts/generate-git-info.mjs New build-time script generating .git-info.
package.json Runs git-info generation via prebuild.
modules/common/calibration.py Adds CapSense2 calibration + presence detection logic for Pod 5.
modules/calibrator/main.py Prefers CapSense2 calibrator when capSense2 RAW records exist.
.gitignore Ignores generated .git-info.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +114 to +117
.where(and(
gte(waterLevelReadings.timestamp, midpoint),
sql`${waterLevelReadings.level} = 'low'`,
))
)

# Find quietest 5-min window across sensing channels only
window = min(self.MIN_WINDOW_S, len(timestamps))
Comment on lines +705 to +789
"water_level_alerts": {
"name": "water_level_alerts",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"started_at": {
"name": "started_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"dismissed_at": {
"name": "dismissed_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"message": {
"name": "message",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"water_level_readings": {
"name": "water_level_readings",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"timestamp": {
"name": "timestamp",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"level": {
"name": "level",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"idx_water_level_timestamp": {
"name": "idx_water_level_timestamp",
"columns": [
"timestamp"
],
"isUnique": false
}
Comment on lines +14 to +16
.meta({ openapi: { method: 'GET', path: '/water-level/history', protect: false, tags: ['Water Level'] } })
.input(z.object({
startDate: z.date().optional(),
ng and others added 2 commits March 15, 2026 19:10
- Replace raw SQL fragments with eq() in waterLevel trend queries
- Regenerate migration snapshot (fixes uniqueIndex + adds alerts index)
- Add corrective migration 0005 for index fixes
- Wire cancelSnooze into GestureActionHandler dismiss path
- Add unit tests for snoozeManager (6 tests) and primeNotification (6 tests)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The static import of snoozeManager pulls in dacMonitor.instance which
imports @/src/db — breaking gestureActionHandler.test.ts in CI where
the DB module can't resolve. Use dynamic import() instead.

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

🎉 This PR is included in version 1.1.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants