reviewkit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/CODE_OF_CONDUCT.md +123 -0
  4. data/CONTRIBUTING.md +44 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +335 -0
  7. data/Rakefile +7 -0
  8. data/SECURITY.md +18 -0
  9. data/app/assets/builds/reviewkit/application.css +2 -0
  10. data/app/assets/javascripts/reviewkit/application.js +12 -0
  11. data/app/assets/javascripts/reviewkit/controllers/file_nav_controller.js +24 -0
  12. data/app/assets/javascripts/reviewkit/controllers/review_index_controller.js +84 -0
  13. data/app/assets/tailwind/reviewkit/application.css +865 -0
  14. data/app/controllers/reviewkit/application_controller.rb +80 -0
  15. data/app/controllers/reviewkit/comments_controller.rb +147 -0
  16. data/app/controllers/reviewkit/review_threads_controller.rb +277 -0
  17. data/app/controllers/reviewkit/reviews_controller.rb +142 -0
  18. data/app/helpers/reviewkit/application_helper.rb +12 -0
  19. data/app/helpers/reviewkit/asset_helper.rb +39 -0
  20. data/app/helpers/reviewkit/diff_helper.rb +230 -0
  21. data/app/helpers/reviewkit/flash_helper.rb +36 -0
  22. data/app/helpers/reviewkit/frame_helper.rb +37 -0
  23. data/app/helpers/reviewkit/icon_helper.rb +107 -0
  24. data/app/helpers/reviewkit/review_thread_helper.rb +54 -0
  25. data/app/models/concerns/reviewkit/notifies_lifecycle_events.rb +39 -0
  26. data/app/models/reviewkit/application_record.rb +7 -0
  27. data/app/models/reviewkit/comment.rb +29 -0
  28. data/app/models/reviewkit/current.rb +7 -0
  29. data/app/models/reviewkit/document.rb +79 -0
  30. data/app/models/reviewkit/review.rb +66 -0
  31. data/app/models/reviewkit/review_thread.rb +75 -0
  32. data/app/services/reviewkit/diffs/intraline_budget.rb +40 -0
  33. data/app/services/reviewkit/diffs/intraline_diff.rb +220 -0
  34. data/app/services/reviewkit/diffs/split_diff.rb +112 -0
  35. data/app/services/reviewkit/reviews/create.rb +57 -0
  36. data/app/views/layouts/reviewkit/application.html.erb +15 -0
  37. data/app/views/reviewkit/comments/_comment.html.erb +53 -0
  38. data/app/views/reviewkit/comments/_edit_form.html.erb +26 -0
  39. data/app/views/reviewkit/comments/_form.html.erb +16 -0
  40. data/app/views/reviewkit/review_threads/_bucket.html.erb +53 -0
  41. data/app/views/reviewkit/review_threads/_bucket_frame.html.erb +13 -0
  42. data/app/views/reviewkit/review_threads/_bucket_row.html.erb +55 -0
  43. data/app/views/reviewkit/review_threads/_edit_form.html.erb +29 -0
  44. data/app/views/reviewkit/review_threads/_thread.html.erb +87 -0
  45. data/app/views/reviewkit/reviews/_document.html.erb +41 -0
  46. data/app/views/reviewkit/reviews/_document_split.html.erb +73 -0
  47. data/app/views/reviewkit/reviews/_document_unified.html.erb +57 -0
  48. data/app/views/reviewkit/reviews/_edit_form.html.erb +35 -0
  49. data/app/views/reviewkit/reviews/_index_content.html.erb +160 -0
  50. data/app/views/reviewkit/reviews/_review_sidebar.html.erb +70 -0
  51. data/app/views/reviewkit/reviews/_show_content.html.erb +164 -0
  52. data/app/views/reviewkit/reviews/index.html.erb +11 -0
  53. data/app/views/reviewkit/reviews/show.html.erb +11 -0
  54. data/app/views/reviewkit/shared/_flash.html.erb +10 -0
  55. data/bin/console +4 -0
  56. data/bin/lint +4 -0
  57. data/bin/rails +14 -0
  58. data/bin/setup +9 -0
  59. data/bin/test +4 -0
  60. data/config/importmap.rb +6 -0
  61. data/config/routes.rb +24 -0
  62. data/db/migrate/20260331181500_create_reviewkit_reviews.rb +19 -0
  63. data/db/migrate/20260331181600_create_reviewkit_documents.rb +23 -0
  64. data/db/migrate/20260331181700_create_reviewkit_review_threads.rb +23 -0
  65. data/db/migrate/20260331181800_create_reviewkit_comments.rb +15 -0
  66. data/db/migrate/20260401093000_add_description_to_reviewkit_reviews.rb +7 -0
  67. data/lib/generators/reviewkit/controllers/controllers_generator.rb +24 -0
  68. data/lib/generators/reviewkit/controllers/templates/comments_controller_extension.rb +13 -0
  69. data/lib/generators/reviewkit/controllers/templates/review_threads_controller_extension.rb +13 -0
  70. data/lib/generators/reviewkit/controllers/templates/reviews_controller_extension.rb +19 -0
  71. data/lib/generators/reviewkit/install/install_generator.rb +52 -0
  72. data/lib/generators/reviewkit/install/templates/importmap.rb +3 -0
  73. data/lib/generators/reviewkit/install/templates/reviewkit.rb +19 -0
  74. data/lib/generators/reviewkit/models/models_generator.rb +24 -0
  75. data/lib/generators/reviewkit/models/templates/comment_extension.rb +21 -0
  76. data/lib/generators/reviewkit/models/templates/review_extension.rb +22 -0
  77. data/lib/generators/reviewkit/models/templates/review_thread_extension.rb +21 -0
  78. data/lib/generators/reviewkit/views/views_generator.rb +15 -0
  79. data/lib/reviewkit/configuration.rb +33 -0
  80. data/lib/reviewkit/engine.rb +67 -0
  81. data/lib/reviewkit/version.rb +5 -0
  82. data/lib/reviewkit.rb +26 -0
  83. data/lib/tasks/reviewkit_tasks.rake +12 -0
  84. data/sig/reviewkit.rbs +129 -0
  85. metadata +238 -0
@@ -0,0 +1,164 @@
1
+ <main class="<%= reviewkit_frame_request? ? "reviewkit-page reviewkit-page--embedded" : "reviewkit-page" %>">
2
+ <% additions = review.documents.sum(&:additions_count) %>
3
+ <% deletions = review.documents.sum(&:deletions_count) %>
4
+ <% frame_id = reviewkit_review_frame_id(review) %>
5
+
6
+ <section class="reviewkit-review-layout" data-controller="reviewkit--file-nav">
7
+ <%= render "reviewkit/reviews/review_sidebar",
8
+ review: review,
9
+ selected_document: @selected_document,
10
+ view_mode: @view_mode,
11
+ frame_id: frame_id %>
12
+
13
+ <div class="reviewkit-review-main">
14
+ <% if reviewkit_frame_request? && (flash[:notice].present? || flash[:alert].present?) %>
15
+ <%= render "reviewkit/shared/flash" %>
16
+ <% end %>
17
+
18
+ <header class="reviewkit-review-header">
19
+ <div class="reviewkit-review-header__top">
20
+ <% if @editing_review %>
21
+ <%= render "reviewkit/reviews/edit_form",
22
+ review: review,
23
+ frame_id: frame_id,
24
+ selected_document: @selected_document,
25
+ view_mode: @view_mode %>
26
+ <% else %>
27
+ <div class="space-y-2">
28
+ <div class="flex flex-wrap items-center gap-2">
29
+ <h1 class="reviewkit-review-title"><%= review.title %></h1>
30
+ <span class="<%= reviewkit_status_pill_class(review) %>"><%= review.status %></span>
31
+ </div>
32
+
33
+ <% if review.description.present? %>
34
+ <p class="reviewkit-review-description"><%= review.description %></p>
35
+ <% end %>
36
+ </div>
37
+ <% end %>
38
+
39
+ <div class="reviewkit-review-actions">
40
+ <% unless @editing_review %>
41
+ <% approval_blocked = review.open_threads_count.positive? %>
42
+
43
+ <% unless review.approved? %>
44
+ <%= button_to "Approve",
45
+ approve_review_path(review, document_id: @selected_document&.id, view: @view_mode),
46
+ method: :patch,
47
+ class: "reviewkit-button reviewkit-button--success",
48
+ disabled: approval_blocked,
49
+ title: ("Resolve or mark open threads outdated before approving." if approval_blocked),
50
+ form: { data: { turbo_frame: frame_id } } %>
51
+ <% end %>
52
+
53
+ <% unless review.rejected? %>
54
+ <%= button_to "Reject",
55
+ reject_review_path(review, document_id: @selected_document&.id, view: @view_mode),
56
+ method: :patch,
57
+ class: "reviewkit-button reviewkit-button--danger",
58
+ form: { data: { turbo_frame: frame_id } } %>
59
+ <% end %>
60
+
61
+ <div class="reviewkit-inline-actions" aria-label="Review actions">
62
+ <%= link_to edit_review_path(review, document_id: @selected_document&.id, view: @view_mode),
63
+ class: "reviewkit-inline-icon-button",
64
+ data: { turbo_frame: frame_id },
65
+ aria: { label: "Edit review" },
66
+ title: "Edit review" do %>
67
+ <%= reviewkit_pencil_icon %>
68
+ <% end %>
69
+
70
+ <%= link_to review_path(review),
71
+ class: "reviewkit-inline-icon-button reviewkit-inline-icon-button--danger",
72
+ data: {
73
+ turbo_confirm: "Delete this review?",
74
+ turbo_frame: "_top",
75
+ turbo_method: :delete
76
+ },
77
+ aria: { label: "Delete review" },
78
+ title: "Delete review" do %>
79
+ <%= reviewkit_trash_icon %>
80
+ <% end %>
81
+ </div>
82
+ <% end %>
83
+ </div>
84
+ </div>
85
+
86
+ <div class="reviewkit-review-meta">
87
+ <% if review.external_reference.present? %>
88
+ <span class="reviewkit-review-meta-item">
89
+ <span class="font-semibold text-slate-700">Reference:</span> <%= review.external_reference %>
90
+ </span>
91
+ <% end %>
92
+ <% if review.creator.present? %>
93
+ <span class="reviewkit-review-meta-item">
94
+ <span class="font-semibold text-slate-700">Author:</span> <%= reviewkit_actor_label(review.creator) %>
95
+ </span>
96
+ <% end %>
97
+ <span class="reviewkit-review-meta-item"><%= pluralize(review.documents.size, "file") %> changed</span>
98
+ <span class="reviewkit-review-meta-item"><%= pluralize(review.open_threads_count, "open thread") %></span>
99
+ <span class="reviewkit-review-meta-item"><%= pluralize(review.resolved_threads_count, "resolved thread") %></span>
100
+ <span class="reviewkit-review-meta-item">Updated <%= time_ago_in_words(review.updated_at) %> ago</span>
101
+ </div>
102
+ </header>
103
+
104
+ <div class="reviewkit-review-mobile-summary xl:hidden">
105
+ <span><%= pluralize(review.documents.size, "file") %></span>
106
+ <span><%= pluralize(review.open_threads_count, "open thread") %></span>
107
+ <span><%= pluralize(review.resolved_threads_count, "resolved thread") %></span>
108
+ <% if review.external_reference.present? %>
109
+ <span><%= review.external_reference %></span>
110
+ <% end %>
111
+ </div>
112
+
113
+ <% if review.metadata.present? %>
114
+ <details class="reviewkit-sidebar-details xl:hidden">
115
+ <summary>Review metadata</summary>
116
+ <pre class="reviewkit-code-panel"><%= JSON.pretty_generate(review.metadata) %></pre>
117
+ </details>
118
+ <% end %>
119
+
120
+ <div class="reviewkit-files-header">
121
+ <div class="flex items-center gap-2">
122
+ <h2 class="reviewkit-files-title">Files changed</h2>
123
+ <span class="reviewkit-files-count"><%= review.documents.size %></span>
124
+ </div>
125
+
126
+ <div class="reviewkit-files-header__controls">
127
+ <div class="reviewkit-files-diffstat" aria-label="<%= "#{additions} additions and #{deletions} deletions" %>">
128
+ <span class="font-medium text-emerald-700">+<%= additions %></span>
129
+ <span class="font-medium text-rose-700">-<%= deletions %></span>
130
+ </div>
131
+
132
+ <div class="reviewkit-view-toggle" role="group" aria-label="Diff layout">
133
+ <%= link_to "Split",
134
+ review_path(review, document_id: @selected_document&.id, view: "split"),
135
+ class: [ "reviewkit-view-toggle__button", ("reviewkit-view-toggle__button--active" if @view_mode == "split") ].compact.join(" "),
136
+ data: { turbo_frame: frame_id } %>
137
+ <%= link_to "Unified",
138
+ review_path(review, document_id: @selected_document&.id, view: "unified"),
139
+ class: [ "reviewkit-view-toggle__button", ("reviewkit-view-toggle__button--active" if @view_mode == "unified") ].compact.join(" "),
140
+ data: { turbo_frame: frame_id } %>
141
+ </div>
142
+ </div>
143
+ </div>
144
+
145
+ <section class="reviewkit-files-list">
146
+ <% if @selected_document.present? %>
147
+ <%= render "reviewkit/reviews/document",
148
+ document: @selected_document,
149
+ frame_id: frame_id,
150
+ open_thread_line_code: @open_thread_line_code,
151
+ open_thread_side: @open_thread_side,
152
+ review: review,
153
+ view_mode: @view_mode,
154
+ thread_index: thread_index %>
155
+ <% else %>
156
+ <div class="reviewkit-panel reviewkit-empty-state">
157
+ <p class="reviewkit-kicker">No files available</p>
158
+ <h2 class="text-2xl font-semibold tracking-tight text-slate-950">This review does not have any documents yet.</h2>
159
+ </div>
160
+ <% end %>
161
+ </section>
162
+ </div>
163
+ </section>
164
+ </main>
@@ -0,0 +1,11 @@
1
+ <% unless reviewkit_frame_request? %>
2
+ <% content_for :head do %>
3
+ <%= reviewkit_page_assets %>
4
+ <% end %>
5
+
6
+ <%= reviewkit_page_module_tag %>
7
+ <% end %>
8
+
9
+ <%= reviewkit_wrap_in_frame do %>
10
+ <%= render "reviewkit/reviews/index_content", reviews: @reviews %>
11
+ <% end %>
@@ -0,0 +1,11 @@
1
+ <% unless reviewkit_frame_request? %>
2
+ <% content_for :head do %>
3
+ <%= reviewkit_page_assets %>
4
+ <% end %>
5
+
6
+ <%= reviewkit_page_module_tag %>
7
+ <% end %>
8
+
9
+ <%= turbo_frame_tag reviewkit_review_frame_id(@review) do %>
10
+ <%= render "reviewkit/reviews/show_content", review: @review, thread_index: @thread_index %>
11
+ <% end %>
@@ -0,0 +1,10 @@
1
+ <% flash.each do |type, message| %>
2
+ <% next if message.blank? %>
3
+
4
+ <div class="<%= reviewkit_flash_shell_class %>" data-reviewkit-flash-type="<%= type %>">
5
+ <div class="<%= reviewkit_flash_class(type) %>" role="<%= reviewkit_flash_role(type) %>">
6
+ <span class="reviewkit-flash__label"><%= reviewkit_flash_label(type) %></span>
7
+ <span><%= message %></span>
8
+ </div>
9
+ </div>
10
+ <% end %>
data/bin/console ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ bundle exec rails console
data/bin/lint ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ bundle exec rubocop "$@"
data/bin/rails ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails gems
3
+ # installed from the root of your application.
4
+
5
+ ENGINE_ROOT = File.expand_path("..", __dir__)
6
+ ENGINE_PATH = File.expand_path("../lib/reviewkit/engine", __dir__)
7
+ APP_PATH = File.expand_path("../spec/dummy/config/application", __dir__)
8
+
9
+ # Set up gems listed in the Gemfile.
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
11
+ require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
12
+
13
+ require "rails/all"
14
+ require "rails/engine/commands"
data/bin/setup ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ bundle install
5
+ bundle exec rake reviewkit:build_assets
6
+ bundle exec bin/rails app:db:prepare
7
+
8
+ echo
9
+ echo "Reviewkit is ready."
data/bin/test ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ bundle exec rspec "$@"
@@ -0,0 +1,6 @@
1
+ pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
2
+ pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
3
+
4
+ pin "reviewkit/application", to: "reviewkit/application.js", preload: true
5
+ pin "reviewkit/controllers/file_nav_controller", to: "reviewkit/controllers/file_nav_controller.js"
6
+ pin "reviewkit/controllers/review_index_controller", to: "reviewkit/controllers/review_index_controller.js"
data/config/routes.rb ADDED
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ Reviewkit::Engine.routes.draw do
4
+ root to: "reviews#index"
5
+
6
+ resources :reviews, only: %i[index show edit update destroy] do
7
+ member do
8
+ patch :approve
9
+ patch :reject
10
+ end
11
+
12
+ resources :review_threads, only: :create, path: "threads"
13
+ end
14
+
15
+ resources :review_threads, only: %i[show edit update destroy] do
16
+ member do
17
+ patch :mark_outdated
18
+ patch :resolve
19
+ patch :reopen
20
+ end
21
+
22
+ resources :comments, only: %i[create show edit update destroy]
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateReviewkitReviews < ActiveRecord::Migration[8.1]
4
+ def change
5
+ create_table :reviewkit_reviews do |t|
6
+ t.string :title, null: false
7
+ t.string :status, null: false, default: "draft"
8
+ t.references :reviewable, polymorphic: true, null: true
9
+ t.string :external_reference
10
+ t.references :creator, polymorphic: true, null: true
11
+ t.json :metadata, null: false, default: {}
12
+
13
+ t.timestamps
14
+ end
15
+
16
+ add_index :reviewkit_reviews, :status
17
+ add_index :reviewkit_reviews, :external_reference
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateReviewkitDocuments < ActiveRecord::Migration[8.1]
4
+ def change
5
+ create_table :reviewkit_documents do |t|
6
+ t.references :review, null: false, foreign_key: { to_table: :reviewkit_reviews }
7
+ t.string :path, null: false
8
+ t.string :language, null: false, default: "plaintext"
9
+ t.string :status, null: false, default: "modified"
10
+ t.integer :position, null: false, default: 0
11
+ t.text :old_content
12
+ t.text :new_content
13
+ t.json :diff_cache, null: false, default: {}
14
+ t.json :metadata, null: false, default: {}
15
+
16
+ t.timestamps
17
+ end
18
+
19
+ add_index :reviewkit_documents, %i[review_id position]
20
+ add_index :reviewkit_documents, %i[review_id path], unique: true
21
+ add_index :reviewkit_documents, :status
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateReviewkitReviewThreads < ActiveRecord::Migration[8.1]
4
+ def change
5
+ create_table :reviewkit_review_threads do |t|
6
+ t.references :review, null: false, foreign_key: { to_table: :reviewkit_reviews }
7
+ t.references :document, null: false, foreign_key: { to_table: :reviewkit_documents }
8
+ t.string :status, null: false, default: "open"
9
+ t.string :side, null: false, default: "new"
10
+ t.integer :old_line
11
+ t.integer :new_line
12
+ t.string :line_code, null: false
13
+ t.datetime :resolved_at
14
+ t.references :resolved_by, polymorphic: true, null: true
15
+ t.json :metadata, null: false, default: {}
16
+
17
+ t.timestamps
18
+ end
19
+
20
+ add_index :reviewkit_review_threads, %i[document_id line_code]
21
+ add_index :reviewkit_review_threads, :status
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateReviewkitComments < ActiveRecord::Migration[8.1]
4
+ def change
5
+ create_table :reviewkit_comments do |t|
6
+ t.references :review_thread, null: false, foreign_key: { to_table: :reviewkit_review_threads }
7
+ t.references :author, polymorphic: true, null: true
8
+ t.text :body, null: false
9
+ t.datetime :edited_at
10
+ t.json :metadata, null: false, default: {}
11
+
12
+ t.timestamps
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddDescriptionToReviewkitReviews < ActiveRecord::Migration[8.1]
4
+ def change
5
+ add_column :reviewkit_reviews, :description, :text
6
+ end
7
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Reviewkit
6
+ module Generators
7
+ class ControllersGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ def copy_controller_extensions
11
+ template "reviews_controller_extension.rb", "app/controllers/concerns/reviewkit/reviews_controller_extension.rb"
12
+ template "review_threads_controller_extension.rb", "app/controllers/concerns/reviewkit/review_threads_controller_extension.rb"
13
+ template "comments_controller_extension.rb", "app/controllers/concerns/reviewkit/comments_controller_extension.rb"
14
+ end
15
+
16
+ def print_instructions
17
+ say ""
18
+ say "Reviewkit controller extensions installed.", :green
19
+ say "The engine will automatically prepend these modules on reload."
20
+ say "Use them to extend permitted params, scopes, redirects, and review flow behavior."
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reviewkit
4
+ module CommentsControllerExtension
5
+ protected
6
+
7
+ def permitted_comment_attributes
8
+ super
9
+ # Example:
10
+ # super + %i[comment_type]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reviewkit
4
+ module ReviewThreadsControllerExtension
5
+ protected
6
+
7
+ def permitted_review_thread_attributes
8
+ super
9
+ # Example:
10
+ # super + %i[source_revision_id]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reviewkit
4
+ module ReviewsControllerExtension
5
+ protected
6
+
7
+ def permitted_review_attributes
8
+ super
9
+ # Example:
10
+ # super + %i[review_type]
11
+ end
12
+
13
+ def review_transition_failure_message(review)
14
+ super
15
+ # Example:
16
+ # "#{super} Resolve content approval blockers before marking this review approved."
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Reviewkit
6
+ module Generators
7
+ class InstallGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ class_option :mount,
11
+ type: :boolean,
12
+ default: true,
13
+ desc: "Mount Reviewkit::Engine into the host app routes."
14
+ class_option :mount_path,
15
+ type: :string,
16
+ default: "/reviewkit",
17
+ desc: "Mount path to use when inserting the engine route."
18
+
19
+ def copy_initializer
20
+ template "reviewkit.rb", "config/initializers/reviewkit.rb"
21
+ end
22
+
23
+ def ensure_importmap
24
+ return if File.exist?(File.join(destination_root, "config", "importmap.rb"))
25
+
26
+ template "importmap.rb", "config/importmap.rb"
27
+ end
28
+
29
+ def mount_engine
30
+ return unless options[:mount]
31
+
32
+ route %(mount Reviewkit::Engine => "#{options[:mount_path]}")
33
+ end
34
+
35
+ def install_migrations
36
+ rake "reviewkit:install:migrations"
37
+ end
38
+
39
+ def print_instructions
40
+ say ""
41
+ say "Reviewkit installed.", :green
42
+ say "Next steps:"
43
+ say " 1. Run bundle exec rails db:migrate"
44
+ say " 2. Visit #{options[:mount_path]} once you create review data"
45
+ say " 3. Keep javascript_importmap_tags in your layout or use reviewkit_assets for a custom Reviewkit layout"
46
+ say " 4. Optionally run rails g reviewkit:views to copy the shipped UI into the host app"
47
+ say " 5. Optionally run rails g reviewkit:models for host-side validations, scopes, and callbacks"
48
+ say " 6. Optionally run rails g reviewkit:controllers to extend permitted params and controller flow"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,3 @@
1
+ # Reviewkit relies on importmap-rails for its Rails-only Hotwire entrypoint.
2
+ # Engine-owned pins are registered automatically; this file exists so Rails can
3
+ # build the host application's import map.
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ Reviewkit.configure do |config|
4
+ config.current_actor = lambda do |controller|
5
+ controller.respond_to?(:current_user, true) ? controller.send(:current_user) : nil
6
+ end
7
+
8
+ config.authorize_action = lambda do |_controller, _action, _record = nil, **_context|
9
+ true
10
+ end
11
+
12
+ config.layout = "reviewkit/application"
13
+
14
+ # Keep intraline highlighting conservative by default so large reviews
15
+ # fall back to line-level diffs before they become expensive.
16
+ config.intraline_limits.max_review_files = 50
17
+ config.intraline_limits.max_changed_lines = 50
18
+ config.intraline_limits.max_line_length = 500
19
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Reviewkit
6
+ module Generators
7
+ class ModelsGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ def copy_model_extensions
11
+ template "review_extension.rb", "app/models/concerns/reviewkit/review_extension.rb"
12
+ template "review_thread_extension.rb", "app/models/concerns/reviewkit/review_thread_extension.rb"
13
+ template "comment_extension.rb", "app/models/concerns/reviewkit/comment_extension.rb"
14
+ end
15
+
16
+ def print_instructions
17
+ say ""
18
+ say "Reviewkit model extensions installed.", :green
19
+ say "The engine will automatically include these concerns on reload."
20
+ say "Use them to add validations, scopes, and standard Active Record callbacks."
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reviewkit
4
+ module CommentExtension
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ # Example:
9
+ #
10
+ # before_validation :normalize_comment_source
11
+ # after_update :track_comment_edits, if: :saved_change_to_body?
12
+ end
13
+
14
+ private
15
+
16
+ def normalize_comment_source
17
+ # Example:
18
+ # metadata["source"] ||= "host_app"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reviewkit
4
+ module ReviewExtension
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ # Example:
9
+ #
10
+ # validates :review_type, presence: true
11
+ #
12
+ # after_update :notify_host_workflow, if: :saved_change_to_status?
13
+ end
14
+
15
+ private
16
+
17
+ def notify_host_workflow
18
+ # Example:
19
+ # HostReviewSyncJob.perform_later(id) if approved?
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reviewkit
4
+ module ReviewThreadExtension
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ # Example:
9
+ #
10
+ # validate :ensure_thread_context_is_present
11
+ # after_update :notify_thread_status_change, if: :saved_change_to_status?
12
+ end
13
+
14
+ private
15
+
16
+ def ensure_thread_context_is_present
17
+ # Example:
18
+ # errors.add(:metadata, "must include a resource_id") if metadata["resource_id"].blank?
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Reviewkit
6
+ module Generators
7
+ class ViewsGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("../../../../", __dir__)
9
+
10
+ def copy_views
11
+ directory "app/views/reviewkit", "app/views/reviewkit"
12
+ end
13
+ end
14
+ end
15
+ end