Add crew member list and profile views for admin oversight #146

Merged
ldraney merged 2 commits from 117-crew-tab into main 2026-06-07 04:51:26 +00:00
Owner

Summary

  • Replace Crew tab placeholder (PR #136) with real crew member list and profile views
  • Admins and super_admins can see active team members with role badges and click through to individual profiles
  • CrewMember model backed by new migration with idempotent crew:sync rake task for seeding

Changes

  • db/migrate/20260607030548_create_crew_members.rb: create_crew_members table (name, keycloak_username, role, active boolean) with unique index on keycloak_username
  • app/models/crew_member.rb: CrewMember model with ROLES constant, validations, active/by_name scopes
  • app/controllers/crew_controller.rb: replaced placeholder with real index (active members) and show (member profile) actions
  • app/views/crew/index.html.erb: member list with name, role badge, active status, linked to show
  • app/views/crew/show.html.erb: member profile with name, role, status, keycloak username, created date
  • config/routes.rb: added :show to crew resources
  • app/assets/stylesheets/application.css: crew list, role badges (color-coded per role), status indicators, profile layout
  • lib/tasks/crew.rake: idempotent crew:sync task seeding 5 test users (follows feature_flags.rake pattern)
  • spec/requests/crew_spec.rb: index + show specs with role-based access coverage
  • spec/requests/role_access_spec.rb: added crew show route tests for all roles
  • spec/models/crew_member_spec.rb: validations, scopes, defaults

Test Plan

  • Tests pass locally (bundle exec rspec via docker-compose)
  • /crew shows active members with role badges (admin/super_admin only)
  • Clicking a member navigates to /crew/:id profile page
  • Non-admin roles (member, lead, client) get redirected from both index and show
  • rake crew:sync seeds test data idempotently
  • No regressions in existing role access specs

Review Checklist

  • Passed automated review-fix loop
  • No secrets committed
  • No unnecessary file changes
  • Commit messages are descriptive
  • Feature flag needed? No -- crew tab is already role-gated to admin+ (PR #136), this is an internal model change with no new external integration
  • Closes #117
  • ldraney/landscaping-assistant #117 -- Crew tab admin oversight
  • Parent: #107 (Auth + Roles + Audit Trail)
  • PR #136 created the Crew tab skeleton this builds on
## Summary - Replace Crew tab placeholder (PR #136) with real crew member list and profile views - Admins and super_admins can see active team members with role badges and click through to individual profiles - CrewMember model backed by new migration with idempotent crew:sync rake task for seeding ## Changes - `db/migrate/20260607030548_create_crew_members.rb`: create_crew_members table (name, keycloak_username, role, active boolean) with unique index on keycloak_username - `app/models/crew_member.rb`: CrewMember model with ROLES constant, validations, active/by_name scopes - `app/controllers/crew_controller.rb`: replaced placeholder with real index (active members) and show (member profile) actions - `app/views/crew/index.html.erb`: member list with name, role badge, active status, linked to show - `app/views/crew/show.html.erb`: member profile with name, role, status, keycloak username, created date - `config/routes.rb`: added :show to crew resources - `app/assets/stylesheets/application.css`: crew list, role badges (color-coded per role), status indicators, profile layout - `lib/tasks/crew.rake`: idempotent crew:sync task seeding 5 test users (follows feature_flags.rake pattern) - `spec/requests/crew_spec.rb`: index + show specs with role-based access coverage - `spec/requests/role_access_spec.rb`: added crew show route tests for all roles - `spec/models/crew_member_spec.rb`: validations, scopes, defaults ## Test Plan - [ ] Tests pass locally (`bundle exec rspec` via docker-compose) - [ ] `/crew` shows active members with role badges (admin/super_admin only) - [ ] Clicking a member navigates to `/crew/:id` profile page - [ ] Non-admin roles (member, lead, client) get redirected from both index and show - [ ] `rake crew:sync` seeds test data idempotently - [ ] No regressions in existing role access specs ## Review Checklist - [ ] Passed automated review-fix loop - [ ] No secrets committed - [ ] No unnecessary file changes - [ ] Commit messages are descriptive - [ ] Feature flag needed? No -- crew tab is already role-gated to admin+ (PR #136), this is an internal model change with no new external integration ## Related Notes - Closes #117 - `ldraney/landscaping-assistant #117` -- Crew tab admin oversight - Parent: #107 (Auth + Roles + Audit Trail) - PR #136 created the Crew tab skeleton this builds on
Add crew member list and profile views for admin oversight (#117)
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
c7c010b5ba
Replace the Crew tab placeholder with a real member list backed by the
CrewMember model. Admins and super_admins can view active crew members
on the index page and click through to individual profiles showing name,
role, active status, and Keycloak username.

- Migration: create_crew_members table (name, keycloak_username, role, active)
- CrewMember model with validations and scopes
- CrewController: index (active members) and show (member profile)
- crew:sync rake task (idempotent, seeds 5 test users)
- Request specs for crew index + show with role-based access tests
- Model specs for CrewMember validations and scopes
- Updated role_access_spec with crew show route coverage
- CSS for crew list, role badges, status indicators, profile layout

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

QA Review -- PR #146

Checklist

  • Migration: create_crew_members with null constraints, unique index on keycloak_username, default active: true
  • Model: ROLES constant, presence/uniqueness/inclusion validations, active and by_name scopes
  • Controller: require_role :admin, :super_admin inherited, index queries CrewMember.active.by_name, show uses find
  • Index view: iterates active members with role badges and status indicators, empty state with rake hint
  • Show view: back link, <dl> profile layout, role badge, status, keycloak username, created date
  • Routes: :show added to crew resources
  • CSS: design tokens, component comments, no !important, no #id selectors -- follows ~/ror-css-guide
  • Rake task: idempotent find_or_create_by! matching feature_flags.rake pattern, 5 users covering all roles
  • Model specs: validations, scopes, defaults
  • Request specs: index + show with all 5 roles (admin, super_admin, member, lead, client)
  • Role access spec: show route added for all role groups + unauthenticated + dev mode
  • No WorkQueueItem modifications (per issue boundary)
  • No feature flags added (already role-gated)
  • No nav layout changes
  • No secrets committed
  • Closes #117 present in PR body

Observations (non-blocking)

  1. No RecordNotFound rescue in show -- Rails returns a 404 by default for missing records in production, which is acceptable for admin-only pages. No action needed.
  2. role-badge-super_admin CSS class uses an underscore in the class name (from member.role). This works but is unconventional for CSS. Non-blocking since it's generated from the model constant and consistent throughout.
  3. Index only shows active members but show allows viewing inactive members (via direct URL). This matches the issue spec -- admins can see inactive profiles if they have the ID.

VERDICT: PASS

Clean implementation matching all acceptance criteria from #117. No blocking issues found.

## QA Review -- PR #146 ### Checklist - [x] Migration: `create_crew_members` with null constraints, unique index on `keycloak_username`, default `active: true` - [x] Model: ROLES constant, presence/uniqueness/inclusion validations, `active` and `by_name` scopes - [x] Controller: `require_role :admin, :super_admin` inherited, index queries `CrewMember.active.by_name`, show uses `find` - [x] Index view: iterates active members with role badges and status indicators, empty state with rake hint - [x] Show view: back link, `<dl>` profile layout, role badge, status, keycloak username, created date - [x] Routes: `:show` added to crew resources - [x] CSS: design tokens, component comments, no `!important`, no `#id` selectors -- follows `~/ror-css-guide` - [x] Rake task: idempotent `find_or_create_by!` matching `feature_flags.rake` pattern, 5 users covering all roles - [x] Model specs: validations, scopes, defaults - [x] Request specs: index + show with all 5 roles (admin, super_admin, member, lead, client) - [x] Role access spec: show route added for all role groups + unauthenticated + dev mode - [x] No WorkQueueItem modifications (per issue boundary) - [x] No feature flags added (already role-gated) - [x] No nav layout changes - [x] No secrets committed - [x] `Closes #117` present in PR body ### Observations (non-blocking) 1. **No `RecordNotFound` rescue in `show`** -- Rails returns a 404 by default for missing records in production, which is acceptable for admin-only pages. No action needed. 2. **`role-badge-super_admin` CSS class** uses an underscore in the class name (from `member.role`). This works but is unconventional for CSS. Non-blocking since it's generated from the model constant and consistent throughout. 3. **Index only shows active members** but show allows viewing inactive members (via direct URL). This matches the issue spec -- admins can see inactive profiles if they have the ID. ### VERDICT: PASS Clean implementation matching all acceptance criteria from #117. No blocking issues found.
ldraney force-pushed 117-crew-tab from c7c010b5ba
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
to 7f3e66d8a4
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
2026-06-07 03:18:30 +00:00
Compare
Author
Owner

PR #146 Review

DOMAIN REVIEW

Stack: Ruby on Rails 8.0, RSpec request specs, vanilla CSS with design tokens, Rake tasks.

Migration (db/migrate/20260607030548_create_crew_members.rb):

  • Clean schema: name, keycloak_username, role, active with null: false constraints and default: true on active.
  • Unique index on keycloak_username -- correct, matches model uniqueness validation.
  • Note: the db/schema.rb in the diff does NOT include the new crew_members table. This is expected if the PR branch was not rebased after running the migration, but it means the schema.rb update is missing from the diff. The migration file itself is correct, but schema.rb must be updated before merge (run rails db:migrate and commit the result). This is a blocker -- Rails uses schema.rb for db:schema:load in CI and fresh setups.

Model (app/models/crew_member.rb):

  • ROLES constant matches the five-role system: client, member, lead, admin, super_admin. Correct.
  • Validates presence of all three required fields plus role inclusion. Correct.
  • Validates uniqueness of keycloak_username. Correct and backed by DB index.
  • active and by_name scopes are clean and appropriate.
  • No mass assignment concern: the model has no has_many or accepts_nested_attributes_for, and the controller never calls create/update from params.

Controller (app/controllers/crew_controller.rb):

  • require_role :admin, :super_admin at class level -- gates both index and show. Correct.
  • index: uses CrewMember.active.by_name -- correct, only shows active members.
  • show: uses CrewMember.find(params[:id]) -- this raises ActiveRecord::RecordNotFound for missing IDs, which Rails maps to 404 by default. Acceptable. Note: this allows admins to view inactive members via direct URL (by ID), which is tested and intentional per the spec it "can view inactive members".
  • No user input flows to create/update, so no mass assignment or injection risk.

Views:

  • index.html.erb: Iterates @crew_members, shows name, role badge, and active status. Empty state handled with @crew_members.any? guard. Uses link_to crew_path(member) for navigation. All values are output via <%= %> (ERB auto-escapes by default). No XSS risk.
  • show.html.erb: Displays member profile as a <dl> grid. Back link uses crew_index_path. Shows role, status, keycloak username, and created date. Clean. Uses strftime for date formatting -- acceptable.
  • Both views correctly use class="role-badge role-badge-<%= member.role %>" pattern with design-token-based CSS. No inline styles.

Routes (config/routes.rb):

  • Changed from only: [:index] to only: [:index, :show]. Correct and minimal.

CSS (app/assets/stylesheets/application.css):

  • Removes .crew-placeholder (no longer used). Good cleanup.
  • Updates .crew-item (removes old padding/color/font-size, adds border-bottom only). Clean.
  • New classes: .crew-item-link, .crew-item-info, .crew-item-meta, .crew-item-status, .crew-profile, .role-badge, .role-badge-{role}, .status-indicator, .status-indicator-{active|inactive}. All use design tokens. No magic numbers. No !important. No #id selectors. No Tailwind.
  • Component comment header updated to reference both views. Good.

CSS conflict with PR #148 (IMPORTANT):
PR #148 removed a duplicate .role-badge from the Person CSS section. On current main, .role-badge is referenced in person/index.html.erb (line 19) but has NO CSS definition -- it is unstyled. This PR introduces a .role-badge definition in the Crew CSS section. After rebasing onto current main, this will be the ONLY .role-badge definition, and it will correctly style both the Person view and the Crew views. This is actually a fix for the post-PR-#148 regression. No conflict -- this is additive and welcome.

Rake task (lib/tasks/crew.rake):

  • Uses find_or_create_by!(keycloak_username:) with a block -- idempotent for creates. Correct pattern, matches feature_flags.rake.
  • Five test users with correct Keycloak usernames: lucas-super-admin, lucas-admin, lucas-lead, lucas-crew, lucas-client. Matches the requirement.
  • Note: find_or_create_by! will NOT update name or role on subsequent runs if the record already exists. This is consistent with feature_flags.rake behavior but worth documenting.

Specs:

  • spec/models/crew_member_spec.rb: Covers all validations (presence of name, keycloak_username, role; uniqueness of keycloak_username; role inclusion with invalid value AND all valid values), both scopes (active, by_name), and defaults (active: true). Thorough.
  • spec/requests/crew_spec.rb: Covers index and show for dev mode (no Keycloak) and Keycloak mode. Tests admin access, super_admin access, member denial, lead denial, client denial, active/inactive filtering, and member profile display. Comprehensive.
  • spec/requests/role_access_spec.rb: Adds /crew/:id tests for all role categories (unauthenticated, dev mode, member, lead, admin, super_admin, client). Matches the existing pattern for /crew index. Thorough.

BLOCKERS

  1. Missing db/schema.rb update. The migration file is present but the diff does not include a schema.rb change. The current schema.rb on main (version 2026_06_07_011440) does not contain crew_members. After running the migration, the schema.rb must be committed with the new table definition. Without this, rails db:schema:load (used in CI and fresh deploys) will not create the crew_members table. Run rails db:migrate and commit the resulting db/schema.rb.

NITS

  1. border-radius: 1rem on badges -- The .role-badge and .status-indicator both use border-radius: 1rem as a literal value rather than a design token. The existing --radius: 8px token is for standard corners; a pill radius is a different concept. Consider adding a --radius-pill: 1rem token for consistency, or accept the literal value as an intentional one-off for pill shapes. Low priority.

  2. border-radius: 4px on .crew-profile code -- Same concern: literal 4px instead of a token. The rest of the codebase uses var(--radius) (which is 8px). Minor inconsistency.

  3. Rake task does not update existing records -- If a crew member's role or name changes in Keycloak, crew:sync will not reflect the change because find_or_create_by! only sets attributes in the creation block. This is consistent with feature_flags.rake behavior but could surprise someone. Consider documenting this limitation or using find_or_initialize_by + update! if future-proofing is desired. Not blocking for MVP.

  4. Show page date format -- @crew_member.created_at.strftime("%B %d, %Y") produces "June 06, 2026" (with leading zero on day). Consider %-d for "June 6, 2026" if that is preferred. Cosmetic.

SOP COMPLIANCE

  • Branch named after issue (117-crew-tab matches issue #117)
  • PR body follows template (Summary, Changes, Test Plan, Review Checklist, Related)
  • Related references parent issue #117 and parent epic #107
  • No secrets committed
  • No unnecessary file changes (all 11 files are directly related to the feature)
  • Commit messages are descriptive (PR title is clear)
  • Tests exist for new functionality (model specs, request specs, role access specs)
  • No inline styles, no Tailwind, no !important, no #id selectors

PROCESS OBSERVATIONS

  • Good incremental delivery: PR #136 created the skeleton, this PR fills it in. Clean separation.
  • The .role-badge CSS story across PRs #145, #147, #148, and #146 is worth tracking: PR #148 removed the duplicate from Person, and this PR reintroduces the single canonical definition in the Crew section. After rebase, the Person view's .role-badge usage will be styled again. This is the right outcome but was not explicitly called out in the PR body.
  • Test coverage is above average: model validations, request specs for both actions, and cross-cutting role access matrix all covered.
  • The crew:sync task will need a corresponding initContainer entry in the deployment config (similar to feature_flags:sync per issue #140) when this goes to production. Not blocking for this PR but worth a follow-up ticket.

VERDICT: NOT APPROVED

One blocker: the db/schema.rb update is missing from the diff. Run rails db:migrate on the branch, commit the updated schema.rb, and push. Everything else is clean and well-executed.

## PR #146 Review ### DOMAIN REVIEW **Stack:** Ruby on Rails 8.0, RSpec request specs, vanilla CSS with design tokens, Rake tasks. **Migration** (`db/migrate/20260607030548_create_crew_members.rb`): - Clean schema: `name`, `keycloak_username`, `role`, `active` with `null: false` constraints and `default: true` on `active`. - Unique index on `keycloak_username` -- correct, matches model uniqueness validation. - Note: the `db/schema.rb` in the diff does NOT include the new `crew_members` table. This is expected if the PR branch was not rebased after running the migration, but it means the schema.rb update is **missing from the diff**. The migration file itself is correct, but schema.rb must be updated before merge (run `rails db:migrate` and commit the result). **This is a blocker** -- Rails uses schema.rb for `db:schema:load` in CI and fresh setups. **Model** (`app/models/crew_member.rb`): - ROLES constant matches the five-role system: `client, member, lead, admin, super_admin`. Correct. - Validates presence of all three required fields plus role inclusion. Correct. - Validates uniqueness of `keycloak_username`. Correct and backed by DB index. - `active` and `by_name` scopes are clean and appropriate. - No mass assignment concern: the model has no `has_many` or `accepts_nested_attributes_for`, and the controller never calls `create`/`update` from params. **Controller** (`app/controllers/crew_controller.rb`): - `require_role :admin, :super_admin` at class level -- gates both index and show. Correct. - `index`: uses `CrewMember.active.by_name` -- correct, only shows active members. - `show`: uses `CrewMember.find(params[:id])` -- this raises `ActiveRecord::RecordNotFound` for missing IDs, which Rails maps to 404 by default. Acceptable. Note: this allows admins to view inactive members via direct URL (by ID), which is tested and intentional per the spec `it "can view inactive members"`. - No user input flows to create/update, so no mass assignment or injection risk. **Views:** - `index.html.erb`: Iterates `@crew_members`, shows name, role badge, and active status. Empty state handled with `@crew_members.any?` guard. Uses `link_to crew_path(member)` for navigation. All values are output via `<%= %>` (ERB auto-escapes by default). No XSS risk. - `show.html.erb`: Displays member profile as a `<dl>` grid. Back link uses `crew_index_path`. Shows role, status, keycloak username, and created date. Clean. Uses `strftime` for date formatting -- acceptable. - Both views correctly use `class="role-badge role-badge-<%= member.role %>"` pattern with design-token-based CSS. No inline styles. **Routes** (`config/routes.rb`): - Changed from `only: [:index]` to `only: [:index, :show]`. Correct and minimal. **CSS** (`app/assets/stylesheets/application.css`): - Removes `.crew-placeholder` (no longer used). Good cleanup. - Updates `.crew-item` (removes old padding/color/font-size, adds border-bottom only). Clean. - New classes: `.crew-item-link`, `.crew-item-info`, `.crew-item-meta`, `.crew-item-status`, `.crew-profile`, `.role-badge`, `.role-badge-{role}`, `.status-indicator`, `.status-indicator-{active|inactive}`. All use design tokens. No magic numbers. No `!important`. No `#id` selectors. No Tailwind. - Component comment header updated to reference both views. Good. **CSS conflict with PR #148 (IMPORTANT):** PR #148 removed a duplicate `.role-badge` from the Person CSS section. On current main, `.role-badge` is referenced in `person/index.html.erb` (line 19) but has NO CSS definition -- it is unstyled. This PR introduces a `.role-badge` definition in the Crew CSS section. After rebasing onto current main, this will be the ONLY `.role-badge` definition, and it will correctly style both the Person view and the Crew views. This is actually a **fix** for the post-PR-#148 regression. No conflict -- this is additive and welcome. **Rake task** (`lib/tasks/crew.rake`): - Uses `find_or_create_by!(keycloak_username:)` with a block -- idempotent for creates. Correct pattern, matches `feature_flags.rake`. - Five test users with correct Keycloak usernames: `lucas-super-admin`, `lucas-admin`, `lucas-lead`, `lucas-crew`, `lucas-client`. Matches the requirement. - Note: `find_or_create_by!` will NOT update `name` or `role` on subsequent runs if the record already exists. This is consistent with `feature_flags.rake` behavior but worth documenting. **Specs:** - `spec/models/crew_member_spec.rb`: Covers all validations (presence of name, keycloak_username, role; uniqueness of keycloak_username; role inclusion with invalid value AND all valid values), both scopes (active, by_name), and defaults (active: true). Thorough. - `spec/requests/crew_spec.rb`: Covers index and show for dev mode (no Keycloak) and Keycloak mode. Tests admin access, super_admin access, member denial, lead denial, client denial, active/inactive filtering, and member profile display. Comprehensive. - `spec/requests/role_access_spec.rb`: Adds `/crew/:id` tests for all role categories (unauthenticated, dev mode, member, lead, admin, super_admin, client). Matches the existing pattern for `/crew` index. Thorough. ### BLOCKERS 1. **Missing `db/schema.rb` update.** The migration file is present but the diff does not include a schema.rb change. The current schema.rb on main (version `2026_06_07_011440`) does not contain `crew_members`. After running the migration, the schema.rb must be committed with the new table definition. Without this, `rails db:schema:load` (used in CI and fresh deploys) will not create the `crew_members` table. Run `rails db:migrate` and commit the resulting `db/schema.rb`. ### NITS 1. **`border-radius: 1rem` on badges** -- The `.role-badge` and `.status-indicator` both use `border-radius: 1rem` as a literal value rather than a design token. The existing `--radius: 8px` token is for standard corners; a pill radius is a different concept. Consider adding a `--radius-pill: 1rem` token for consistency, or accept the literal value as an intentional one-off for pill shapes. Low priority. 2. **`border-radius: 4px` on `.crew-profile code`** -- Same concern: literal `4px` instead of a token. The rest of the codebase uses `var(--radius)` (which is `8px`). Minor inconsistency. 3. **Rake task does not update existing records** -- If a crew member's role or name changes in Keycloak, `crew:sync` will not reflect the change because `find_or_create_by!` only sets attributes in the creation block. This is consistent with `feature_flags.rake` behavior but could surprise someone. Consider documenting this limitation or using `find_or_initialize_by` + `update!` if future-proofing is desired. Not blocking for MVP. 4. **Show page date format** -- `@crew_member.created_at.strftime("%B %d, %Y")` produces "June 06, 2026" (with leading zero on day). Consider `%-d` for "June 6, 2026" if that is preferred. Cosmetic. ### SOP COMPLIANCE - [x] Branch named after issue (`117-crew-tab` matches issue #117) - [x] PR body follows template (Summary, Changes, Test Plan, Review Checklist, Related) - [x] Related references parent issue #117 and parent epic #107 - [x] No secrets committed - [x] No unnecessary file changes (all 11 files are directly related to the feature) - [x] Commit messages are descriptive (PR title is clear) - [x] Tests exist for new functionality (model specs, request specs, role access specs) - [x] No inline styles, no Tailwind, no `!important`, no `#id` selectors ### PROCESS OBSERVATIONS - Good incremental delivery: PR #136 created the skeleton, this PR fills it in. Clean separation. - The `.role-badge` CSS story across PRs #145, #147, #148, and #146 is worth tracking: PR #148 removed the duplicate from Person, and this PR reintroduces the single canonical definition in the Crew section. After rebase, the Person view's `.role-badge` usage will be styled again. This is the right outcome but was not explicitly called out in the PR body. - Test coverage is above average: model validations, request specs for both actions, and cross-cutting role access matrix all covered. - The `crew:sync` task will need a corresponding `initContainer` entry in the deployment config (similar to `feature_flags:sync` per issue #140) when this goes to production. Not blocking for this PR but worth a follow-up ticket. ### VERDICT: NOT APPROVED One blocker: the `db/schema.rb` update is missing from the diff. Run `rails db:migrate` on the branch, commit the updated schema.rb, and push. Everything else is clean and well-executed.
Author
Owner

PR #146 Review

DOMAIN REVIEW

Tech stack: Ruby on Rails 8.0, ERB views, vanilla CSS, RSpec request specs, PostgreSQL.

Model (app/models/crew_member.rb)

  • Clean, minimal model. ROLES constant, presence/uniqueness/inclusion validations, two scopes. No issues.
  • frozen_string_literal pragma present. Good.

Controller (app/controllers/crew_controller.rb)

  • require_role :admin, :super_admin applied at class level -- correctly gates both index and show. Consistent with the existing TAB_ROLES hash in ApplicationController (line 82: crew: %w[admin super_admin]).
  • show uses CrewMember.find(params[:id]) -- this follows the same pattern as PropertiesController#show (bare find, relying on Rails default 404 for RecordNotFound). Consistent.
  • index scopes to active.by_name -- only active members shown in the list. Good. Show action intentionally allows viewing inactive members by direct ID, which the test covers explicitly.

Migration (db/migrate/20260607030548_create_crew_members.rb)

  • All columns have null: false. active defaults to true. Unique index on keycloak_username. Clean.
  • schema.rb reflects the migration correctly (verified lines 45-53).

Views

  • index.html.erb: Empty state handled (No crew members yet). Role badge uses role-badge role-badge-<%= member.role %> pattern with dynamic class. Status indicator uses ternary for active/inactive. Properly links to crew_path(member).
  • show.html.erb: Uses crew_index_path for back link (correct for resources :crew routing). strftime("%B %d, %Y") for date formatting. Displays keycloak_username in a <code> tag.

CSS (application.css)

  • Role badges are defined ONLY in the Crew component section. The Person section previously had duplicate .role-badge CSS which was removed in PR #148. Confirmed: Person view (person/index.html.erb line 19) uses .role-badge without modifier classes, and the base .role-badge style in the Crew section provides the default (gray background/muted text). The Person view will inherit this base style correctly. No duplication.
  • Status indicators (.status-indicator-active, .status-indicator-inactive) follow the same pill badge pattern as .flag-status in the FlagsTable section. Slightly duplicative in structure but different semantics. Acceptable.
  • All spacing uses design tokens (--spacing-*, --color-*, --radius). No magic numbers except border-radius: 1rem on pills (intentional full-round) and border-radius: 4px on code blocks (matches existing pattern in .flags-table code).

Rake task (lib/tasks/crew.rake)

  • Follows feature_flags.rake pattern exactly: find_or_create_by! with keycloak_username as the unique key, block sets remaining attributes. Idempotent -- re-running does not duplicate or update existing records (by design: find_or_create_by! only runs the block for new records).
  • Seeds 5 test users covering all 5 roles. Good for dev/test coverage.

Routes (config/routes.rb)

  • Added :show to resources :crew. Minimal, correct change.

BLOCKERS

None.

NITS

  1. Rake task will not update existing records. find_or_create_by! only executes the block for new records. If you later need to change a seeded user's name or role, re-running rake crew:sync will not apply the update. This matches the feature_flags.rake pattern intentionally, but worth documenting as a conscious choice. Consider whether find_or_initialize_by + update! would be better for a "sync" task (sync implies updating to match the source of truth, not just creating missing records). Non-blocking -- current behavior is correct for initial seeding.

  2. keycloak_username displayed on show page. This is an internal Keycloak identifier visible to admin/super_admin only, so not a security concern. But if crew members eventually get non-admin views, this should be hidden.

  3. No RecordNotFound rescue for show. If an admin visits /crew/99999, they get Rails' default 404 page. This is consistent with PropertiesController#show so it is not a divergence, but a user-friendly "Member not found" page could be a future improvement.

  4. View template: member.active? ternary is repeated. The status-indicator span with member.active? ? 'active' : 'inactive' and member.active? ? "Active" : "Inactive" appears in both index and show views. A helper method could DRY this up if more views use it later. Non-blocking for two usages.

SOP COMPLIANCE

  • Branch named after issue (117-crew-tab maps to issue #117)
  • PR body follows template (Summary, Changes, Test Plan, Review Checklist, Related)
  • Related section references parent issue (#117) and parent epic (#107)
  • No secrets committed (keycloak_username values are test fixtures, not real credentials)
  • No unnecessary file changes (11 files, all directly related to crew member views)
  • Commit messages -- single PR, descriptive title
  • Test coverage present for model, request specs (index + show), and role access matrix

PROCESS OBSERVATIONS

  • Test coverage is thorough. Model spec covers all validations, both scopes, and defaults. Request spec covers index and show for all 5 roles (member, lead, admin, super_admin, client) plus unauthenticated and dev-mode scenarios. Role access spec extended with /crew/:id for all role permutations. This is well above the minimum bar.
  • Deployment risk is low. New table, no existing table modifications, no data migration needed. The migration is additive only. rake crew:sync must be run post-deploy to seed data (or the empty state message appears, which is a clean fallback).
  • CSS architecture is clean. Role badges are now centralized in the Crew section and shared by Person view via the base .role-badge class. PR #148 cleanup is validated -- no duplicate definitions remain.
  • Schema version alignment. db/schema.rb shows version: 2026_06_07_030548 matching the migration filename. The schema file appears to be committed with changes (git status shows M db/schema.rb), which is correct practice for Rails.

VERDICT: APPROVED

## PR #146 Review ### DOMAIN REVIEW **Tech stack:** Ruby on Rails 8.0, ERB views, vanilla CSS, RSpec request specs, PostgreSQL. **Model (`app/models/crew_member.rb`)** - Clean, minimal model. `ROLES` constant, presence/uniqueness/inclusion validations, two scopes. No issues. - `frozen_string_literal` pragma present. Good. **Controller (`app/controllers/crew_controller.rb`)** - `require_role :admin, :super_admin` applied at class level -- correctly gates both `index` and `show`. Consistent with the existing `TAB_ROLES` hash in `ApplicationController` (line 82: `crew: %w[admin super_admin]`). - `show` uses `CrewMember.find(params[:id])` -- this follows the same pattern as `PropertiesController#show` (bare `find`, relying on Rails default 404 for `RecordNotFound`). Consistent. - `index` scopes to `active.by_name` -- only active members shown in the list. Good. Show action intentionally allows viewing inactive members by direct ID, which the test covers explicitly. **Migration (`db/migrate/20260607030548_create_crew_members.rb`)** - All columns have `null: false`. `active` defaults to `true`. Unique index on `keycloak_username`. Clean. - `schema.rb` reflects the migration correctly (verified lines 45-53). **Views** - `index.html.erb`: Empty state handled (`No crew members yet`). Role badge uses `role-badge role-badge-<%= member.role %>` pattern with dynamic class. Status indicator uses ternary for active/inactive. Properly links to `crew_path(member)`. - `show.html.erb`: Uses `crew_index_path` for back link (correct for `resources :crew` routing). `strftime("%B %d, %Y")` for date formatting. Displays keycloak_username in a `<code>` tag. **CSS (`application.css`)** - Role badges are defined ONLY in the Crew component section. The Person section previously had duplicate `.role-badge` CSS which was removed in PR #148. Confirmed: Person view (`person/index.html.erb` line 19) uses `.role-badge` without modifier classes, and the base `.role-badge` style in the Crew section provides the default (gray background/muted text). The Person view will inherit this base style correctly. No duplication. - Status indicators (`.status-indicator-active`, `.status-indicator-inactive`) follow the same pill badge pattern as `.flag-status` in the FlagsTable section. Slightly duplicative in structure but different semantics. Acceptable. - All spacing uses design tokens (`--spacing-*`, `--color-*`, `--radius`). No magic numbers except `border-radius: 1rem` on pills (intentional full-round) and `border-radius: 4px` on code blocks (matches existing pattern in `.flags-table code`). **Rake task (`lib/tasks/crew.rake`)** - Follows `feature_flags.rake` pattern exactly: `find_or_create_by!` with keycloak_username as the unique key, block sets remaining attributes. Idempotent -- re-running does not duplicate or update existing records (by design: `find_or_create_by!` only runs the block for new records). - Seeds 5 test users covering all 5 roles. Good for dev/test coverage. **Routes (`config/routes.rb`)** - Added `:show` to `resources :crew`. Minimal, correct change. ### BLOCKERS None. ### NITS 1. **Rake task will not update existing records.** `find_or_create_by!` only executes the block for new records. If you later need to change a seeded user's name or role, re-running `rake crew:sync` will not apply the update. This matches the `feature_flags.rake` pattern intentionally, but worth documenting as a conscious choice. Consider whether `find_or_initialize_by` + `update!` would be better for a "sync" task (sync implies updating to match the source of truth, not just creating missing records). Non-blocking -- current behavior is correct for initial seeding. 2. **`keycloak_username` displayed on show page.** This is an internal Keycloak identifier visible to admin/super_admin only, so not a security concern. But if crew members eventually get non-admin views, this should be hidden. 3. **No `RecordNotFound` rescue for `show`.** If an admin visits `/crew/99999`, they get Rails' default 404 page. This is consistent with `PropertiesController#show` so it is not a divergence, but a user-friendly "Member not found" page could be a future improvement. 4. **View template: `member.active?` ternary is repeated.** The `status-indicator` span with `member.active? ? 'active' : 'inactive'` and `member.active? ? "Active" : "Inactive"` appears in both index and show views. A helper method could DRY this up if more views use it later. Non-blocking for two usages. ### SOP COMPLIANCE - [x] Branch named after issue (`117-crew-tab` maps to issue #117) - [x] PR body follows template (Summary, Changes, Test Plan, Review Checklist, Related) - [x] Related section references parent issue (#117) and parent epic (#107) - [x] No secrets committed (keycloak_username values are test fixtures, not real credentials) - [x] No unnecessary file changes (11 files, all directly related to crew member views) - [x] Commit messages -- single PR, descriptive title - [x] Test coverage present for model, request specs (index + show), and role access matrix ### PROCESS OBSERVATIONS - **Test coverage is thorough.** Model spec covers all validations, both scopes, and defaults. Request spec covers index and show for all 5 roles (member, lead, admin, super_admin, client) plus unauthenticated and dev-mode scenarios. Role access spec extended with `/crew/:id` for all role permutations. This is well above the minimum bar. - **Deployment risk is low.** New table, no existing table modifications, no data migration needed. The migration is additive only. `rake crew:sync` must be run post-deploy to seed data (or the empty state message appears, which is a clean fallback). - **CSS architecture is clean.** Role badges are now centralized in the Crew section and shared by Person view via the base `.role-badge` class. PR #148 cleanup is validated -- no duplicate definitions remain. - **Schema version alignment.** `db/schema.rb` shows `version: 2026_06_07_030548` matching the migration filename. The schema file appears to be committed with changes (git status shows `M db/schema.rb`), which is correct practice for Rails. ### VERDICT: APPROVED
ldraney deleted branch 117-crew-tab 2026-06-07 04:51:26 +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!146
No description provided.