Repository navigation
Quick planting addition form - #4808
Merged
Merged
Conversation
crops:pull_from_production copies approved crops, and members:pull_from_production copies one member's gardens, plantings and planting photos, from growstuff.org into a local database. Both refuse to run in a production environment. They are built to be gentle on a server that has been struggling: one request at a time with a pause between requests, exponential backoff (honouring Retry-After) on timeouts, 429s and 5xx, and a hard stop rather than endless retries. The client also stops if a server keeps sending the same page, and will not follow a next link to another host. Gardens and plantings come from the site's own JSON, which carries every column; the JSON:API only exposes a garden's name, and its related resource endpoints return the wrong records. Only the crops that a member's plantings use are fetched. Flickr photos are stored as records pointing at Flickr's image URLs, as on production, so nothing is downloaded from Flickr. Co-Authored-By: Claude Sonnet 5 <[email protected]>
ACTIVE_ONLY=true leaves out the site's all=1 flag, so production lists only the member's active gardens and current (not finished, not failed) plantings. The server does the filtering, so it also means fewer requests, and no crops or photos are fetched for plantings that are skipped. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Adds React and an esbuild build (yarn build, yarn build:watch) that writes app/assets/builds/react_islands.js, which Sprockets serves. jsbundling-rails runs the build as part of assets:precompile, which CI and the Dockerfile already run after yarn install. react_islands.jsx is the entry point: it mounts a React component into every element with data-react-component. No components are registered yet. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Renders an empty element carrying the component name and its props as JSON, for react_islands.jsx to mount a React component into. Props go through Rails' attribute escaping. Co-Authored-By: Claude Sonnet 5 <[email protected]>
The first React island, to prove the pipeline end to end: server-rendered props (owner and garden count) drive a small component with client-side state. Loaded only on this page, deferred, via the scripts content block. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Turns a page of gardens into plain hashes: the garden, its owner, its actions menu, and its active plantings split into perennials and annuals, with progress, badges and quick actions. It mirrors what gardens/_card, gardens/_actions and the planting partials render today, so the server still decides labels, links and permissions. Active plantings for the whole page are loaded in one query (with harvests and the crop's parents), instead of several queries per garden. Co-Authored-By: Claude Sonnet 5 <[email protected]>
The gardens index now hands GardenCardSerializer's output to a GardenCards island instead of rendering gardens/_card per garden. The actions menu moves to the top right of the card header. Everything else on the card is the same: image, perennials, and each annual planting with badges, progress bar and its own quick actions menu. The server still chooses labels, links and permissions. The dropdowns keep the existing Bootstrap and jquery_ujs markup (data-bs-toggle, data-method, data-confirm), so mark inactive, delete and the confirmations work unchanged. Menu button ids are now unique per card, so the edit-from-index spec finds the menu by its Actions text instead of the old repeated id. gardens/_card stays for the activity page. This replaces the GardenSummary island, which only proved the pipeline. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Member
Author
owner_must_match_garden_owner called garden.owner unconditionally, so a planting with a missing garden raised NoMethodError (a 500) instead of failing the garden presence validation. Co-Authored-By: Claude Sonnet 5 <[email protected]>
POST /plantings.json returns the garden's updated card (201), or the validation errors (422), so the cards can show a new planting without a page reload. It is for our own pages only: session cookie plus CSRF token, and no CORS headers, unlike the public /api/v1. Creating now also checks the garden is yours. Before, can :create Planting had no garden condition and create never authorized the garden, so a crafted request could plant into someone else's garden. This applies to the HTML form as well. A missing garden is still left to the presence validation. CanCan::AccessDenied now answers JSON requests with a 403 and an error, instead of redirecting them. The request spec logs in through a real cookie session to exercise CSRF protection: PlantingsController uses the null_session strategy, so a request without the token is treated as signed out (401), and the sign_in test helper would bypass that. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Arriving at the planting form with a garden_id of one of your own gardens (from 'Plant something here') now shows 'Planting in <garden>' with a hidden garden field and a link to choose a different one, instead of the garden radio buttons. Any other arrival, including a garden_id that isn't yours, still asks as before. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Clicking 'Plant something here' on a card opens a dialog to plant into that garden, without leaving the list or asking which garden. Saving posts to POST /plantings.json, swaps in the garden's updated card so the new planting shows straight away (briefly highlighted, with a confirmation), and closes the dialog. Errors show inside it and it stays open. A ctrl/cmd/shift-click still opens the normal form. The dialog is a React-controlled Bootstrap modal: focus moves in and is trapped, Escape and the backdrop close it, and focus returns afterwards. The crop picker searches /crops/search.json like the existing autosuggest, and the planted-from and sun choices come from Planting's constants via props. Co-Authored-By: Claude Sonnet 5 <[email protected]>
…ponents The stylesheet is MDB 4 (Bootstrap 4), with Bootstrap 5's JavaScript on top, so Bootstrap 5-only classes had no styling: the dialog's close button was btn-close (a small grey box), and visually-hidden, form-select and text-start did nothing. Use close with a times sign, sr-only, form-control and text-left, as the app's existing modals do. dropdown-menu-end stays, because Bootstrap 5's dropdown JavaScript reads it to place the menu; dropdown-menu-right is added for the CSS. Co-Authored-By: Claude Sonnet 5 <[email protected]>
'I planted [N] [crop](s) on [date] from [seed] in [sun].' Each blank is an underlined inline field: quantity, the crop search, the planted date, and planted-from and sun/shade selects. The (s) drops away when the quantity is 1. The blanks are labelled for screen readers (Quantity, Crop, Planted date, Planted from, Sun or shade). Notes and 'Mark as finished' stay below. The browser spec now fills in quantity, planted from and sun as well, and checks they are saved. Co-Authored-By: Claude Sonnet 5 <[email protected]>
The crop blank says 'type a crop name'. When a search finds nothing it says so and links to the new-crop form (in a new tab, so the dialog stays open), as the old planting form did. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Co-Authored-By: Claude Sonnet 5 <[email protected]>
The crop picker is now an accessible combobox. As you type, matches appear in a listbox: Up and Down move through them, Enter chooses the highlighted one (or the top match if none is highlighted), and Escape closes the list before a second Escape closes the dialog. Focus moves to 'Change' once a crop is chosen. Matches can still be clicked. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Remove 'Mark as finished' and the 'Tell us more about it' notes from the dialog, along with the never-shown rating field it carried in its state. Co-Authored-By: Claude Sonnet 5 <[email protected]>
The theme's list-group 'active' shade was barely darker than the other matches. The one Enter would choose now has a solid blue background, white bold text and a darker left edge. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Group the sentence into phrases (I planted..., on..., from..., in...) that stay together when it wraps, so a word is never stranded away from its blank. Hide the quantity spinner that clipped 'number', widen the date, and give the two selects a minimum width, with a grey 'optional' when they are still empty. Co-Authored-By: Claude Sonnet 5 <[email protected]>
After choosing a crop the input is replaced by the crop's name and a Change button. Focus went to that button, so pressing Enter again pressed it and cleared the choice. Focus now moves on to the date blank, so a second Enter submits the form as it would from any field. Co-Authored-By: Claude Sonnet 5 <[email protected]>
The dialog asks one big question, 'What did you plant?', with the same
crop search as before (arrow keys, Enter, and a way to request a crop that
isn't found). Choosing a crop shows it back ('You're planting lettuce in
Orchard, today.') with Change and Plant it buttons, and focus lands on Plant
it so Enter confirms. The planting gets today's date and no other details;
those can be added later, which keeps adding one quick.
Drops the sentence blanks, their styles, and the planted-from/sun option lists
the page passed down.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
The highlight for a newly added planting set an inline pale green background on the crop chip. Chips are brown with white text, so the name became white on light green. The highlight is now a CSS class that leaves the chip's colours alone: a soft green row with a left bar for annual plantings, and a green ring around the chip for perennials. The browser spec checks the chip carries no inline style. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Standard patterns so the dialog always says where you are and what it is doing: - a two-step indicator (Choose a crop, Confirm) with a tick on the finished step - a spinner in the search box while searching, plus a 'Searching...' status, a count of matches, and an info icon on the 'no crops match' note - the choice shown back as a Crop / Garden / When summary with a Change button - Plant it as a filled green primary button that stays green while focused (the theme greys a focused button) and shows a spinner while planting, with Cancel and Change disabled meanwhile - an error panel with a warning icon that says the choice is kept for retrying - the matches listed in the dialog rather than floating over its buttons - a tick on the 'Planted ...' notice on the cards - the shovel (spade-marker.svg, the map marker) in the dialog title Co-Authored-By: Claude Sonnet 5 <[email protected]>
app/assets/builds/react_islands.js is built by esbuild and not committed, so on a fresh checkout pages that load it (the gardens index) fail with 'The asset react_islands.js is not present in the asset pipeline'. The rspec CI job never built it: only the browser-feature jobs did, through assets:precompile. Add a yarn build step there, and have the specs build the bundle once if it is missing so a local checkout doesn't fail either. Co-Authored-By: Claude Sonnet 5 <[email protected]>
The planting quick-actions menu used t('view'), which has no translation. In a
view Rails hides that by showing a humanised 'View'; the card serializer uses
I18n.t, which showed 'Translation missing: en.view' in the menu. Add
buttons.view, use it in both places, and add a serializer spec that fails if
any card label is a missing translation.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
The helper builds its hash with symbol keys, but the spec read 'data["@context"]' with a string key. And 'caps posts and photos at 50' relied on create(:post, crops: [crop]), which creates no crop_posts rows here, and on crop.photo_associations.create!, which the Crop model's own association leaves without a photographable. Link posts with CropPost and photos with PhotoAssociation explicitly. Co-Authored-By: Claude Sonnet 5 <[email protected]>
The rspec job ran lib, services, models, controllers, views, routing and requests, but not spec/helpers or spec/serializers, so those specs (including the react_component helper and the card serializer) never ran there, and two broken crops_helper specs went unnoticed. Co-Authored-By: Claude Sonnet 5 <[email protected]>
The cards were cluttered and misaligned. Now: - the header has a smaller title with the owner under it, and the menu top right - a smaller picture, and 'Perennials' and 'Annuals' headings instead of a divider and a gap; 'Nothing planted here yet.' for an empty garden - each annual planting is a row of three aligned columns: the crop with when it was planted (which tells duplicates apart), then its badges and progress bar, then a labelled three-dots menu in place of a bare chevron - the note when there is nothing to predict from is one short muted line that no longer repeats the crop name - on a phone the picture and rows stack Also restores the just-planted highlight styles, which a stylesheet edit had dropped, and adds a spec that the highlight has a background, since the class existing was all that was checked. Co-Authored-By: Claude Sonnet 5 <[email protected]>
The theme gives every .btn a 5.5rem minimum width, so the three-dots button's hover background was a wide grey box. It is now a 2.25rem round button. Co-Authored-By: Claude Sonnet 5 <[email protected]>
CloCkWeRX
approved these changes
Sep 20, 2026
CloCkWeRX
added a commit
that referenced
this pull request
Sep 20, 2026
* Bump msgpack from 1.8.4 to 1.8.5 Bumps [msgpack](https://github.com/msgpack/msgpack-ruby) from 1.8.4 to 1.8.5. - [Changelog](https://github.com/msgpack/msgpack-ruby/blob/master/ChangeLog) - [Commits](msgpack/msgpack-ruby@v1.8.4...v1.8.5) --- updated-dependencies: - dependency-name: msgpack dependency-version: 1.8.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> * Use database distinct count for GardenType gardens Replaces in-memory `.uniq.count`, `.uniq.empty?`, and `.uniq.each` calls with database-level `.distinct` queries on GardenType gardens association. Co-authored-by: CloCkWeRX <[email protected]> * Add require: false to aws-sdk-s3 in Gemfile Prevents aws-sdk-s3, aws-sdk-core, and aws-sdk-kms from auto-loading on application boot across web and worker processes. The gem is required on-demand in config/sitemap.rb. Co-authored-by: CloCkWeRX <[email protected]> * Bump aws-sdk-s3 from 1.229.0 to 1.232.0 Bumps [aws-sdk-s3](https://github.com/aws/aws-sdk-ruby) from 1.229.0 to 1.232.0. - [Release notes](https://github.com/aws/aws-sdk-ruby/releases) - [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-s3/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-ruby/commits) --- updated-dependencies: - dependency-name: aws-sdk-s3 dependency-version: 1.232.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <[email protected]> * Remove Unicorn dependency and enable Puma clustered workers with preloading Co-authored-by: CloCkWeRX <[email protected]> * Remove Unicorn dependency and update Gemfile.lock, enable Puma clustered workers with preloading Co-authored-by: CloCkWeRX <[email protected]> * Merge pull request #4794 from Growstuff/improve-cache-memory-usage-9918781528129420138 Optimize caching memory usage in PhotoCapable and CropsHelper * Bump adm-zip from 0.6.0 to 0.6.1 (#4798) Bumps [adm-zip](https://github.com/cthackers/adm-zip) from 0.6.0 to 0.6.1. - [Release notes](https://github.com/cthackers/adm-zip/releases) - [Changelog](https://github.com/cthackers/adm-zip/blob/master/history.md) - [Commits](cthackers/adm-zip@v0.6.0...v0.6.1) --- updated-dependencies: - dependency-name: adm-zip dependency-version: 0.6.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Optimize CanCan abilities using declarative hash conditions (#4797) Refactor block-based CanCan ability definitions for ScientificName, AlternateName, Planting, Activity, and Harvest into declarative hash conditions to eliminate per-record SQL queries and N+1 evaluation overhead. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * Optimize places memory usage by paginating, plucking fields, and limiting nearest_to (#4792) Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: Brenda Wallace <[email protected]> * Fix races in the members list and places search feature specs (#4801) * Wait for the page reload in the members list spec The "members list" feature specs click "Show" and then read the list of members right away. "Show" submits a GET form, so the browser loads a whole new page. The spec could read the list from the old page. The old page was then replaced, and Selenium raised StaleElementReferenceError. This failed once in CI on the pull request for #4800: https://github.com/Growstuff/growstuff/actions/runs/35477162596 Both examples now wait for the new URL before they read the list. The URL contains the chosen sort order, so the wait only ends once the new page has loaded. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Wait for the place page in the places search spec The "User searches" specs fill in the search form, click "Search", and then read the page straight away. The search goes to /places/search, which redirects to /places/<place>. The spec could read the page while the browser was still moving to the new page. Selenium then raised "Node with given id does not belong to the document". It failed in CI on the first example in "with a valid place": https://github.com/Growstuff/growstuff/actions/runs/35481754102 The search_with helper now waits for the place page URL before it returns. A blank search stays on the same URL, so it does not wait. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Rename the places CI workflow from "Admin" to "Places" The workflow in ci-features-places.yml was named "CI Features - Admin". It looks like it was copied from the admin workflow and never renamed. It runs spec/features/places/. Two workflows then had the same name. Every commit showed two "CI Features - Admin" runs. A failing places spec appeared under the admin name, which sent people to the wrong specs: https://github.com/Growstuff/growstuff/actions/runs/35481754102 Branch protection requires a check called "rspec". That is the job name, not the workflow name, so the required check does not change. Co-Authored-By: Claude Sonnet 5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5 <[email protected]> * Stop Rack::Attack depending on memcached, and cut the memcached timeout (#4800) * Keep Rack::Attack counters in process memory Rack::Attack was storing its request counters in Rails.cache, which is memcached in production. On 2026-09-19, memcached calls were timing out in the production logs. When that happens, the counters stop working. In the same log window, one IP made 753 requests. 717 of them succeeded. The 15 per minute throttle and the 500 per day ban did not stop it. Rack::Attack now uses its own in-memory store, capped at 8 MB. Each Puma worker keeps its own counters, so the limits apply per worker. Refs #1640 Co-Authored-By: Claude Sonnet 5 <[email protected]> * Add BLOCKED_IPS list to Rack::Attack When one crawler overloads the site, we need a fast way to block it. Rack::Attack now blocks every IP listed in the BLOCKED_IPS environment variable. The list is comma separated. Setting the variable does not need a code change. It also keeps IP addresses out of the repository. Refs #1640 Co-Authored-By: Claude Sonnet 5 <[email protected]> * Lower the memcached socket timeout to 0.5 seconds The production cache store waited up to 1.5 seconds for each memcached call. Dalli's own default is 1 second. Dalli also retries after a failure, so one bad cache call can block a Puma thread for longer than the timeout. On 2026-09-19, the production logs showed memcached timeouts every few seconds. With only 5 threads per Puma worker, blocked threads make the site slow. Slow requests hold memory for longer. The timeout is now 0.5 seconds. A healthy memcached call takes a few milliseconds. A call that takes 0.5 seconds has failed in practice, so the request is better off skipping the cache. Refs #1640 Co-Authored-By: Claude Sonnet 5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5 <[email protected]> * Fix Dockerfile base image: add missing ruby: prefix (#4802) The Ruby 4.0.6 bump left the first line as `FROM 4.0.6-trixie`, which docker resolves as an image named "4.0.6-trixie" and fails to pull. Co-authored-by: Claude Sonnet 5 <[email protected]> * Optimize Like lookups in Likeable concern (#4799) - Update liked_by? to return false if member is nil and use likes.exists?(member_id: member.id) instead of allocating login name arrays. - Simplify liked_by_members_names to use members.pluck(:login_name). - Update app/views/photos/_likes.html.haml to use photo.liked_by?(current_member). - Add unit tests for liked_by? in spec/models/like_spec.rb. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * Optimize PaperTrail whodunnit query in CropsController#show (#4796) * Optimize PaperTrail whodunnit query in CropsController#show Replace @crop.versions.map(&:whodunnit) with @crop.versions.distinct.pluck(:whodunnit) to avoid instantiating all PaperTrail version objects and loading object/object_changes into memory. Co-authored-by: CloCkWeRX <[email protected]> * Optimize PaperTrail whodunnit query in CropsController#show Replace @crop.versions.map(&:whodunnit) with @crop.versions.distinct.pluck(:whodunnit) to avoid instantiating all PaperTrail version objects and loading object/object_changes into memory. Added unit test in spec/controllers/crops_controller_spec.rb. Co-authored-by: CloCkWeRX <[email protected]> * Optimize PaperTrail whodunnit query in CropsController#show Use @crop.versions.reorder(nil).distinct.pluck(:whodunnit) to avoid instantiating all PaperTrail version objects into memory and prevent PostgreSQL PG::InvalidColumnReference errors with SELECT DISTINCT. Co-authored-by: CloCkWeRX <[email protected]> * Fix crop route parameter in controller spec Update parameter key from id to slug in CropsController show spec to match param: :slug route definition. Co-authored-by: CloCkWeRX <[email protected]> --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * Add rake tasks to pull crops and a member's data from production (#4803) * Add rake tasks to pull crops and a member's data from production crops:pull_from_production copies approved crops, and members:pull_from_production copies one member's gardens, plantings and planting photos, from growstuff.org into a local database. Both refuse to run in a production environment. They are built to be gentle on a server that has been struggling: one request at a time with a pause between requests, exponential backoff (honouring Retry-After) on timeouts, 429s and 5xx, and a hard stop rather than endless retries. The client also stops if a server keeps sending the same page, and will not follow a next link to another host. Gardens and plantings come from the site's own JSON, which carries every column; the JSON:API only exposes a garden's name, and its related resource endpoints return the wrong records. Only the crops that a member's plantings use are fetched. Flickr photos are stored as records pointing at Flickr's image URLs, as on production, so nothing is downloaded from Flickr. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Add ACTIVE_ONLY option to the member import ACTIVE_ONLY=true leaves out the site's all=1 flag, so production lists only the member's active gardens and current (not finished, not failed) plantings. The server does the filtering, so it also means fewer requests, and no crops or photos are fetched for plantings that are skipped. Co-Authored-By: Claude Sonnet 5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5 <[email protected]> * Fix new harvest from planting not prefilling crop (#4786) When initializing a new harvest from a planting (via `planting_slug` or `harvest[planting_id]`), `HarvestsController#new` now properly looks up the planting, assigns `@harvest.planting` and `@harvest.crop` from the planting's crop, and sets `@planting` and `@crop` instance variables for view rendering. Regression tests added to `HarvestsController` spec and request spec. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * Optimize markdownify allocations with in-place gsub! and combined regex (#4805) * Optimize markdownify allocations with in-place gsub and combined regex Avoid chaining multiple full-string copies during markdown parsing. Duplicate the input string once in ApplicationHelper#markdownify and modify it in-place using gsub! in GrowstuffMarkdown#expand_crops! and #expand_members!. Combine MEMBER_REGEX and MEMBER_AT_REGEX with Regexp.union to parse member mentions in a single pass. Co-authored-by: CloCkWeRX <[email protected]> * Apply suggestion from @CloCkWeRX * Apply suggestion from @CloCkWeRX * Optimize markdownify allocations with in-place gsub and combined regex Avoid chaining multiple full-string copies during markdown parsing. Duplicate the input string once in ApplicationHelper#markdownify and modify it in-place using gsub! in GrowstuffMarkdown#expand_crops! and #expand_members!. Combine MEMBER_REGEX and MEMBER_AT_REGEX with Regexp.union to parse member mentions in a single pass. Co-authored-by: CloCkWeRX <[email protected]> --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * Optimize SearchCrops#search_data planters_ids and has_photos (#4806) Use `.distinct` when fetching `planters_ids` to avoid duplicate member IDs and replace `photos.size` with the `photo_associations_count` counter cache column for `has_photos`. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * Add pagination to crops#data_improvement (#4807) In `CropsController#data_improvement`, paginate `@crops` with `per_page: 50` so tabs like photos/descriptions do not render all crops at once. In `data_improvement.html.haml`, render `= will_paginate @crops`. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * Quick planting addition form (#4808) * Add rake tasks to pull crops and a member's data from production crops:pull_from_production copies approved crops, and members:pull_from_production copies one member's gardens, plantings and planting photos, from growstuff.org into a local database. Both refuse to run in a production environment. They are built to be gentle on a server that has been struggling: one request at a time with a pause between requests, exponential backoff (honouring Retry-After) on timeouts, 429s and 5xx, and a hard stop rather than endless retries. The client also stops if a server keeps sending the same page, and will not follow a next link to another host. Gardens and plantings come from the site's own JSON, which carries every column; the JSON:API only exposes a garden's name, and its related resource endpoints return the wrong records. Only the crops that a member's plantings use are fetched. Flickr photos are stored as records pointing at Flickr's image URLs, as on production, so nothing is downloaded from Flickr. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Add ACTIVE_ONLY option to the member import ACTIVE_ONLY=true leaves out the site's all=1 flag, so production lists only the member's active gardens and current (not finished, not failed) plantings. The server does the filtering, so it also means fewer requests, and no crops or photos are fetched for plantings that are skipped. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Bundle JavaScript with esbuild via jsbundling-rails Adds React and an esbuild build (yarn build, yarn build:watch) that writes app/assets/builds/react_islands.js, which Sprockets serves. jsbundling-rails runs the build as part of assets:precompile, which CI and the Dockerfile already run after yarn install. react_islands.jsx is the entry point: it mounts a React component into every element with data-react-component. No components are registered yet. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Add react_component view helper Renders an empty element carrying the component name and its props as JSON, for react_islands.jsx to mount a React component into. Props go through Rails' attribute escaping. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Show a React GardenSummary island on the gardens index The first React island, to prove the pipeline end to end: server-rendered props (owner and garden count) drive a small component with client-side state. Loaded only on this page, deferred, via the scripts content block. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Add GardenCardSerializer for the React garden cards Turns a page of gardens into plain hashes: the garden, its owner, its actions menu, and its active plantings split into perennials and annuals, with progress, badges and quick actions. It mirrors what gardens/_card, gardens/_actions and the planting partials render today, so the server still decides labels, links and permissions. Active plantings for the whole page are loaded in one query (with harvests and the crop's parents), instead of several queries per garden. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Render the garden cards on the index with React The gardens index now hands GardenCardSerializer's output to a GardenCards island instead of rendering gardens/_card per garden. The actions menu moves to the top right of the card header. Everything else on the card is the same: image, perennials, and each annual planting with badges, progress bar and its own quick actions menu. The server still chooses labels, links and permissions. The dropdowns keep the existing Bootstrap and jquery_ujs markup (data-bs-toggle, data-method, data-confirm), so mark inactive, delete and the confirmations work unchanged. Menu button ids are now unique per card, so the edit-from-index spec finds the menu by its Actions text instead of the old repeated id. gardens/_card stays for the activity page. This replaces the GardenSummary island, which only proved the pipeline. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Don't raise when validating a planting that has no garden owner_must_match_garden_owner called garden.owner unconditionally, so a planting with a missing garden raised NoMethodError (a 500) instead of failing the garden presence validation. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Create plantings as JSON for the React garden cards POST /plantings.json returns the garden's updated card (201), or the validation errors (422), so the cards can show a new planting without a page reload. It is for our own pages only: session cookie plus CSRF token, and no CORS headers, unlike the public /api/v1. Creating now also checks the garden is yours. Before, can :create Planting had no garden condition and create never authorized the garden, so a crafted request could plant into someone else's garden. This applies to the HTML form as well. A missing garden is still left to the presence validation. CanCan::AccessDenied now answers JSON requests with a 403 and an error, instead of redirecting them. The request spec logs in through a real cookie session to exercise CSRF protection: PlantingsController uses the null_session strategy, so a request without the token is treated as signed out (401), and the sign_in test helper would bypass that. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Don't ask which garden when planting from a garden Arriving at the planting form with a garden_id of one of your own gardens (from 'Plant something here') now shows 'Planting in <garden>' with a hidden garden field and a link to choose a different one, instead of the garden radio buttons. Any other arrival, including a garden_id that isn't yours, still asks as before. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Open 'Plant something here' as a dialog over the garden cards Clicking 'Plant something here' on a card opens a dialog to plant into that garden, without leaving the list or asking which garden. Saving posts to POST /plantings.json, swaps in the garden's updated card so the new planting shows straight away (briefly highlighted, with a confirmation), and closes the dialog. Errors show inside it and it stays open. A ctrl/cmd/shift-click still opens the normal form. The dialog is a React-controlled Bootstrap modal: focus moves in and is trapped, Escape and the backdrop close it, and focus returns afterwards. The crop picker searches /crops/search.json like the existing autosuggest, and the planted-from and sun choices come from Planting's constants via props. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Use the Bootstrap 4 class names the app's CSS defines in the card components The stylesheet is MDB 4 (Bootstrap 4), with Bootstrap 5's JavaScript on top, so Bootstrap 5-only classes had no styling: the dialog's close button was btn-close (a small grey box), and visually-hidden, form-select and text-start did nothing. Use close with a times sign, sr-only, form-control and text-left, as the app's existing modals do. dropdown-menu-end stays, because Bootstrap 5's dropdown JavaScript reads it to place the menu; dropdown-menu-right is added for the CSS. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Make the plant-something form read as a sentence 'I planted [N] [crop](s) on [date] from [seed] in [sun].' Each blank is an underlined inline field: quantity, the crop search, the planted date, and planted-from and sun/shade selects. The (s) drops away when the quantity is 1. The blanks are labelled for screen readers (Quantity, Crop, Planted date, Planted from, Sun or shade). Notes and 'Mark as finished' stay below. The browser spec now fills in quantity, planted from and sun as well, and checks they are saved. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Hint at typing a crop name, and offer to request one that isn't found The crop blank says 'type a crop name'. When a search finds nothing it says so and links to the new-crop form (in a new tab, so the dialog stays open), as the old planting form did. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Say 'number' in the quantity blank Co-Authored-By: Claude Sonnet 5 <[email protected]> * Choose a crop with the cursor keys The crop picker is now an accessible combobox. As you type, matches appear in a listbox: Up and Down move through them, Enter chooses the highlighted one (or the top match if none is highlighted), and Escape closes the list before a second Escape closes the dialog. Focus moves to 'Change' once a crop is chosen. Matches can still be clicked. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Trim the plant-something form to just the sentence Remove 'Mark as finished' and the 'Tell us more about it' notes from the dialog, along with the never-shown rating field it carried in its state. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Make the highlighted crop match obvious The theme's list-group 'active' shade was barely darker than the other matches. The one Enter would choose now has a solid blue background, white bold text and a darker left edge. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Tidy the plant-something sentence layout Group the sentence into phrases (I planted..., on..., from..., in...) that stay together when it wraps, so a word is never stranded away from its blank. Hide the quantity spinner that clipped 'number', widen the date, and give the two selects a minimum width, with a grey 'optional' when they are still empty. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Don't let a second Enter undo a chosen crop After choosing a crop the input is replaced by the crop's name and a Change button. Focus went to that button, so pressing Enter again pressed it and cleared the choice. Focus now moves on to the date blank, so a second Enter submits the form as it would from any field. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Plant with one search and a confirmation, instead of the sentence form The dialog asks one big question, 'What did you plant?', with the same crop search as before (arrow keys, Enter, and a way to request a crop that isn't found). Choosing a crop shows it back ('You're planting lettuce in Orchard, today.') with Change and Plant it buttons, and focus lands on Plant it so Enter confirms. The planting gets today's date and no other details; those can be added later, which keeps adding one quick. Drops the sentence blanks, their styles, and the planted-from/sun option lists the page passed down. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Fix unreadable text on a just-planted crop The highlight for a newly added planting set an inline pale green background on the crop chip. Chips are brown with white text, so the name became white on light green. The highlight is now a CSS class that leaves the chip's colours alone: a soft green row with a left bar for annual plantings, and a green ring around the chip for perennials. The browser spec checks the chip carries no inline style. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Tidy the plant dialog and show what is happening at each stage Standard patterns so the dialog always says where you are and what it is doing: - a two-step indicator (Choose a crop, Confirm) with a tick on the finished step - a spinner in the search box while searching, plus a 'Searching...' status, a count of matches, and an info icon on the 'no crops match' note - the choice shown back as a Crop / Garden / When summary with a Change button - Plant it as a filled green primary button that stays green while focused (the theme greys a focused button) and shows a spinner while planting, with Cancel and Change disabled meanwhile - an error panel with a warning icon that says the choice is kept for retrying - the matches listed in the dialog rather than floating over its buttons - a tick on the 'Planted ...' notice on the cards - the shovel (spade-marker.svg, the map marker) in the dialog title Co-Authored-By: Claude Sonnet 5 <[email protected]> * Build the React bundle in CI and for fresh checkouts app/assets/builds/react_islands.js is built by esbuild and not committed, so on a fresh checkout pages that load it (the gardens index) fail with 'The asset react_islands.js is not present in the asset pipeline'. The rspec CI job never built it: only the browser-feature jobs did, through assets:precompile. Add a yarn build step there, and have the specs build the bundle once if it is missing so a local checkout doesn't fail either. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Add the missing 'View' translation The planting quick-actions menu used t('view'), which has no translation. In a view Rails hides that by showing a humanised 'View'; the card serializer uses I18n.t, which showed 'Translation missing: en.view' in the menu. Add buttons.view, use it in both places, and add a serializer spec that fails if any card label is a missing translation. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Fix two crop_jsonld_data specs that could not pass The helper builds its hash with symbol keys, but the spec read 'data["@context"]' with a string key. And 'caps posts and photos at 50' relied on create(:post, crops: [crop]), which creates no crop_posts rows here, and on crop.photo_associations.create!, which the Crop model's own association leaves without a photographable. Link posts with CropPost and photos with PhotoAssociation explicitly. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Run the helper and serializer specs in CI The rspec job ran lib, services, models, controllers, views, routing and requests, but not spec/helpers or spec/serializers, so those specs (including the react_component helper and the card serializer) never ran there, and two broken crops_helper specs went unnoticed. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Tidy the garden cards The cards were cluttered and misaligned. Now: - the header has a smaller title with the owner under it, and the menu top right - a smaller picture, and 'Perennials' and 'Annuals' headings instead of a divider and a gap; 'Nothing planted here yet.' for an empty garden - each annual planting is a row of three aligned columns: the crop with when it was planted (which tells duplicates apart), then its badges and progress bar, then a labelled three-dots menu in place of a bare chevron - the note when there is nothing to predict from is one short muted line that no longer repeats the crop name - on a phone the picture and rows stack Also restores the just-planted highlight styles, which a stylesheet edit had dropped, and adds a spec that the highlight has a background, since the class existing was all that was checked. Co-Authored-By: Claude Sonnet 5 <[email protected]> * Make the planting menu button compact The theme gives every .btn a 5.5rem minimum width, so the three-dots button's hover background was a wide grey box. It is now a 2.25rem round button. Co-Authored-By: Claude Sonnet 5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5 <[email protected]> * Cap RSS feeds iterate unbounded associations to 50 records (#4809) - Cap @member.posts in app/views/members/show.rss.haml with .limit(50) - Cap @post.comments in app/views/posts/show.rss.haml with .limit(50) and eager load authors - Remove eager loading of all comments+authors on Post in PostsController#show - Add view specs to verify RSS feed limits Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * Optimize Wikidata rake task using find_each and eager loading (#4812) - Replace Crop.all.each with Crop.includes(:scientific_names).find_each - Avoid per-crop N+1 queries when looking up Wikidata IDs from scientific names - Add RSpec tests for wikidata:import_alternate_names task Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * Require specific Rails frameworks instead of rails/all (#4811) * Require specific Rails frameworks instead of rails/all Replaced `require 'rails/all'` in `config/application.rb` with individual framework railtie requirements (`active_model`, `active_job`, `active_record`, `action_controller`, `action_mailer`, `action_view`, `sprockets`), excluding unused frameworks such as ActionCable, ActionMailbox, ActionText, and ActiveStorage. Cleaned up unused `config.active_storage.service` settings in development and test environment files. Co-authored-by: CloCkWeRX <[email protected]> * Require specific Rails frameworks instead of rails/all Replaced `require 'rails/all'` in `config/application.rb` with individual framework railtie/engine requirements (`active_model`, `active_job`, `active_record`, `active_storage`, `action_controller`, `action_mailer`, `action_view`, `sprockets`), excluding unused frameworks such as ActionCable, ActionMailbox, and ActionText. Retained `active_storage/engine` required by Comfy CMS. Co-authored-by: CloCkWeRX <[email protected]> --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * Change production log_level from :debug to :info by default (#4810) Updated config/environments/production.rb to set config.log_level using ENV.fetch("RAILS_LOG_LEVEL", "info").to_sym. This prevents excessive SQL query logging in production by default while allowing configuration via the RAILS_LOG_LEVEL environment variable. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: Brenda Wallace <[email protected]> Co-authored-by: Claude Sonnet 5 <[email protected]>
Br3nda
added a commit
that referenced
this pull request
Sep 20, 2026
The cards from #4808 still used Bootstrap 4 names: .close with a times sign (now .btn-close), sr-only (now visually-hidden), badge-info (now text-bg-info), and dropdown-menu-right next to dropdown-menu-end (only -end is needed). Co-Authored-By: Claude Sonnet 5 <[email protected]>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Add a quicker way to add to your garden