Add cancel() method to form_controller that handles all close cases:
- tabular forms (<tr>): removes inner form, re-enables link, shows hidden row
- measurements create form: removes self, enables trigger link, shows no-items
Data attributes already present on controller elements (data-form, data-link,
data-hidden-row) drive the behaviour. Two extra attributes on the create form
(data-cancel-enable, data-cancel-show) cover the non-tabular case.
Delete now-unused _form_close and _edit_form_close partial templates.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add stimulus-rails gem and wire up 7 controllers:
- measurements_view_controller: view toggle (compact/wide) via localStorage
- measurements_controller: grouped rows MutationObserver
- charts_controller: Plotly chart rendering
- form_controller: keyboard shortcuts (Escape/Enter) and submit validation
- details_controller: quantity picker state, focusout close, MutationObserver
- readout_unit_controller: default unit button enable/disable + PATCH submission
- drag_controller: drag-and-drop for quantity reparenting and unit rebasing
Remove all inline onclick/onkeydown/ondrag*/onsubmit handlers from templates.
Remove all window.* global exports from application.js.
Remove bare <script> block from measurements/_form.html.erb.
Remove turbo:load listeners for behavior now in controller connect().
application.js now only contains: Turbo Stream custom action definitions
and the showPage visibility listener.
Document Stimulus conventions in CLAUDE.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
readoutUnitChanged was manually setting disabled/aria-disabled/tabindex
attributes — duplicating Turbo.StreamElement.prototype.disableElement/
enableElement which already exists for this purpose. Replace with calls
to those methods.
Also replace fetch() in setDefaultUnit and drop with form.requestSubmit()
so Turbo handles CSRF, stream responses and lifecycle natively.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
setDefaultUnit and drop previously made raw fetch() requests and called
Turbo.renderStreamMessage() manually. Now both create a temporary <form>,
append hidden inputs, and call form.requestSubmit() — Turbo intercepts
the submission natively, handling CSRF, stream responses, and lifecycle.
Also document this convention in CLAUDE.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add _wide_table.html.erb partial (server-rendered pivot table)
- Add load_measurements helper in controller to prepare @wide_groups and
@wide_quantities for all mutating actions
- Update index view to render the wide_table partial in #measurements-wide
- Add/update create, destroy, update turbo_stream views to refresh the
wide table atomically after each mutation
- Remove buildWideTable() and editMeasurementWide() from application.js
- Fix create.turbo_stream.erb condition (empty readouts are vacuously all persisted)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the toggle-view approach and hidden DOM data carrier with a
proper dedicated Charts page:
- Move Charts out of Measurements view toggles into its own nav tab
and route (GET /charts)
- ChartsController serializes readout data as JSON (ordered by
taken_at); the view embeds it in a <script type="application/json">
element instead of rendering a hidden copy of the measurements
partial just to ferry data attributes to JS
- buildCharts() reads from the JSON element directly — no DOM parsing,
no sorting in JS (server already orders the data)
- Turbo load handler detects the charts page via #charts-data presence
- Add controller tests (authentication, data shape, ordering,
data isolation between users) and system tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Users can now switch to a Charts view that renders a separate
time-series line chart for each tracked quantity, using Plotly.js
loaded via CDN. Charts are sorted chronologically and styled to
match the app palette. A dedicated toggle button and matching
CSS visibility rules mirror the existing Compact/Wide view pattern.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* adding streams in client breaks things (e.g. autofocus)
* some tasks need to be performed in one stream action to avoid
flickering (e.g. table row substitution)