From c822639260f9fefa9e577fdb5e42f310796fdd67 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Fri, 23 Jan 2026 13:27:04 -0500 Subject: [PATCH] Remove unnecessary claude plan --- docs/plans/2026-01-21-card-boosts-design.md | 741 -------------------- 1 file changed, 741 deletions(-) delete mode 100644 docs/plans/2026-01-21-card-boosts-design.md diff --git a/docs/plans/2026-01-21-card-boosts-design.md b/docs/plans/2026-01-21-card-boosts-design.md deleted file mode 100644 index c828a66b5..000000000 --- a/docs/plans/2026-01-21-card-boosts-design.md +++ /dev/null @@ -1,741 +0,0 @@ -# Card Boosts Implementation Plan - -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. - -**Goal:** Extend the Reaction model to support reactions on Cards (not just Comments), with a compact boost count on card previews and full reaction UI in the card detail footer. - -**Architecture:** Make Reaction polymorphic with `reactable_type`/`reactable_id`. Create `Cards::ReactionsController` mirroring the existing comment reactions controller. Add views for card reactions in the footer, and a compact count display on card previews. - -**Tech Stack:** Rails 8, Turbo, Stimulus, ERB views - ---- - -## Task 1: Migration - Make Reaction Polymorphic - -**Files:** -- Create: `db/migrate/YYYYMMDDHHMMSS_make_reactions_polymorphic.rb` - -**Step 1: Generate and write the migration** - -Run: -```bash -bin/rails generate migration MakeReactionsPolymorphic -``` - -Then edit the migration file: - -```ruby -class MakeReactionsPolymorphic < ActiveRecord::Migration[8.0] - def change - add_column :reactions, :reactable_type, :string - add_column :reactions, :reactable_id, :uuid - - reversible do |dir| - dir.up do - execute <<~SQL - UPDATE reactions SET reactable_type = 'Comment', reactable_id = comment_id - SQL - end - end - - change_column_null :reactions, :reactable_type, false - change_column_null :reactions, :reactable_id, false - - remove_column :reactions, :comment_id, :uuid - - add_index :reactions, [:reactable_type, :reactable_id] - end -end -``` - -**Step 2: Run the migration** - -Run: `bin/rails db:migrate` - -**Step 3: Verify migration succeeded** - -Run: `bin/rails runner "puts Reaction.column_names.inspect"` - -Expected: Should include `reactable_type` and `reactable_id`, should NOT include `comment_id` - -**Step 4: Commit** - -```bash -git add db/migrate/*_make_reactions_polymorphic.rb db/schema.rb -git commit -m "Make Reaction polymorphic for card and comment reactions" -``` - ---- - -## Task 2: Update Reaction Model - -**Files:** -- Modify: `app/models/reaction.rb` - -**Step 1: Write the failing test** - -Add to `test/models/reaction_test.rb`: - -```ruby -test "reaction can belong to a card" do - card = cards(:logo) - reaction = card.reactions.create!(content: "🎉") - - assert_equal card, reaction.reactable - assert_equal "Card", reaction.reactable_type -end - -test "creating card reaction touches card last_active_at" do - card = cards(:logo) - original_last_active_at = card.last_active_at - - travel 1.minute do - card.reactions.create!(content: "🚀") - end - - assert_operator card.reload.last_active_at, :>, original_last_active_at -end -``` - -**Step 2: Run the test to verify it fails** - -Run: `bin/rails test test/models/reaction_test.rb` - -Expected: FAIL - Card doesn't have `reactions` association yet - -**Step 3: Update the Reaction model** - -Replace `app/models/reaction.rb`: - -```ruby -class Reaction < ApplicationRecord - belongs_to :account, default: -> { reactable.account } - belongs_to :reactable, polymorphic: true, touch: true - belongs_to :reacter, class_name: "User", default: -> { Current.user } - - scope :ordered, -> { order(:created_at) } - - after_create :register_card_activity - - delegate :all_emoji?, to: :content - - private - def register_card_activity - reactable.card.touch_last_active_at - end -end -``` - -**Step 4: Run tests to verify they still fail (need Card association)** - -Run: `bin/rails test test/models/reaction_test.rb` - -Expected: FAIL - Card doesn't have `reactions` association - -**Step 5: Commit model change** - -```bash -git add app/models/reaction.rb test/models/reaction_test.rb -git commit -m "Update Reaction model to be polymorphic" -``` - ---- - -## Task 3: Update Comment Model - -**Files:** -- Modify: `app/models/comment.rb` - -**Step 1: Update Comment association** - -In `app/models/comment.rb`, change line 7 from: - -```ruby -has_many :reactions, -> { order(:created_at) }, dependent: :delete_all -``` - -to: - -```ruby -has_many :reactions, -> { order(:created_at) }, as: :reactable, dependent: :delete_all -``` - -**Step 2: Run existing comment reaction tests** - -Run: `bin/rails test test/controllers/cards/comments/reactions_controller_test.rb` - -Expected: All tests PASS - -**Step 3: Commit** - -```bash -git add app/models/comment.rb -git commit -m "Update Comment reactions association for polymorphic" -``` - ---- - -## Task 4: Add Reactions to Card Model - -**Files:** -- Modify: `app/models/card.rb` - -**Step 1: Add reactions association to Card** - -In `app/models/card.rb`, after the `has_many :comments` line (around line 10), add: - -```ruby -has_many :reactions, -> { order(:created_at) }, as: :reactable, dependent: :delete_all -``` - -**Step 2: Run the model tests** - -Run: `bin/rails test test/models/reaction_test.rb` - -Expected: All tests PASS - -**Step 3: Commit** - -```bash -git add app/models/card.rb -git commit -m "Add reactions association to Card model" -``` - ---- - -## Task 5: Add Routes for Card Reactions - -**Files:** -- Modify: `config/routes.rb` - -**Step 1: Write failing test for routes** - -Add to `test/controllers/cards/reactions_controller_test.rb` (new file): - -```ruby -require "test_helper" - -class Cards::ReactionsControllerTest < ActionDispatch::IntegrationTest - setup do - sign_in_as :david - @card = cards(:logo) - end - - test "routes exist" do - assert_routing({ path: "/5986089/cards/#{@card.number}/reactions", method: :get }, - { controller: "cards/reactions", action: "index", account_id: "5986089", card_id: @card.number.to_s }) - assert_routing({ path: "/5986089/cards/#{@card.number}/reactions", method: :post }, - { controller: "cards/reactions", action: "create", account_id: "5986089", card_id: @card.number.to_s }) - end -end -``` - -**Step 2: Run test to verify it fails** - -Run: `bin/rails test test/controllers/cards/reactions_controller_test.rb` - -Expected: FAIL - No route matches - -**Step 3: Add routes** - -In `config/routes.rb`, inside the `resources :cards` block (around line 93), add after `resources :taggings`: - -```ruby -resources :reactions, module: :cards -``` - -So it looks like: - -```ruby -resources :taggings - -resources :reactions, module: :cards - -resources :comments do -``` - -**Step 4: Run test to verify routes exist** - -Run: `bin/rails test test/controllers/cards/reactions_controller_test.rb` - -Expected: FAIL - Controller doesn't exist (but routes work) - -**Step 5: Commit** - -```bash -git add config/routes.rb test/controllers/cards/reactions_controller_test.rb -git commit -m "Add routes for card reactions" -``` - ---- - -## Task 6: Create Cards::ReactionsController - -**Files:** -- Create: `app/controllers/cards/reactions_controller.rb` - -**Step 1: Add controller tests** - -Replace `test/controllers/cards/reactions_controller_test.rb`: - -```ruby -require "test_helper" - -class Cards::ReactionsControllerTest < ActionDispatch::IntegrationTest - setup do - sign_in_as :david - @card = cards(:logo) - end - - test "index" do - get card_reactions_path(@card) - assert_response :success - end - - test "create" do - assert_difference -> { @card.reactions.count }, 1 do - post card_reactions_path(@card, format: :turbo_stream), params: { reaction: { content: "🎉" } } - assert_turbo_stream action: :replace, target: dom_id(@card, :reacting) - end - end - - test "destroy" do - reaction = @card.reactions.create!(content: "👍") - - assert_difference -> { @card.reactions.count }, -1 do - delete card_reaction_path(@card, reaction, format: :turbo_stream) - assert_turbo_stream action: :remove, target: dom_id(reaction) - end - end - - test "non-owner cannot destroy reaction" do - sign_in_as :kevin - reaction = @card.reactions.create!(content: "👍", reacter: users(:david)) - - assert_no_difference -> { @card.reactions.count } do - delete card_reaction_path(@card, reaction, format: :turbo_stream) - assert_response :forbidden - end - end - - test "create as JSON" do - assert_difference -> { @card.reactions.count }, 1 do - post card_reactions_path(@card), params: { reaction: { content: "👍" } }, as: :json - end - - assert_response :created - end - - test "destroy as JSON" do - reaction = @card.reactions.create!(content: "👍") - - assert_difference -> { @card.reactions.count }, -1 do - delete card_reaction_path(@card, reaction), as: :json - end - - assert_response :no_content - end -end -``` - -**Step 2: Run tests to verify they fail** - -Run: `bin/rails test test/controllers/cards/reactions_controller_test.rb` - -Expected: FAIL - Controller doesn't exist - -**Step 3: Create the controller** - -Create `app/controllers/cards/reactions_controller.rb`: - -```ruby -class Cards::ReactionsController < ApplicationController - include CardScoped - - before_action :set_reaction, only: %i[ destroy ] - before_action :ensure_permision_to_administer_reaction, only: %i[ destroy ] - - def index - end - - def new - end - - def create - @reaction = @card.reactions.create!(params.expect(reaction: :content)) - - respond_to do |format| - format.turbo_stream - format.json { head :created } - end - end - - def destroy - @reaction.destroy - - respond_to do |format| - format.turbo_stream - format.json { head :no_content } - end - end - - private - def set_reaction - @reaction = @card.reactions.find(params[:id]) - end - - def ensure_permision_to_administer_reaction - head :forbidden if Current.user != @reaction.reacter - end -end -``` - -**Step 4: Run tests (will fail due to missing views)** - -Run: `bin/rails test test/controllers/cards/reactions_controller_test.rb` - -Expected: FAIL - Missing template - -**Step 5: Commit controller** - -```bash -git add app/controllers/cards/reactions_controller.rb test/controllers/cards/reactions_controller_test.rb -git commit -m "Add Cards::ReactionsController" -``` - ---- - -## Task 7: Create Card Reactions Views - -**Files:** -- Create: `app/views/cards/reactions/_reactions.html.erb` -- Create: `app/views/cards/reactions/_reaction.html.erb` -- Create: `app/views/cards/reactions/_menu.html.erb` -- Create: `app/views/cards/reactions/index.html.erb` -- Create: `app/views/cards/reactions/new.html.erb` -- Create: `app/views/cards/reactions/create.turbo_stream.erb` -- Create: `app/views/cards/reactions/destroy.turbo_stream.erb` - -**Step 1: Create the views directory** - -Run: `mkdir -p app/views/cards/reactions` - -**Step 2: Create `_reactions.html.erb`** - -```erb -<%= turbo_frame_tag card, :reacting do %> -
-
- <%= render partial: "cards/reactions/reaction", collection: card.reactions %> -
- - <%= turbo_frame_tag card, :new_reaction do %> - <%= link_to new_card_reaction_path(card), role: "button", - class: "reactions__trigger btn btn--circle", action: "soft-keyboard#open", - data: { turbo_frame: dom_id(card, :new_reaction), action: "dialog#close" } do %> - <%= image_tag "boost-color.svg", aria: { hidden: true } %> - Add your own reaction - <% end %> - <% end %> -
-<% end %> -``` - -**Step 3: Create `_reaction.html.erb`** - -```erb -
-
- <%= avatar_tag reaction.reacter, aria: { label: "#{reaction.reacter.name} reacted #{reaction.content}" } %> -
- - <%= tag.span reaction.content, role: "button", - class: [ "txt-small", { "txt-medium": reaction.all_emoji? } ], - data: { action: "click->reaction-delete#reveal keydown.enter->reaction-delete#reveal:prevent", reaction_delete_target: "content" } %> - - <%= button_to card_reaction_path(reaction.reactable, reaction), - method: :delete, - class: "reaction__delete btn btn--negative flex-item-justify-end", - data: { action: "reaction-delete#perform", reaction_delete_target: "button" } do %> - <%= icon_tag "trash" %> - Delete this reaction - <% end %> -
-``` - -**Step 4: Create `_menu.html.erb`** - -```erb -
- - - -
- <% EmojiHelper::REACTIONS.each do |character, title| %> - <%= tag.button character, title: title, class: "reaction__emoji-btn btn btn--circle borderless hide-focus-ring", type: "button", data: { action: "reaction-emoji#insertEmoji dialog#close", emoji: character } %> - <% end %> -
-
-
-``` - -**Step 5: Create `index.html.erb`** - -```erb -<%= render "cards/reactions/reactions", card: @card %> -``` - -**Step 6: Create `new.html.erb`** - -```erb -<%= turbo_frame_tag @card, :new_reaction do %> - <%= form_with url: card_reactions_path(@card), - class: "reaction reaction__form expanded", - html: { aria: { label: "New reaction" } }, - data: { controller: "form reaction-emoji", turbo_frame: dom_id(@card, :reacting), action: "keydown.esc->form#cancel submit->form#preventEmptySubmit submit->form#preventComposingSubmit" } do |form| %> - - - <%= render "cards/reactions/menu", card: @card %> - - <%= form.button class: "reaction__submit-btn btn btn--circle borderless", type: "submit", data: { form_target: "submit" } do %> - <%= icon_tag "check-circle" %> Submit - <% end %> - - <%= link_to card_reactions_path(@card), role: "button", - data: { turbo_frame: dom_id(@card, :reacting), form_target: "cancel" }, class: "reaction__cancel-btn btn btn--circle borderless" do %> - <%= icon_tag "close-circle" %> Cancel - <% end %> - <% end %> -<% end %> -``` - -**Step 7: Create `create.turbo_stream.erb`** - -```erb -<%= turbo_stream.replace([ @card, :reacting ]) do %> - <%= render "cards/reactions/reactions", card: @card.reload %> -<% end %> -``` - -**Step 8: Create `destroy.turbo_stream.erb`** - -```erb -<%= turbo_stream.remove @reaction %> -``` - -**Step 9: Run controller tests** - -Run: `bin/rails test test/controllers/cards/reactions_controller_test.rb` - -Expected: All tests PASS - -**Step 10: Commit** - -```bash -git add app/views/cards/reactions/ -git commit -m "Add card reactions views" -``` - ---- - -## Task 8: Add Reactions UI to Card Detail Footer - -**Files:** -- Modify: `app/views/cards/container/footer/_published.html.erb` - -**Step 1: Add reactions to footer** - -Replace `app/views/cards/container/footer/_published.html.erb`: - -```erb -<%# FIXME: Let's move this aside outside of the card container section so these frames don't reload/flicker when card is replaced %> -
- <%= render "cards/reactions/reactions", card: card %> -
- -
- <%= turbo_frame_tag card, :watch, src: card_watch_path(card), target: "_top", refresh: :morph do %> - <%= button_to card_watch_path(card), class: "btn", data: { controller: "tooltip" } do %> - <%= icon_tag "bell-off" %> Watch this - <% end %> - <% end %> - <%= turbo_frame_tag card, :pin, src: card_pin_path(card), refresh: :morph do %> - <%= button_to card_pin_path(card), class: "btn", data: { controller: "tooltip" } do %> - <%= icon_tag "unpinned" %> Pin this card - <% end %> - <% end %> -
- -<%= render "cards/container/closure", card: card %> -``` - -**Step 2: Manually test in browser** - -Run: `bin/dev` - -Visit a card detail page and verify the reactions UI appears in the footer. - -**Step 3: Commit** - -```bash -git add app/views/cards/container/footer/_published.html.erb -git commit -m "Add reactions UI to card detail footer" -``` - ---- - -## Task 9: Add Boost Count to Card Preview - -**Files:** -- Create: `app/views/cards/display/preview/_reactions.html.erb` -- Modify: `app/views/cards/display/_preview.html.erb` - -**Step 1: Create the preview reactions partial** - -Create `app/views/cards/display/preview/_reactions.html.erb`: - -```erb -<% if card.reactions.any? %> -
- <%= image_tag "boost-color.svg", aria: { hidden: true }, class: "card__reactions-icon" %> - <%= card.reactions.size %> -
-<% end %> -``` - -**Step 2: Add to card preview footer** - -In `app/views/cards/display/_preview.html.erb`, modify the footer section (lines 47-51) to add the reactions partial. Change from: - -```erb - -``` - -to: - -```erb - -``` - -**Step 3: Manually test in browser** - -Visit a board page, add a reaction to a card in detail view, then verify the count appears on the card preview. - -**Step 4: Commit** - -```bash -git add app/views/cards/display/preview/_reactions.html.erb app/views/cards/display/_preview.html.erb -git commit -m "Add boost count to card preview" -``` - ---- - -## Task 10: Add Fixtures for Card Reactions - -**Files:** -- Modify: `test/fixtures/reactions.yml` - -**Step 1: Add card reaction fixtures** - -Add to `test/fixtures/reactions.yml`: - -```yaml -card_reaction_david: - id: <%= ActiveRecord::FixtureSet.identify("card_reaction_david", :uuid) %> - account: 37s_uuid - content: "🚀" - reactable_type: Card - reactable_id: <%= ActiveRecord::FixtureSet.identify("logo", :uuid) %> - reacter: david_uuid - -card_reaction_kevin: - id: <%= ActiveRecord::FixtureSet.identify("card_reaction_kevin", :uuid) %> - account: 37s_uuid - content: "👍" - reactable_type: Card - reactable_id: <%= ActiveRecord::FixtureSet.identify("logo", :uuid) %> - reacter: kevin_uuid -``` - -Also update existing fixtures to include `reactable_type`: - -```yaml -kevin: - id: <%= ActiveRecord::FixtureSet.identify("kevin_reaction", :uuid) %> - account: 37s_uuid - content: "👍" - reactable_type: Comment - reactable_id: <%= ActiveRecord::FixtureSet.identify("logo_agreement_jz", :uuid) %> - reacter: kevin_uuid - -david: - id: <%= ActiveRecord::FixtureSet.identify("david_reaction", :uuid) %> - account: 37s_uuid - content: "👍" - reactable_type: Comment - reactable_id: <%= ActiveRecord::FixtureSet.identify("logo_agreement_jz", :uuid) %> - reacter: david_uuid -``` - -**Step 2: Run all tests** - -Run: `bin/rails test` - -Expected: All tests PASS - -**Step 3: Commit** - -```bash -git add test/fixtures/reactions.yml -git commit -m "Add card reaction fixtures" -``` - ---- - -## Task 11: Run Full Test Suite - -**Step 1: Run full CI suite** - -Run: `bin/ci` - -Expected: All checks PASS - -**Step 2: Fix any issues** - -If any tests fail, fix them and commit. - ---- - -## Task 12: Style Card Reactions (if needed) - -Work with the user to adjust styling for the card reactions in both the detail footer and preview. May need to add CSS for: - -- `.card__reactions` - container on preview -- `.card__reactions-icon` - boost icon sizing -- Positioning of reactions in footer - -This will be an iterative process based on visual feedback.