Implements feature request: https://community.gladysassistant.com/t/visualiser-m…odifier-supprimer-une-valeur-dans-lhistorique-dun-element/9899
> **This PR was opened by an automated Claude Code run. It needs a human review before merging** — nobody has tested it against a real installation with real devices.
### Description
When a sensor glitches, the wrong value stays in the history forever: there was no way to look at the raw recorded values of a device feature, let alone fix or remove one. This PR adds a small editor for that.
**Server** — three new functions on the device manager, and three routes next to the existing `device_feature` ones:
| Route | Function |
| --- | --- |
| `GET /api/v1/device_feature/:device_feature_selector/state` | `device.getDeviceFeatureStatesPaginated` |
| `PATCH /api/v1/device_feature/:device_feature_selector/state` | `device.updateState` |
| `DELETE /api/v1/device_feature/:device_feature_selector/state` | `device.destroyState` |
- `getDeviceFeatureStatesPaginated(selector, { from, to, take, skip })` returns `{ total, take, skip, states }`, most recent first. The query is **always bounded in time** (it defaults to the last 7 days when no range is given): there is no index on `device_feature_id` in DuckDB, so an unbounded query would scan the whole history, whereas a bounded window is pruned thanks to the per-row-group min/max metadata — same reasoning as the progressive windows in `device.getDeviceStatesHistory`.
- `updateState(selector, createdAt, newValue)` and `destroyState(selector, createdAt)` act on a single state, identified by its `(device_feature_id, created_at)` pair — the same key `db.duckDbUpdateState` already uses in `device.saveHistoricalState`. They 404 when the feature or the state does not exist, and 400 on an invalid date / non-numeric value.
- `refreshFeatureLastValue(deviceFeature)`: `last_value` / `last_value_changed` on `t_device_feature` is a denormalized copy of the most recent state. When the edited state *is* that most recent one, the last value is recomputed from the history and propagated to the DB, the state manager and the `device.new-state` websocket message — otherwise deleting the glitch would leave the device page still showing it, which is exactly what the request is about. When the edited state is older, nothing is touched (no extra query).
- All three routes use `authenticated: true`, like the neighbouring device / device_feature routes (`delete /api/v1/device/:device_selector` is authenticated-only too).
**Front** — a new page at `/dashboard/devices/:device_selector/history`, reachable from the devices list (a "list" button on the desktop table row and on the mobile item).
Why there: the devices list is where a device is looked up today, and it is the only place in the navigation that maps 1:1 to a device. The dashboard chart box is per-dashboard and per-metric (and is an editing surface for the box, not for the data), and `/dashboard/history` is a cross-device activity feed with its own live-buffering/grouping logic — grafting a per-feature editor into either would have been more invasive than a dedicated page.
The page has a feature selector, a from/to date range (last 7 days by default), a paginated table of the raw values, inline edit of a value, and delete with an inline confirmation.
i18n added in **en**, **fr** and **de**. Demo-mode fixtures added in `front/src/config/demo.js` for the two demo devices and their features, plus a small fallback in `DemoHttpClient.delete` so a `DELETE` URL carrying a dynamic query string resolves to the URL-only key (the same fallback `get` already has).
### Explicitly out of scope
- **No recomputation of derived data.** Correcting or deleting a state does not re-run the energy/cost calculation that may already have consumed it (`energy-monitoring.calculateEnergyFromIndex`, `calculateCostFrom`, …). A warning to that effect is displayed on the page. Wiring a "recalculate from this date" action into the existing energy jobs felt like a separate, larger change; happy to follow up if you want it here.
- `t_device_feature_state_aggregate` is not touched — nothing in the current code writes it (it is only ever deleted, by `purgeStatesByFeatureId` / `device.migrate`), so there is no aggregate to keep in sync.
- No bulk edit / bulk delete over a selection; only one value at a time. `device.destroyStatesFrom` already covers "delete everything from a date".
## Forum
Forum: https://community.gladysassistant.com/t/visualiser-modifier-supprimer-une-valeur-dans-lhistorique-dun-element/9899
### Checklist
- [x] Tests pass: `cd server && npm run coverage` (Codecov requires 100% coverage on changed lines) and Cypress (`npm run cypress:run`) if the UI changed
- New tests: `server/test/lib/device/device.getDeviceFeatureStatesPaginated.test.js`, `device.updateState.test.js`, `device.destroyState.test.js`, and a `Device feature state edition` block in `server/test/controllers/device/device.controller.test.js`. They cover every branch of the four new lib files, including the `refreshFeatureLastValue` paths (most recent state edited, older state edited, feature with no `last_value_changed`, and history emptied by the delete).
- `test/lib/device/**` + `test/controllers/device/**`: **354 passing**. `npm run coverage` itself was not run (no Codecov here).
- The full `npm test` run has pre-existing failures in this sandbox only — gateway backup/restore (`sqlite3: not found`), Docker-dependent external-integration tests and gateway two-factor/AI tests. None of them touch the device layer, and none of the files they exercise are modified here.
- Cypress was **not** run.
- [x] Linter and prettier pass on both front and server (`npm run eslint`, `npm run prettier`)
- `server`: `npm run eslint` → 0 errors, `npm run prettier-check` → clean.
- `front`: `npm run eslint` → 0 errors, `npm run prettier-check` → clean, `npm run compare-translations` → complete in the 3 languages, `npm run build` → OK.
- [x] No undocumented breaking change
---
_Generated by [Claude Code](https://claude.ai/code/session_013yxguVaLdJ8ZKmw5x3HePT)_
## Summary by CodeRabbit
* **New Features**
* Added device history pages accessible from each device.
* View historical feature values with date and feature filters, pagination, and timestamps.
* Edit or delete recorded values with confirmation and localized feedback.
* Added support for English, German, and French translations.
* **Bug Fixes**
* Improved demo handling for delete requests with query parameters.
* **Tests**
* Added coverage for history retrieval, pagination, editing, deletion, validation, and state refresh behavior.