Best use case
home-sensor-dashboard is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Teams using home-sensor-dashboard should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/home-sensor-dashboard/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How home-sensor-dashboard Compares
| Feature / Agent | home-sensor-dashboard | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
This skill provides specific capabilities for your AI agent. See the About section for full details.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Skill: home-sensor-dashboard
## What this skill knows
This skill covers the home-sensor-dashboard application: a pnpm-workspaces monorepo with a Node.js/Express/SQLite backend and a React/Vite frontend. It covers the ingest API, alert threshold computation, polling hook, React store shape, and component architecture.
## Monorepo layout
```
home-sensor-dashboard/
packages/
server/src/
index.ts - Express entry + migrations
db.ts - better-sqlite3 singleton
routes/
ingest.ts - POST /api/ingest
status.ts - GET /api/status
sensors.ts - GET /api/sensors, PUT /api/sensors/:id
readings.ts - GET /api/readings/:sensorId
client/src/
components/
SensorCard/ - gauge + sparkline + badge
SensorGrid/ - responsive CSS grid
ReadingsChart/ - full Chart.js line chart
StatusTable/ - sensor list table
AlertBadge/ - ok/warn/alert/stale pill
pages/
DashboardPage.tsx
SensorDetailPage.tsx
store/useDashboardStore.ts
hooks/usePolling.ts
```
## Environment variables
```
PORT=3001
DB_PATH=./data/sensors.db
ALLOWED_ORIGIN=http://localhost:5173
```
## API routes
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/ingest` | Single or array of readings |
| GET | `/api/status` | Latest reading + alert_state per sensor |
| GET | `/api/sensors` | All sensors with metadata |
| PUT | `/api/sensors/:id` | Update name/thresholds |
| GET | `/api/readings/:id` | History with limit/from/to params |
## Ingest endpoint
Accepts a single object or an array:
```json
{ "sensor_id": "bedroom-temp", "value": 22.4, "unit": "C" }
```
```json
[
{ "sensor_id": "bedroom-temp", "value": 22.4, "unit": "C" },
{ "sensor_id": "bedroom-hum", "value": 61, "unit": "%" }
]
```
Returns `201 { ok: true, count: N }`.
## DB schema
```sql
CREATE TABLE IF NOT EXISTS sensors (
id TEXT PRIMARY KEY,
name TEXT NOT NULL DEFAULT '',
unit TEXT NOT NULL DEFAULT '',
alert_min REAL,
alert_max REAL,
created_at INTEGER NOT NULL,
last_seen INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS readings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sensor_id TEXT NOT NULL REFERENCES sensors(id),
value REAL NOT NULL,
unit TEXT NOT NULL,
ts INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_readings_sensor_ts ON readings (sensor_id, ts DESC);
```
## db.ts pattern
```ts
import Database from 'better-sqlite3';
const db = new Database(process.env.DB_PATH ?? './data/sensors.db');
db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');
export default db;
```
Always use WAL mode for better concurrent read performance.
## Alert state computation
```ts
function computeAlertState(value: number, min: number | null, max: number | null): 'ok' | 'warn' | 'alert' {
if (min === null && max === null) return 'ok';
if ((min !== null && value < min) || (max !== null && value > max)) return 'alert';
// WARN = within 10% of threshold
const warnMin = min !== null ? min * 1.1 : null;
const warnMax = max !== null ? max * 0.9 : null;
if ((warnMin !== null && value < warnMin) || (warnMax !== null && value > warnMax)) return 'warn';
return 'ok';
}
```
## Stale sensor detection
A sensor is stale when `Date.now() - last_seen > STALE_THRESHOLD_MS`.
Default: 60000ms (60 seconds). Controlled by the `STALE_THRESHOLD` env var (value in seconds).
The client checks staleness on each poll cycle:
```ts
const STALE_MS = 60_000;
const isStale = (sensor: SensorStatus) => Date.now() - sensor.ts > STALE_MS;
```
Stale sensors render with `opacity: 0.6` and show `badge-stale` class.
## usePolling hook
```ts
export function usePolling<T>(
url: string,
onData: (data: T) => void,
intervalMs: number = 10000
) {
useEffect(() => {
let active = true;
async function run() {
try {
const res = await fetch(url);
if (res.ok && active) onData(await res.json());
} catch { /* ignore network errors silently */ }
}
run();
const id = setInterval(run, intervalMs);
return () => { active = false; clearInterval(id); };
}, [url, intervalMs]);
}
```
## Zustand store
```ts
interface DashboardStore {
sensors: SensorStatus[];
selectedSensorId: string | null;
readings: Record<string, Reading[]>;
setSensors(sensors: SensorStatus[]): void;
setSelectedSensor(id: string | null): void;
setReadings(sensorId: string, readings: Reading[]): void;
}
```
## SensorCard sparkline
The sparkline is a mini Chart.js `line` chart with:
- `animation: false`
- no axes, no legend, no title
- `pointRadius: 0`
- `borderColor: '#d97706'`
- height: 36px
When alert state is `alert`, use `borderColor: '#dc2626'`.
## ReadingsChart
Full-size Chart.js line chart on the detail page:
- x-axis: ISO timestamp labels
- y-axis: auto-scale
- `animation: false`
- gradient fill from `rgba(217,119,6,0.2)` to transparent
- Threshold lines using Chart.js annotation plugin (or custom horizontal line plugin)
## StatusTable columns
| Column | Field | Notes |
|--------|-------|-------|
| Sensor | `sensor.name` or `sensor.id` | Link to detail page |
| Value | `sensor.value` | Right-aligned monospace bold |
| Unit | `sensor.unit` | Secondary text |
| Last Seen | `sensor.ts` | Relative time e.g. "3s ago" |
| Status | `sensor.alert_state` | AlertBadge component |
## Ingest transaction pattern
Use a better-sqlite3 transaction for batch inserts:
```ts
const insertMany = db.transaction((items: IngestItem[]) => {
for (const item of items) {
upsertSensor.run(item.sensor_id, item.sensor_id, item.unit, now, now);
insertReading.run(item.sensor_id, item.value, item.unit, now);
}
});
insertMany(items);
```
The `ON CONFLICT(id) DO UPDATE` pattern (upsert) updates `last_seen` and `unit` without changing other columns.
## Common mistakes
- Using `async` with better-sqlite3 - all its methods are synchronous. Never use `await db.prepare(...)` or `await stmt.run(...)`.
- Not creating the `data/` directory before opening the database. Check/create with `fs.mkdirSync(path.dirname(DB_PATH), { recursive: true })`.
- Querying without the index: always filter by `sensor_id` first, then order by `ts DESC LIMIT N`. This uses the composite index.
- Polling hook running multiple fetches concurrently: use the `active` flag and `clearInterval` to avoid state updates after unmount.
- Using the sensor `id` field as the display name - always prefer `name` (which defaults to `id` on first registration).Related Skills
vitals-dashboard
Track blood pressure, heart rate, weight, blood glucose, and temperature over time with target range bands, trend analysis, alerts for out-of-range readings, correlation analysis, and CSV/PDF export. Use when a user needs to log vital sign readings, review trends, acknowledge alerts, or produce a health summary report.
time-series-dashboard
Manage dashboards, metrics, alert rules, and query time-series data in the time-series-dashboard application. Use this skill for all operations against the running server.
pomodoro-dashboard
Browser-only Pomodoro timer SPA with task tracking, focus stats, and a 12-week activity heatmap. All data stored in localStorage. No backend required.
weather-station-dashboard
Query live and historical weather sensor data, manage stations and alert thresholds, and retrieve daily summaries. Use when asked to check current temperature, view recent readings for a station, find the highest temperature recorded this week, dismiss an active alert, list which stations are online, or retrieve today's daily summary. Triggers include "current temperature", "sensor readings", "station status", "alert history", "daily summary", "wind speed", "humidity", "rainfall total", or any query about live or historical weather data.
sensor-ingest
Configure and troubleshoot sensor data ingest into weather-station-dashboard via MQTT or HTTP POST. Use when asked to set up MQTT broker connection, debug why readings are not appearing, configure a new sensor sending data over HTTP, check MQTT topic configuration, test sensor connectivity, or understand the ingest pipeline. Triggers include "MQTT not receiving", "sensor not sending", "configure MQTT", "HTTP POST reading", "ingest setup", "topic configuration", "reading not showing up", or any task involving getting sensor data into the system.
Skill: Uptime Monitoring
## Overview
Skill: Status Page
## Overview
Skill: unit-conversion
## Overview
Skill: recipe-scaler
## Overview
reading-list
Operate the reading-list API to save, manage, tag, search, and export articles.
email-digest
Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.
websocket-realtime
Use the WebSocket connection in poll-builder to receive live vote updates. Use when you need to stream real-time poll results, monitor a poll for new votes, or build a live dashboard. Triggers include "live results", "real-time updates", "stream votes", "watch poll", or "WebSocket".