Skip to content

Add timeline, lifecycle, dependencies, search, calendar, reminders, attachments - #3

Merged
joewolly merged 3 commits into
mainfrom
claude/missing-value-features-zssxle
Sep 19, 2026
Merged

joewolly merged 3 commits into
mainfrom
claude/missing-value-features-zssxle

Conversation

@joewolly

@joewolly joewolly commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Adds activity timelines, device lifecycle and dependency tracking, full-text search, calendar feeds, reminders, Markdown, bulk actions, and attachments while preserving the dependency-free Node.js and single-SQLite-file design.

  • Record ticket changes transactionally and show them alongside comments.
  • Track device serial numbers, purchase cost, warranties, and parent dependencies; generate warranty tickets without duplicates.
  • Index ticket titles, bodies, and comments with FTS5, with a literal-search fallback.
  • Export due dates and schedules as iCalendar and send optional due-soon reminders and daily or weekly digests.
  • Store attachment bytes in SQLite; preserve Unicode filenames through upload and download using an ASCII fallback and UTF-8 Content-Disposition parameter.
  • Support Markdown and atomic bulk close, resolve, and tag actions.
  • Correct direct server startup on Windows and paths containing spaces, and report the actual listening port.

Requires Node 22.16 or newer. Includes versioned schema migrations and expanded configuration and API documentation.

Validation: all 188 tests pass locally on Windows with Node 22.16.0, 24.21.0, and 26.8.1. Coverage includes Unicode attachment round trips, authentication enforcement at executable startup, authenticated export and metrics, and ticket persistence across a process restart. Frontend JavaScript syntax checks and git diff --check pass. Hosted CI and Docker validation remain separate gates.

Summary by CodeRabbit

  • New Features

    • Added Markdown rendering, ticket activity timelines, attachments, full-text search, and bulk ticket actions.
    • Expanded device records with lifecycle, warranty, cost, and dependency information.
    • Added calendar feed export for open tickets and schedules.
    • Added due-soon reminders, backlog digests, and automated warranty alerts.
    • Added warranty-aware metrics and richer device exports.
  • Bug Fixes

    • Improved validation, attachment security, dependency checks, notification deduplication, and calendar authentication.
  • Documentation

    • Expanded setup and feature documentation, including configuration options and API capabilities.

…ttachments

Six high-value features plus Markdown, bulk edits, and attachments, all within
the existing no-dependency / single-SQLite-file design.

- Activity timeline: an append-only ticket_events log records status, priority,
  device, due-date, title, and tag changes, recorded inside the same
  transaction as the change and shown interleaved with comments.
- Device lifecycle: serial number, purchase date, warranty expiry, and cost,
  with a maintenance-sweep warranty check that opens one ticket per lapse.
- Device dependencies: a self-referential parent link with a cycle guard, and a
  device page that shows what it depends on and what depends on it.
- Full-text search: an FTS5 index over title, body, and comments kept current
  by triggers, with a safe query builder and a LIKE fallback.
- Calendar feed: GET /api/calendar.ics of due dates and schedules, with the API
  token accepted in the query string for that read-only endpoint only.
- Reminders and digest: a due-soon nudge that re-arms when a due date moves, and
  an opt-in daily/weekly backlog summary whose cadence is persisted in a meta
  table.
- Attachments stored as BLOBs (single-file backup preserved) behind a short
  type allowlist; safe Markdown rendering for descriptions and notes; bulk
  close/resolve/tag from the ticket list.

Adds migration 4, four new api/ modules, and 28 tests (150 -> 178).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xiY1C9gt2Z14c2zhQHNS8
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: c689ab4e-3d02-435f-b0d3-8d2f9d681f19

📥 Commits

Reviewing files that changed from the base of the PR and between 9ce93ba and 81f3ae9.

📒 Files selected for processing (5)
  • README.md
  • src/api/metrics.js
  • src/server.js
  • test/http.test.js
  • test/startup.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • README.md

Included review availability: 9 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 10 reviews per hour.


📝 Walkthrough

Walkthrough

The application adds ticket activity history, full-text search, bulk updates, Markdown rendering, attachments, calendar feeds, device lifecycle data, warranty sweeps, due-soon reminders, and notification digests.

Changes

Ticket operations and maintenance

Layer / File(s) Summary
Data and device foundation
src/db.js, src/api/devices.js, src/validate.js, src/api/export.js, src/api/metrics.js, public/app.js, test/devices.test.js
The schema and device APIs now support lifecycle fields, dependency links, warranty state, exports, metrics, validation, and interface fields.
Ticket activity, search, and bulk updates
src/api/events.js, src/api/tickets.js, public/app.js, public/styles.css, src/server.js, test/tickets.test.js
Tickets now record activity events, use synchronized FTS5 search, support transactional bulk updates, and render Markdown activity timelines.
Attachments and calendar feeds
src/api/attachments.js, src/api/calendar.js, src/auth.js, src/server.js, public/app.js, public/styles.css, test/http.test.js, test/hardening.test.js
The application stores validated attachments, serves iCalendar feeds, authenticates calendar query tokens, and exposes upload, download, deletion, and feed links.
Maintenance notifications and warranty sweeps
src/config.js, src/notify.js, src/api/warranty.js, src/server.js, .env.example, test/ops.test.js
Maintenance now processes warranty alerts, due-soon reminders, and daily or weekly backlog digests with persisted delivery state.
Runtime support and documentation
package.json, README.md, src/server.js, test/startup.test.js
The minimum Node.js version and documentation now describe the expanded features, APIs, configuration, maintenance behavior, and module layout.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MaintenanceScheduler
  participant ServerMaintenance
  participant WarrantySweep
  participant Notifier
  participant Database
  MaintenanceScheduler->>ServerMaintenance: run periodic maintenance
  ServerMaintenance->>WarrantySweep: sweepWarranties()
  WarrantySweep->>Database: create warranty tickets and mark devices
  ServerMaintenance->>Notifier: sweepDueSoon() and maybeSendDigest()
  Notifier->>Database: read tickets and persist notification state
Loading

Suggested reviewers: claude

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.62% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 58 functions across 12 files. (1 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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 changes, including timelines, lifecycle tracking, dependencies, search, calendar feeds, reminders, and attachments.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 58.62% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 58 functions across 12 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🧹 Nitpick comments (9)
src/db.js (1)

201-213: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Add a trigger for comment updates.

The index tracks comment inserts and deletes only. If a comment body is ever made editable, the search index silently keeps the old text. An AFTER UPDATE ON comments trigger costs one statement and removes that failure mode.

♻️ Proposed trigger
   CREATE TRIGGER comments_fts_ad AFTER DELETE ON comments BEGIN
     UPDATE tickets_fts
        SET comments = (SELECT coalesce(group_concat(body, ' '), '') FROM comments
                         WHERE ticket_id = old.ticket_id)
      WHERE rowid = old.ticket_id;
   END;
+
+  CREATE TRIGGER comments_fts_au AFTER UPDATE ON comments BEGIN
+    UPDATE tickets_fts
+       SET comments = (SELECT coalesce(group_concat(body, ' '), '') FROM comments
+                        WHERE ticket_id = new.ticket_id)
+     WHERE rowid = new.ticket_id;
+  END;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/db.js` around lines 201 - 213, Add an AFTER UPDATE trigger for the
comments table alongside comments_fts_ai and comments_fts_ad. Recompute
tickets_fts.comments with group_concat(body, ' ') for the updated comment’s
ticket_id, using the existing ticket rowid relationship so edited comment bodies
replace stale indexed text.
test/tickets.test.js (2)

289-289: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace the filler assertion.

assert.equal(a.id > 0, true) only keeps a referenced. Assert something meaningful instead, for example that a term unique to ticket a returns only that ticket.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/tickets.test.js` at line 289, Replace the filler assertion in the ticket
test with a meaningful behavioral assertion that verifies searching for a term
unique to ticket a returns only ticket a. Keep the assertion focused on the
relevant search result and use the existing ticket/search symbols in the
surrounding test.

329-333: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

The test name promises an oversized case that is absent.

Both assertions cover empty or missing ids. The MAX_BULK branch at src/api/tickets.js Line 223-225 stays untested. Add a case, or rename the test.

💚 Proposed test addition
 test('a bulk update rejects an empty or oversized id list', () => {
   const db = fresh();
   assert.throws(() => bulkUpdateTickets(db, { ids: [], status: 'closed' }), { status: 400 });
   assert.throws(() => bulkUpdateTickets(db, {}), { status: 400 });
+  const tooMany = Array.from({ length: 501 }, (_, i) => i + 1);
+  assert.throws(() => bulkUpdateTickets(db, { ids: tooMany, status: 'closed' }), { status: 400 });
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/tickets.test.js` around lines 329 - 333, Update the test around “a bulk
update rejects an empty or oversized id list” to cover the oversized-list
validation branch in bulkUpdateTickets, using a non-empty ids array larger than
the configured MAX_BULK limit and asserting status 400. Keep the existing empty
and missing-ids assertions.
test/devices.test.js (1)

149-161: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a case with a string identifier.

Every assertion passes numeric ids, so the tests cannot detect the strict-equality gap in assertParentIsSafe. HTTP route parameters arrive as strings. Add updateDevice(db, String(a.id), { parent_id: a.id }) and expect a 400.

💚 Proposed test addition
   assert.throws(() => updateDevice(db, a.id, { parent_id: a.id }), { status: 400 });
+  // Route parameters arrive as strings; the guard must still reject self-parenting.
+  assert.throws(() => updateDevice(db, String(a.id), { parent_id: a.id }), { status: 400 });
   assert.throws(() => createDevice(db, { name: 'c', parent_id: 9999 }), { status: 400 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/devices.test.js` around lines 149 - 161, Extend the “refuses a parent…”
test around assertParentIsSafe/updateDevice with a case that passes String(a.id)
as the device identifier while retaining a.id as parent_id, and assert it
rejects with status 400. Keep the existing numeric self-parent, missing-parent,
and loop assertions unchanged.
src/api/devices.js (2)

61-69: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the shared open-ticket count expression.

The dependents query repeats the correlated subquery already present in SELECT_DEVICE at Line 23-24. Extract it into one constant so both stay consistent when the closed-status rule changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/devices.js` around lines 61 - 69, Extract the correlated open-ticket
count SQL expression currently duplicated in SELECT_DEVICE and the dependents
query into a shared constant, then interpolate that constant in both queries.
Preserve the existing device aliases and CLOSED_LIST filtering so both queries
remain consistent.

170-188: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Guard the parent walk against pre-existing cycles.

The route already converts params.id to a number. Add a visited-ID Set; the foreign key does not prevent existing cycles, which can make this loop run forever.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/devices.js` around lines 170 - 188, Update assertParentIsSafe to
track each cursor in a visited-ID Set during the parent walk, and stop or reject
when an ID repeats so pre-existing cycles cannot cause an infinite loop.
Preserve the existing self-dependency and new-loop validation behavior.
src/api/metrics.js (1)

73-78: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the metric window with WARRANTY_ALERT_DAYS.

The gauge hardcodes 30 days. README Line 256 documents WARRANTY_ALERT_DAYS as the configurable window for the warranty sweep. If an operator changes that value, the metric and the ticket sweep report different sets. The help text also says "lapses within 30 days", but the query counts already-expired warranties as well.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/metrics.js` around lines 73 - 78, Update the
homelab_warranties_expiring metric query and help text to use the configured
WARRANTY_ALERT_DAYS window instead of hardcoded 30-day wording or SQL. Match the
warranty sweep’s boundary semantics so only non-retired devices with warranties
expiring from now through the configured window are counted, keeping the metric
aligned with the sweep.
src/api/tickets.js (1)

230-233: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider returning identifiers instead of full tickets.

At the 500-ticket ceiling the response embeds 500 hydrated tickets, each carrying its comments, links, attachments, and event history. getTicket also runs four queries per ticket. The client at public/app.js Line 604-606 only reads result.updated. Returning ids would cut both the query count and the payload size.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/tickets.js` around lines 230 - 233, Update the transaction return in
the ticket update flow around updateTicket so it returns the updated ticket
identifiers rather than hydrated ticket objects. Avoid calling the full ticket
hydration path for each id, while preserving the existing updated count and the
client-facing result.updated field.
src/api/events.js (1)

9-19: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the unused EVENT_LABELS export. public/app.js:eventLine renders event text independently, and recordChanges records status changes rather than reopened; remove the stale reopened entry with the map.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/events.js` around lines 9 - 19, Remove the unused EVENT_LABELS export
and its entire map from the events module, including the stale reopened entry;
eventLine and recordChanges do not depend on it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@public/app.js`:
- Around line 609-621: Update addTag to use the transactional /tickets/bulk
endpoint instead of sequential per-ticket PATCH requests, adding server-side
merge semantics so the new tag is preserved alongside each ticket’s existing
tags. Ensure bulk tagging is atomic and the success toast/render behavior
remains correct.
- Around line 883-887: Update the Activity heading in the card-rendering
expression to count the rendered entries using entries.length instead of
ticket.comments.length, keeping the existing entries.map(row) list unchanged.
- Around line 644-664: Update the call site that maps open tickets to use an
explicit single-argument callback, such as open.map((ticket) =>
ticketRow(ticket)), so ticketRow does not receive the array index as onToggle.
Preserve ticketRow’s existing optional toggle behavior for callers that
intentionally provide a callback.
- Around line 905-918: Update the upload handler around the X-Filename header to
pass an encodeURIComponent-encoded file.name, then update uploadAttachment to
decode that header with decodeURIComponent before forwarding the filename to
addAttachment. Preserve filename handling for Latin-1 and non-Latin-1 names.

In `@public/styles.css`:
- Around line 655-656: Update the .attachment delete-control styles so keyboard
focus also reveals the control, adding a focus-visible condition alongside the
existing hover rule while preserving the current transition and layout behavior.

In `@README.md`:
- Around line 81-83: Update the fenced code block containing the calendar API
URL to specify the text language identifier, changing the opening fence to use
text while preserving the URL example unchanged.
- Around line 121-126: Rewrite the warranty-check sentence in the README so it
directly states that editing the warranty date re-arms the alert, replacing the
incomplete phrase “editing the date re-arming it” while preserving the
surrounding behavior and configuration details.

In `@src/api/attachments.js`:
- Around line 66-72: Update deleteAttachment to refresh the parent ticket’s
timestamp after the attachment deletion succeeds, matching the behavior in
addAttachment. Use the existing ticket update mechanism and ticketId, ensuring
it runs only after db.prepare('DELETE FROM attachments...').run completes.

In `@src/api/tickets.js`:
- Around line 218-234: Deduplicate the validated ticket IDs in bulkUpdateTickets
before enforcing the MAX_BULK limit and running the transaction. Use the
deduplicated IDs for updateTicket calls and the updated count, while preserving
requiredId validation for every input ID.

In `@src/db.js`:
- Around line 176-186: Raise the project’s minimum Node.js version to 22.16.0 or
newer in package.json and README.md so the tickets_fts migration can rely on
node:sqlite FTS5 support. Keep the existing FTS5 migration unchanged; update all
documented and enforced version constraints consistently.

In `@src/notify.js`:
- Around line 151-168: Update maybeSendDigest to atomically reserve a digest
lease in the meta table before awaiting deliver(), so concurrent calls cannot
both send. Treat unexpired digest lease values as unavailable, expire abandoned
leases using the configured cadence, clear the lease when delivery fails, and
replace it with digest_last_sent only after successful delivery.
- Around line 116-135: Update the ticket-claiming loop in sweepDueSoon around
the mark statement so the UPDATE only succeeds when due_soon_notified_at is
still NULL. Send the ticket.due_soon notification and add its ID to sent only
when mark.run reports one changed row; skip delivery when another overlapping
sweep already claimed it.

---

Nitpick comments:
In `@src/api/devices.js`:
- Around line 61-69: Extract the correlated open-ticket count SQL expression
currently duplicated in SELECT_DEVICE and the dependents query into a shared
constant, then interpolate that constant in both queries. Preserve the existing
device aliases and CLOSED_LIST filtering so both queries remain consistent.
- Around line 170-188: Update assertParentIsSafe to track each cursor in a
visited-ID Set during the parent walk, and stop or reject when an ID repeats so
pre-existing cycles cannot cause an infinite loop. Preserve the existing
self-dependency and new-loop validation behavior.

In `@src/api/events.js`:
- Around line 9-19: Remove the unused EVENT_LABELS export and its entire map
from the events module, including the stale reopened entry; eventLine and
recordChanges do not depend on it.

In `@src/api/metrics.js`:
- Around line 73-78: Update the homelab_warranties_expiring metric query and
help text to use the configured WARRANTY_ALERT_DAYS window instead of hardcoded
30-day wording or SQL. Match the warranty sweep’s boundary semantics so only
non-retired devices with warranties expiring from now through the configured
window are counted, keeping the metric aligned with the sweep.

In `@src/api/tickets.js`:
- Around line 230-233: Update the transaction return in the ticket update flow
around updateTicket so it returns the updated ticket identifiers rather than
hydrated ticket objects. Avoid calling the full ticket hydration path for each
id, while preserving the existing updated count and the client-facing
result.updated field.

In `@src/db.js`:
- Around line 201-213: Add an AFTER UPDATE trigger for the comments table
alongside comments_fts_ai and comments_fts_ad. Recompute tickets_fts.comments
with group_concat(body, ' ') for the updated comment’s ticket_id, using the
existing ticket rowid relationship so edited comment bodies replace stale
indexed text.

In `@test/devices.test.js`:
- Around line 149-161: Extend the “refuses a parent…” test around
assertParentIsSafe/updateDevice with a case that passes String(a.id) as the
device identifier while retaining a.id as parent_id, and assert it rejects with
status 400. Keep the existing numeric self-parent, missing-parent, and loop
assertions unchanged.

In `@test/tickets.test.js`:
- Line 289: Replace the filler assertion in the ticket test with a meaningful
behavioral assertion that verifies searching for a term unique to ticket a
returns only ticket a. Keep the assertion focused on the relevant search result
and use the existing ticket/search symbols in the surrounding test.
- Around line 329-333: Update the test around “a bulk update rejects an empty or
oversized id list” to cover the oversized-list validation branch in
bulkUpdateTickets, using a non-empty ids array larger than the configured
MAX_BULK limit and asserting status 400. Keep the existing empty and missing-ids
assertions.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d77cf83d-7210-40e4-bba1-7a093afe8b08

📥 Commits

Reviewing files that changed from the base of the PR and between ff6d599 and 85ab903.

📒 Files selected for processing (23)
  • .env.example
  • README.md
  • public/app.js
  • public/styles.css
  • src/api/attachments.js
  • src/api/calendar.js
  • src/api/devices.js
  • src/api/events.js
  • src/api/export.js
  • src/api/metrics.js
  • src/api/tickets.js
  • src/api/warranty.js
  • src/auth.js
  • src/config.js
  • src/db.js
  • src/notify.js
  • src/server.js
  • src/validate.js
  • test/devices.test.js
  • test/hardening.test.js
  • test/http.test.js
  • test/ops.test.js
  • test/tickets.test.js

Comment thread public/app.js
Comment thread public/app.js
Comment thread public/app.js
Comment thread public/app.js
Comment thread public/styles.css Outdated
Comment thread src/api/attachments.js
Comment thread src/api/tickets.js
Comment thread src/db.js
Comment thread src/notify.js
Comment thread src/notify.js Outdated
Fixes verified against the code; low-value nitpicks skipped.

- Bulk tagging is now atomic: a new add_tags option on /tickets/bulk merges a
  label onto each ticket's own set inside the one transaction, replacing the
  client's sequential per-ticket PATCH loop.
- bulkUpdateTickets dedupes ids before the batch limit and the transaction, so
  a repeated id is neither updated nor counted twice.
- Digest sending claims an atomic lease in meta before delivering, releasing it
  on failure, so an ad-hoc /api/maintenance/run overlapping the timer cannot
  double-send. The due-soon sweep claims each ticket with a conditional UPDATE
  for the same reason.
- deleteAttachment bumps the ticket's updated_at, matching addAttachment.
- assertParentIsSafe coerces the id to a number (a string id no longer slips the
  self-check) and tracks visited devices so a corrupted pre-existing cycle can't
  loop forever.
- Attachment filenames are percent-encoded into X-Filename and decoded server
  side, so accented and emoji names survive the Latin-1 header.
- The warranty metric uses the configured WARRANTY_ALERT_DAYS window.
- Frontend: Activity heading counts rendered entries; device open-ticket list
  no longer passes the map index as the selection callback.
- New migration adds the missing AFTER UPDATE comments FTS trigger; shared the
  open-ticket count SQL; removed the unused EVENT_LABELS export.
- Node floor raised to 22.16.0 (FTS5); attachment delete control reveals on
  keyboard focus; README fence language and warranty wording fixed.
- Tests: bulk oversize/dedupe/add_tags, string-id self-parent, a meaningful
  search assertion (180 passing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xiY1C9gt2Z14c2zhQHNS8

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/api/metrics.js`:
- Around line 76-80: Update the help text for the homelab_warranties_expiring
metric to state that the gauge includes already expired warranties, matching the
existing SQL query’s behavior; leave the query unchanged.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 60a6f52b-a8b4-468f-bf5f-a6e92987530c

📥 Commits

Reviewing files that changed from the base of the PR and between 85ab903 and 9ce93ba.

📒 Files selected for processing (14)
  • README.md
  • package.json
  • public/app.js
  • public/styles.css
  • src/api/attachments.js
  • src/api/devices.js
  • src/api/events.js
  • src/api/metrics.js
  • src/api/tickets.js
  • src/db.js
  • src/notify.js
  • src/server.js
  • test/devices.test.js
  • test/tickets.test.js
💤 Files with no reviewable changes (1)
  • src/api/events.js
🚧 Files skipped from review as they are similar to previous changes (10)
  • test/devices.test.js
  • public/styles.css
  • test/tickets.test.js
  • src/api/attachments.js
  • src/notify.js
  • src/db.js
  • src/server.js
  • README.md
  • src/api/tickets.js
  • public/app.js

Comment thread src/api/metrics.js
@joewolly
joewolly merged commit 4a93e91 into main Sep 19, 2026
9 of 17 checks passed
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