Add weekly tracking view, properties tab, and four-tab nav #13

Merged
ldraney merged 2 commits from 10-weekly-tracking into main 2026-05-25 02:24:06 +00:00
Owner

Summary

  • Week view showing completion grid (Mon-Sun) for all active properties with summary count
  • Properties management tab with active/inactive toggle via Turbo Stream
  • Four-tab bottom nav: Today, Week, New, Properties
  • Root route changed to Today queue (primary use case)

Changes

  • app/controllers/weeks_controller.rb: new controller aggregating weekly completions
  • app/views/weeks/index.html.erb: week grid with day columns, checkmarks, prev/next nav, summary
  • app/controllers/properties_controller.rb: manage and toggle_active actions
  • app/views/properties/manage.html.erb: property list with search and active/inactive toggle
  • app/views/properties/toggle_active.turbo_stream.erb: no-reload toggle response
  • app/views/layouts/application.html.erb: four-tab nav (Today, Week, New, Properties)
  • config/routes.rb: weeks resource, manage/toggle_active routes, root to today
  • app/assets/stylesheets/application.css: week grid, manage list, toggle button, inactive dimming
  • db/schema.rb: version bump for active column
  • spec/requests/weeks_spec.rb: 8 request specs
  • spec/requests/properties_spec.rb: 6 request specs

Test Plan

  • Weeks index returns 200 with correct completion data
  • Week nav shows correct date ranges
  • Inactive properties excluded from week view
  • Property active toggle persists and returns turbo_stream
  • Manage page shows all properties (active and inactive)
  • All 32 specs pass

Review Checklist

  • No secrets committed
  • No unnecessary file changes
  • Commit messages are descriptive
  • Closes #10
  • Parent: #8
  • Depends on: #9 (merged as PR #12), #7 (merged as PR #11)
  • project-landscaping-assistant
## Summary - Week view showing completion grid (Mon-Sun) for all active properties with summary count - Properties management tab with active/inactive toggle via Turbo Stream - Four-tab bottom nav: Today, Week, New, Properties - Root route changed to Today queue (primary use case) ## Changes - `app/controllers/weeks_controller.rb`: new controller aggregating weekly completions - `app/views/weeks/index.html.erb`: week grid with day columns, checkmarks, prev/next nav, summary - `app/controllers/properties_controller.rb`: manage and toggle_active actions - `app/views/properties/manage.html.erb`: property list with search and active/inactive toggle - `app/views/properties/toggle_active.turbo_stream.erb`: no-reload toggle response - `app/views/layouts/application.html.erb`: four-tab nav (Today, Week, New, Properties) - `config/routes.rb`: weeks resource, manage/toggle_active routes, root to today - `app/assets/stylesheets/application.css`: week grid, manage list, toggle button, inactive dimming - `db/schema.rb`: version bump for active column - `spec/requests/weeks_spec.rb`: 8 request specs - `spec/requests/properties_spec.rb`: 6 request specs ## Test Plan - [x] Weeks index returns 200 with correct completion data - [x] Week nav shows correct date ranges - [x] Inactive properties excluded from week view - [x] Property active toggle persists and returns turbo_stream - [x] Manage page shows all properties (active and inactive) - [x] All 32 specs pass ## Review Checklist - [x] No secrets committed - [x] No unnecessary file changes - [x] Commit messages are descriptive ## Related Notes - Closes #10 - Parent: #8 - Depends on: #9 (merged as PR #12), #7 (merged as PR #11) - `project-landscaping-assistant`
Add weekly tracking view, properties tab, and four-tab nav
Some checks are pending
CI / scan_ruby (pull_request) Waiting to run
CI / scan_js (pull_request) Waiting to run
CI / lint (pull_request) Waiting to run
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
4514e8d5f0
Implements issue #10: weekly completion grid (Mon-Sun), properties
management with active/inactive toggle via Turbo Stream, and expands
bottom nav from 2 to 4 tabs (Today, Week, New, Properties).

- WeeksController aggregates work_queue_items completions per week
- Properties#manage shows all properties with toggle_active action
- Nav uses controller_name + action_name for correct active states
- Week grid reuses day-nav pattern for prev/next navigation
- schema.rb updated to include active column from migration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Owner

PR #13 Review

DOMAIN REVIEW

Tech stack: Ruby on Rails 8.1, Hotwire (Turbo Streams/Frames), Stimulus, vanilla CSS with design tokens.

WeeksController query correctness (app/controllers/weeks_controller.rb):

  • The aggregation query plucks [property_id, work_date], groups by property_id, then extracts dates. This is correct and efficient -- one query, no N+1.
  • Week boundary logic uses beginning_of_week(:monday) consistently and rescues Date::Error for invalid params. Solid.
  • Minor logic concern on @total_completed (line 15-16): The ternary checks if flatten.uniq.length > 0 then returns @completions.keys.count. This counts "properties that had at least one completion" but the uniq block with { |d| d } is redundant (equivalent to plain .uniq). The logic works but is confusingly written -- it would be clearer as @completions.keys.count alone since @completions only contains completed items anyway (the hash is empty if nothing was completed).

Turbo Stream toggle_active -- BROKEN after first toggle:

In manage.html.erb (line 11), each property is wrapped:

<%= turbo_frame_tag dom_id(property, :manage) do %>
  <li class="manage-item ...">...</li>
<% end %>

This renders as <turbo-frame id="manage_property_123"><li>...</li></turbo-frame>.

In toggle_active.turbo_stream.erb (line 1), the replacement targets that same ID:

<%= turbo_stream.replace dom_id(@property, :manage) do %>
  <li class="manage-item ...">...</li>
<% end %>

The replacement content is a bare <li> -- it does NOT re-wrap in a <turbo-frame> tag. After the first toggle, turbo_stream.replace removes the entire <turbo-frame id="manage_property_123"> element and inserts a bare <li>. On the second toggle click, Turbo cannot find an element with id="manage_property_123" because it no longer exists in the DOM. The second and all subsequent toggles will silently fail (no error, no UI update -- the Turbo Stream response just has nowhere to land).

Fix: Wrap the replacement <li> in a matching turbo_frame_tag in the turbo stream template, or switch to using a plain id attribute on the <li> itself (removing the turbo-frame wrapper in both files and relying on a dom_id-based id attribute on the <li> directly).

Four-tab nav active state logic (app/views/layouts/application.html.erb):

  • "Today" uses controller_name == 'work_queue_items' -- correct.
  • "Week" uses controller_name == 'weeks' -- correct.
  • "New" uses controller_name == 'properties' && action_name == 'index' -- correct.
  • "Properties" uses controller_name == 'properties' && action_name == 'manage' -- correct.
  • Edge case: The show and update actions on PropertiesController will have no active tab highlighted. Acceptable for now since show is accessed contextually.

CSS design token usage: All colors, spacing, radii, and borders use var(--color-*), var(--spacing-*), and var(--radius) tokens. No hardcoded color values. One border-radius: 1rem on .btn-toggle is a reasonable pill-shape choice outside the token system. Clean.

Route structure: Well-organized. manage as collection route, toggle_active as member route with PATCH -- RESTful and correct.

BLOCKERS

  1. Turbo Stream replace target destroyed after first toggle (app/views/properties/toggle_active.turbo_stream.erb): The replacement content lacks the <turbo-frame> wrapper present in the initial render. After one toggle, subsequent toggles silently fail -- the UI becomes unresponsive for that property until page reload. This is a functional bug in core feature behavior.

NITS

  1. WeeksController @total_completed logic is convoluted (line 15-16): The ternary with .uniq { |d| d } is equivalent to .uniq and the length check is unnecessary since an empty hash would return 0 from .keys.count anyway. Simplify to @total_completed = @completions.keys.count.

  2. DRY opportunity in manage templates: The <li> markup in manage.html.erb and toggle_active.turbo_stream.erb is duplicated. Consider extracting to a partial (_manage_item.html.erb) rendered in both places. Not blocking since these are small and unlikely to diverge for now, but worth noting.

  3. Missing aria-label on toggle buttons: The "Active"/"Inactive" text is inside the button so screen readers get it, but adding aria-pressed would improve accessibility semantics for toggle buttons.

  4. @properties in WeeksController triggers N+1 for client_name: Currently Property.active.order(:address_line) -- since client_name is on the same table this is fine, but if services were ever displayed in the week view, an .includes(:services) would be needed. Not an issue today.

SOP COMPLIANCE

  • Branch named after issue: 10-weekly-tracking matches issue #10
  • PR body follows template: Summary, Changes, Test Plan, Review Checklist, Related all present
  • Related references plan slug: project-landscaping-assistant referenced
  • No secrets committed
  • No unnecessary file changes (11 files, all on-topic)
  • Commit messages are descriptive
  • Test coverage present: 8 weeks specs + 6 properties specs covering happy paths, edge cases, format responses

PROCESS OBSERVATIONS

  • Good test coverage for a feature PR -- both controllers have request specs covering the primary flows.
  • The Turbo Stream bug is the kind of issue that passes in tests (the spec only checks first toggle response format, not idempotent re-toggling) but fails in production usage. Consider adding an integration/system test that toggles twice in sequence to catch frame-target survival issues.
  • Clean separation of concerns: one controller per domain concept, CSS organized by component with clear section headers.

VERDICT: NOT APPROVED

One blocker: the Turbo Stream replace target is destroyed after first toggle, making the toggle_active feature break on repeated use. Fix the turbo stream template to preserve the frame wrapper (or switch to ID-based targeting without frames), and this is ready to merge.

## PR #13 Review ### DOMAIN REVIEW **Tech stack**: Ruby on Rails 8.1, Hotwire (Turbo Streams/Frames), Stimulus, vanilla CSS with design tokens. **WeeksController query correctness** (`app/controllers/weeks_controller.rb`): - The aggregation query plucks `[property_id, work_date]`, groups by property_id, then extracts dates. This is correct and efficient -- one query, no N+1. - Week boundary logic uses `beginning_of_week(:monday)` consistently and rescues `Date::Error` for invalid params. Solid. - Minor logic concern on `@total_completed` (line 15-16): The ternary checks if `flatten.uniq.length > 0` then returns `@completions.keys.count`. This counts "properties that had at least one completion" but the `uniq` block with `{ |d| d }` is redundant (equivalent to plain `.uniq`). The logic works but is confusingly written -- it would be clearer as `@completions.keys.count` alone since `@completions` only contains completed items anyway (the hash is empty if nothing was completed). **Turbo Stream toggle_active -- BROKEN after first toggle**: In `manage.html.erb` (line 11), each property is wrapped: ```erb <%= turbo_frame_tag dom_id(property, :manage) do %> <li class="manage-item ...">...</li> <% end %> ``` This renders as `<turbo-frame id="manage_property_123"><li>...</li></turbo-frame>`. In `toggle_active.turbo_stream.erb` (line 1), the replacement targets that same ID: ```erb <%= turbo_stream.replace dom_id(@property, :manage) do %> <li class="manage-item ...">...</li> <% end %> ``` The replacement content is a bare `<li>` -- it does NOT re-wrap in a `<turbo-frame>` tag. After the first toggle, `turbo_stream.replace` removes the entire `<turbo-frame id="manage_property_123">` element and inserts a bare `<li>`. On the second toggle click, Turbo cannot find an element with `id="manage_property_123"` because it no longer exists in the DOM. **The second and all subsequent toggles will silently fail** (no error, no UI update -- the Turbo Stream response just has nowhere to land). **Fix**: Wrap the replacement `<li>` in a matching `turbo_frame_tag` in the turbo stream template, or switch to using a plain `id` attribute on the `<li>` itself (removing the turbo-frame wrapper in both files and relying on a `dom_id`-based `id` attribute on the `<li>` directly). **Four-tab nav active state logic** (`app/views/layouts/application.html.erb`): - "Today" uses `controller_name == 'work_queue_items'` -- correct. - "Week" uses `controller_name == 'weeks'` -- correct. - "New" uses `controller_name == 'properties' && action_name == 'index'` -- correct. - "Properties" uses `controller_name == 'properties' && action_name == 'manage'` -- correct. - Edge case: The `show` and `update` actions on PropertiesController will have no active tab highlighted. Acceptable for now since show is accessed contextually. **CSS design token usage**: All colors, spacing, radii, and borders use `var(--color-*)`, `var(--spacing-*)`, and `var(--radius)` tokens. No hardcoded color values. One `border-radius: 1rem` on `.btn-toggle` is a reasonable pill-shape choice outside the token system. Clean. **Route structure**: Well-organized. `manage` as collection route, `toggle_active` as member route with PATCH -- RESTful and correct. ### BLOCKERS 1. **Turbo Stream replace target destroyed after first toggle** (`app/views/properties/toggle_active.turbo_stream.erb`): The replacement content lacks the `<turbo-frame>` wrapper present in the initial render. After one toggle, subsequent toggles silently fail -- the UI becomes unresponsive for that property until page reload. This is a functional bug in core feature behavior. ### NITS 1. **WeeksController `@total_completed` logic is convoluted** (line 15-16): The ternary with `.uniq { |d| d }` is equivalent to `.uniq` and the length check is unnecessary since an empty hash would return 0 from `.keys.count` anyway. Simplify to `@total_completed = @completions.keys.count`. 2. **DRY opportunity in manage templates**: The `<li>` markup in `manage.html.erb` and `toggle_active.turbo_stream.erb` is duplicated. Consider extracting to a partial (`_manage_item.html.erb`) rendered in both places. Not blocking since these are small and unlikely to diverge for now, but worth noting. 3. **Missing `aria-label` on toggle buttons**: The "Active"/"Inactive" text is inside the button so screen readers get it, but adding `aria-pressed` would improve accessibility semantics for toggle buttons. 4. **`@properties` in WeeksController triggers N+1 for `client_name`**: Currently `Property.active.order(:address_line)` -- since `client_name` is on the same table this is fine, but if services were ever displayed in the week view, an `.includes(:services)` would be needed. Not an issue today. ### SOP COMPLIANCE - [x] Branch named after issue: `10-weekly-tracking` matches issue #10 - [x] PR body follows template: Summary, Changes, Test Plan, Review Checklist, Related all present - [x] Related references plan slug: `project-landscaping-assistant` referenced - [x] No secrets committed - [x] No unnecessary file changes (11 files, all on-topic) - [x] Commit messages are descriptive - [x] Test coverage present: 8 weeks specs + 6 properties specs covering happy paths, edge cases, format responses ### PROCESS OBSERVATIONS - Good test coverage for a feature PR -- both controllers have request specs covering the primary flows. - The Turbo Stream bug is the kind of issue that passes in tests (the spec only checks first toggle response format, not idempotent re-toggling) but fails in production usage. Consider adding an integration/system test that toggles twice in sequence to catch frame-target survival issues. - Clean separation of concerns: one controller per domain concept, CSS organized by component with clear section headers. ### VERDICT: NOT APPROVED One blocker: the Turbo Stream replace target is destroyed after first toggle, making the toggle_active feature break on repeated use. Fix the turbo stream template to preserve the frame wrapper (or switch to ID-based targeting without frames), and this is ready to merge.
Fix Turbo Stream toggle target and simplify week completion count
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
CI / scan_ruby (pull_request) Has been cancelled
CI / scan_js (pull_request) Has been cancelled
CI / lint (pull_request) Has been cancelled
be823fc16d
Replace turbo_frame_tag with plain id= on <li> so turbo_stream.replace
can find the target after the first toggle. Simplify @total_completed
from needless ternary to direct key count.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Owner

PR #13 Review

DOMAIN REVIEW

Tech Stack: Ruby on Rails 8.1, Hotwire (Turbo Streams + Stimulus), PostgreSQL, RSpec

Turbo Stream Blocker Fix (Verified)

The previous review's blocker is resolved. Both manage.html.erb and toggle_active.turbo_stream.erb now use the same DOM structure:

<li id="<%= dom_id(property, :manage) %>" class="manage-item ...">

No turbo_frame_tag wrapper in either file. turbo_stream.replace targets the id attribute directly, so subsequent toggles will always find the element. Correct fix.

WeeksController

  • parse_week_start properly rescues Date::Error for invalid params -- good defensive coding.
  • The query in index uses .pluck(:property_id, :work_date) which avoids loading full AR objects -- efficient.
  • @total_completed = @completions.keys.count is clean and correct (counts distinct properties with at least one completion that week).

PropertiesController#toggle_active

  • Uses update! which will raise on validation failure -- appropriate for a simple boolean toggle.
  • Responds to both turbo_stream and html formats -- graceful degradation.

Routes

  • RESTful structure with collection/member routes properly nested.
  • Root changed to work_queue_items#index (Today queue) -- makes sense as primary use case.

CSS

  • Uses design tokens (--color-*, --spacing-*, --radius) consistently.
  • No magic numbers for colors or spacing.
  • Grid layout for week view is responsive-friendly with 2rem day columns.

Filter Controller (Stimulus)

  • Existing controller properly targets property elements. The data-filter-target="property" in both manage and turbo_stream templates ensures search continues working after toggle.

BLOCKERS

None. The previous blocker (Turbo Stream replacing a turbo_frame_tag-wrapped element with a bare <li>) is resolved.

NITS

  1. DRY opportunity in templates: The <li> markup in manage.html.erb and toggle_active.turbo_stream.erb is identical (17 lines duplicated). Consider extracting to a partial (_manage_item.html.erb). Not a blocker since this is view markup, not auth/security logic, but reduces maintenance surface.

  2. N+1 potential in WeeksController: Property.active.order(:address_line) does not .includes(:services) -- not needed here since the week view only displays address_line and client_name. Fine as-is but worth noting if the view template ever adds service info.

  3. total_completed edge case: @completions.keys.count counts properties that had ANY completion in the week, not total completions. The view shows "X/Y completed" where Y is total active properties. The semantics are "properties serviced at least once this week" vs "total active" -- this is reasonable but could be confusing if a property is serviced multiple days. A comment clarifying intent would help.

  4. Missing aria-label on toggle button: The btn-toggle button says "Active" or "Inactive" as text content, which is fine for screen readers. However, the button_to generates a form, and the button lacks context about which property it toggles. Consider adding aria-label="Toggle active status for #{property.address_line}".

SOP COMPLIANCE

  • Branch named after issue: 10-weekly-tracking matches issue #10
  • PR body follows template: Summary, Changes, Test Plan, Related all present
  • Related references plan slug: project-landscaping-assistant referenced
  • No secrets committed
  • No unnecessary file changes (11 files, all relevant)
  • Commit messages are descriptive
  • Tests exist: 8 weeks specs + 4 properties specs = 12 new request specs covering happy paths, edge cases, and format negotiation

PROCESS OBSERVATIONS

  • Good dependency management: PR correctly documents that #12 and #11 are merged prerequisites.
  • Test coverage is solid: invalid params, format negotiation, active/inactive toggling in both directions, and exclusion of inactive properties from week view are all tested.
  • The 32 total specs passing (mentioned in PR body) indicates no regressions from the new code.
  • Change failure risk is low -- this is additive (new controller, new views, new routes) with minimal modification to existing code.

VERDICT: APPROVED

## PR #13 Review ### DOMAIN REVIEW **Tech Stack**: Ruby on Rails 8.1, Hotwire (Turbo Streams + Stimulus), PostgreSQL, RSpec **Turbo Stream Blocker Fix (Verified)** The previous review's blocker is resolved. Both `manage.html.erb` and `toggle_active.turbo_stream.erb` now use the same DOM structure: ```erb <li id="<%= dom_id(property, :manage) %>" class="manage-item ..."> ``` No `turbo_frame_tag` wrapper in either file. `turbo_stream.replace` targets the `id` attribute directly, so subsequent toggles will always find the element. Correct fix. **WeeksController** - `parse_week_start` properly rescues `Date::Error` for invalid params -- good defensive coding. - The query in `index` uses `.pluck(:property_id, :work_date)` which avoids loading full AR objects -- efficient. - `@total_completed = @completions.keys.count` is clean and correct (counts distinct properties with at least one completion that week). **PropertiesController#toggle_active** - Uses `update!` which will raise on validation failure -- appropriate for a simple boolean toggle. - Responds to both `turbo_stream` and `html` formats -- graceful degradation. **Routes** - RESTful structure with collection/member routes properly nested. - Root changed to `work_queue_items#index` (Today queue) -- makes sense as primary use case. **CSS** - Uses design tokens (`--color-*`, `--spacing-*`, `--radius`) consistently. - No magic numbers for colors or spacing. - Grid layout for week view is responsive-friendly with `2rem` day columns. **Filter Controller (Stimulus)** - Existing controller properly targets `property` elements. The `data-filter-target="property"` in both manage and turbo_stream templates ensures search continues working after toggle. ### BLOCKERS None. The previous blocker (Turbo Stream replacing a `turbo_frame_tag`-wrapped element with a bare `<li>`) is resolved. ### NITS 1. **DRY opportunity in templates**: The `<li>` markup in `manage.html.erb` and `toggle_active.turbo_stream.erb` is identical (17 lines duplicated). Consider extracting to a partial (`_manage_item.html.erb`). Not a blocker since this is view markup, not auth/security logic, but reduces maintenance surface. 2. **N+1 potential in WeeksController**: `Property.active.order(:address_line)` does not `.includes(:services)` -- not needed here since the week view only displays `address_line` and `client_name`. Fine as-is but worth noting if the view template ever adds service info. 3. **`total_completed` edge case**: `@completions.keys.count` counts properties that had ANY completion in the week, not total completions. The view shows "X/Y completed" where Y is total active properties. The semantics are "properties serviced at least once this week" vs "total active" -- this is reasonable but could be confusing if a property is serviced multiple days. A comment clarifying intent would help. 4. **Missing `aria-label` on toggle button**: The `btn-toggle` button says "Active" or "Inactive" as text content, which is fine for screen readers. However, the `button_to` generates a form, and the button lacks context about which property it toggles. Consider adding `aria-label="Toggle active status for #{property.address_line}"`. ### SOP COMPLIANCE - [x] Branch named after issue: `10-weekly-tracking` matches issue #10 - [x] PR body follows template: Summary, Changes, Test Plan, Related all present - [x] Related references plan slug: `project-landscaping-assistant` referenced - [x] No secrets committed - [x] No unnecessary file changes (11 files, all relevant) - [x] Commit messages are descriptive - [x] Tests exist: 8 weeks specs + 4 properties specs = 12 new request specs covering happy paths, edge cases, and format negotiation ### PROCESS OBSERVATIONS - Good dependency management: PR correctly documents that #12 and #11 are merged prerequisites. - Test coverage is solid: invalid params, format negotiation, active/inactive toggling in both directions, and exclusion of inactive properties from week view are all tested. - The 32 total specs passing (mentioned in PR body) indicates no regressions from the new code. - Change failure risk is low -- this is additive (new controller, new views, new routes) with minimal modification to existing code. ### VERDICT: APPROVED
ldraney deleted branch 10-weekly-tracking 2026-05-25 02:24:06 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ldraney/landscaping-assistant!13
No description provided.