Review: Profile tab: business code join form

review-1906-2026-07-25 Review

review ready

Verdict: READY

Template Completeness

  • [x] Type — Feature
  • [x] Lineage — SaaS / multi-tenancy sprint
  • [x] Repo — ldraney/landscaping-assistant
  • [x] User Story — well-formed (As a user on the Profile/Person tab...)
  • [x] Context — thorough, describes current placeholder state and target flow with 6-step behavior spec
  • [x] File Targets — 6 files listed in table format with current state and change needed
  • [x] Feature Flag — business_codes, disabled by default, removal criteria stated (2 weeks stable)
  • [x] Acceptance Criteria — 10 criteria, all testable
  • [x] Test Expectations — request specs in spec/requests/join_crew_spec.rb, 6 test cases described
  • [x] Constraints — 6 explicit do-NOT boundaries
  • [x] Checklist — present
  • [x] Dependencies — explicit hard dependency on #308
  • [x] Related — links to #308, downstream tickets, and docs

Traceability

  • [x] story:saas label — "SaaS subscription, business codes for crew onboarding, admin approval of join requests, Apple IAP billing"
  • [x] story note verified — found in project-landscaping-assistant user-stories section. Key: "saas", role: "All roles", summary includes business codes and crew onboarding. #309 explicitly listed as child of umbrella #308.
  • [x] arch:rails-app label — Rails App (Shared Pattern)
  • [x] arch note verified — arch-rails-app note exists in pal-e-docs (active, covers deployment pipeline, Hotwire, CNPG, Keycloak)
  • [x] Forgejo issue — https://forgejo.tail5b443a.ts.net/ldraney/landscaping-assistant/issues/309, state: open

File Targets

  • [x] app/views/join_crew/index.html.erb — verified (811 bytes): disabled placeholder form with business_name and join_notes fields, disabled Submit Request button. Matches issue description exactly.
  • [x] app/controllers/join_crew_controller.rb — verified (102 bytes, 6 lines): empty index action only, no create. Matches issue description.
  • [x] config/routes.rb — verified: line 63 has get "join-crew", to: "join_crew#index" only. No POST route. Matches issue description.
  • [x] app/models/user_business.rb — verified: STATUSES = %w[pending approved denied], ROLES = %w[crew_member crew_lead admin], validates uniqueness on [user_id, business_id]. No changes needed per issue. Confirmed.
  • [x] app/views/profile/index.html.erb — verified: line 35 links to join_crew_path. No change needed per issue. Confirmed.
  • [x] lib/tasks/feature_flags.rake — verified: FEATURES array exists with 14 flags, business_codes not yet present. Agent will add it.
Additional verification: user_businesses table confirmed in db/schema.rb with user_id (string — stores Keycloak sub), business_id (bigint FK), role, status (default: "pending"), favorited. Unique index on [user_id, business_id]. Existing test file spec/requests/join_crew_spec.rb has 3 passing specs for GET /join-crew (unauthenticated redirect, authenticated render, placeholder form). Agent will extend this file.
Note: Issue Context section references current_sub but the actual app helper is current_user[:sub] (verified in application_controller.rb and profile_controller.rb). This is in narrative text only — file targets and AC are correct. Agent will read actual code.

Repo Placement

OK. Issue filed on ldraney/landscaping-assistant. All file targets are in the same repo. Single-repo change, no cross-repo effects.

Dependencies

  • Hard dependency: #308 (board item #1905) — "Add business code and subscription fields to Business model". Adds join_code column to businesses table. Currently in backlog, state: open. Verified: join_code does NOT exist in db/schema.rb or any migration. This ticket calls Business.find_by(join_code: ...) which requires #308 merged first. Dependency correctly documented in issue.
  • Downstream: #310 (board item #1907) — "Admin approval UI on Crew tab for pending join requests". In backlog, 3 points. Enabled by #309 (creates pending UserBusiness records that #310 will display).
  • Downstream: #312 (board item #1909) — Keycloak registration field. In backlog, 5 points. Same story:saas but independent path.
  • No items currently in in_progress or next_up that block this ticket (next_up has only #1683, an iOS pipeline bug).
  • Issue correctly states exclusive ownership of business_codes feature flag registration — #310, #311, #312 should NOT also register it.

Acceptance Criteria

10 ACs, all testable via request specs. Each maps to a specific HTTP verb + path + expected response. Test expectations section names the spec file (spec/requests/join_crew_spec.rb) and describes 6 test cases covering the major ACs. Auth enforcement testable via existing sign_in_as helper (already used in the existing spec file). Feature flag gating testable via existing feature_enabled? helper. Turbo response testable via content type assertion.

Blast Radius

Low. Changes are self-contained to the join_crew namespace (controller + view + route + flag). UserBusiness model is NOT modified — only creates new records via standard ActiveRecord. Feature flag gating ensures zero impact when disabled. No cross-repo effects. No downstream consumers affected until #310 adds admin approval UI. The business_codes flag is new and independent of the existing multi_tenancy flag.

Decomposition Assessment

  • 6 file targets in 1 repo — under the >3 files across >2 repos threshold
  • 10 acceptance criteria — exceeds the >5 threshold
  • Estimated agent work: ~4 minutes (1 controller action, 1 view rewrite, 1 route, 1 flag)
The AC count formally exceeds the decomposition threshold. However, all 10 ACs are state variations of a single controller action (create) and a single view (conditional rendering based on membership status + feature flag). Decomposing "valid code submission" from "invalid code handling" or "denied status display" would split one controller action into artificial sub-tickets with shared dependencies on the same route and view. The work is cohesive: 1 new action + 1 view rewrite + 1 route + 1 flag definition. No decomposition needed.

Recommendations

No action needed.