Skip to content

Conversation

@lodewiges
Copy link
Contributor

@lodewiges lodewiges commented Oct 28, 2025

work in progress

Remove the old way of serving javascript through rails and replaced it with a rails 7 alternative

TO DO

  • get icons working
  • get js working
  • migrate all templates to axios
  • stop using import if possible
  • linting errors
  • migrate to stylelint with scss plugin
  • get order screen working
  • get login working

Summary by CodeRabbit

  • New Features

    • Turbo navigation enabled; new Vue-powered widgets for users, invoices, payments, credit mutations, activities, and order screens with proper teardown.
  • Chores

    • Modernized asset/build pipeline and scripts, unified builds output, added development launcher and Procfile for running web/JS/CSS watchers.
  • Documentation

    • README updated to reference Turbo and small wording fixes.
  • UI

    • Font Awesome icons replaced with inline classes for consistent rendering.

@coderabbitai
Copy link

coderabbitai bot commented Oct 28, 2025

Walkthrough

Migrates front-end from Webpacker/Turbolinks to a Rails 7+ bundling setup: adds webpack.config.js, bin/dev, Procfile.dev, npm scripts and Sass/FontAwesome, replaces Turbolinks with Turbo, moves Vue initializers to app/javascript turbo-driven modules, updates manifests/views, adjusts Babel/Gem/package deps, and removes Webpacker configs/generator/bin scripts. (50 words)

Changes

Cohort / File(s) Summary
Babel & npm build tooling
\.babelrc, babel.config.js, package.json, webpack.config.js
.babelrc updated (useBuiltIns→"usage", corejs:3, babel-plugin-macros, transform-runtime); legacy babel.config.js deleted; package.json updated for Webpack 5, @hotwired/turbo-rails, FontAwesome, sass, @babel/runtime, new scripts (build/watch/build:css); added webpack.config.js exporting a Webpack 5 config targeting app/assets/builds.
Webpacker removal
config/webpack/*, config/webpacker.yml, bin/webpack*, lib/generators/rails/webpacker_assets/*
Removed Webpacker environment, loaders, per-env webpack files, webpacker.yml, bin/webpack and bin/webpack-dev-server; deleted generator and its USAGE doc.
App JS reorganization (packs → modules)
Deleted: app/javascript/packs/* (many)
Added/Updated: app/javascript/*.js (e.g. application.js, activities.js, activity.js, credit_mutations.js, invoices.js, order_screen.js, payment_add.js, price_lists.js, user.js, users.js, etc.)
Removed Webpacker packs and introduced turbo-driven entry modules under app/javascript — each listens to turbo:load, mounts Vue components, and tears down on turbo:before-cache. application.js centralizes Turbo, Vue, axios, CSRF and WebFontLoader setup.
Asset pipeline & styles
app/assets/config/manifest.js, app/assets/stylesheets/*, config/initializers/assets.rb, .gitignore
Manifest switched to link_tree ../builds; SCSS imports now use npm packages (bootstrap/scss, @fortawesome/fontawesome-free); assets initializer points to app/assets/builds; .gitignore ignores app/assets/builds/* but keeps .keep.
Views & layout updates
app/views/layouts/application.html.erb, app/views/layouts/errors.html.erb, many app/views/* (e.g. app/views/activities/*, app/views/invoices/index.html.erb, app/views/users/*, app/views/price_lists/index.html.erb, etc.)
Added responsive meta, CSRF/CSP meta tags, yield(:head) and per-page content_for :head JS includes; replaced Turbolinks tag attrs with data-turbo-track+defer; replaced fa_icon helpers with inline <i> tags across templates.
Sprockets / legacy manifests removed
app/assets/javascripts/application.js
Removed Sprockets JS manifest and its Turbolinks/WebFont/TurbolinksAnimate initialization.
Development tooling / Procfile
Procfile.dev, bin/dev
Added Procfile.dev (web/js/css processes) and bin/dev launcher that ensures foreman and runs Procfile.dev.
Webpack config files removed
config/webpack/environment.js, config/webpack/loaders/*, config/webpack/*.js (development.js, production.js, staging.js, test.js, luxproduction.js)
Deleted webpack environment, Vue loader, eslint loader, and per-environment config files and their exports.
Rails env tweaks & other
config/environments/*.rb, config/puma.rb, config/initializers/assets.rb, Gemfile
Removed webpacker-specific settings (check_yarn_integrity, sass inline maps), fixed caching-dev path check, aligned puma port comment/default, updated Gemfile to add css/js bundling gems and replace older asset gems.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Browser
    participant Rails as Rails (layout)
    participant AppJS as application.js
    participant PageJS as Page module
    participant Vue as Vue instance

    Browser->>Rails: GET page
    Rails->>Browser: HTML (head includes + application.js defer + per-page script)
    Browser->>AppJS: execute deferred `application.js`
    AppJS->>AppJS: init Turbo, WebFont, axios, CSRF headers
    Note right of AppJS: global setup (fonts, headers)
    Browser->>PageJS: load per-page JS (deferred)
    PageJS->>PageJS: subscribe to `turbo:load`
    AppJS->>PageJS: `turbo:load` fired (navigation complete)
    PageJS->>PageJS: read element.dataset, prepare props
    PageJS->>Vue: mount component
    Vue->>Browser: DOM interactions & user events
    Browser->>PageJS: navigation away (caching)
    PageJS->>Vue: destroy on `turbo:before-cache`
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Focus areas:

  • webpack.config.js entry discovery, loaders, output path and chunk limits
  • .babelrc changes and deletion of babel.config.js (core-js + transform-runtime implications)
  • package.json dependency/script changes for Webpack v5 and new build flow
  • Migration of Vue initializers from Webpacker packs to turbo-driven modules (lifecycle, CSRF wiring, teardown)
  • View/layout yield(:head) usage and manifest.jslink_tree to ensure built assets are served

Possibly related PRs

Poem

🐇 I hopped from packs into builds tonight,

Turbo chimed in and Turbolinks took flight.
Vue wakes on turbo:load, fonts hum a tune,
Webpack packs carrots to app/assets/builds by noon.
Tiny paws, big bundles — frontend springtime soon! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description is largely incomplete and does not follow the required template structure. The description is marked "work in progress" and provides minimal actual change summary (only 2 lines), missing the required Checklist section with database migration validation steps, the Summary section with detailed change description and UI/screenshot information, and proper Other information formatting. Instead, the author has provided a TODO list indicating the work is unfinished and still requires icon fixes, JS functionality verification, template migration to axios, linting fixes, and stylelint configuration before the PR is ready for review. The author should complete the PR by updating the description to follow the template structure: (1) add the completed Checklist with database migration testing confirmation, (2) provide a comprehensive Summary section describing all major changes, any UI impacts, and reference any related issues using the "fixes #xyz" format, and (3) add an Other information section noting any new conventions or dependencies introduced. Additionally, the TODO items should be addressed or the PR should be converted to a draft if it remains work-in-progress and not ready for merge.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "Update/migrate webpacker to js bundeling" directly reflects the primary change across the entire changeset, which involves systematically replacing the Webpacker asset pipeline with a Rails 7 JS bundling approach. The title accurately summarizes the main objective evident in the changes: removing Webpacker configuration files, adding webpack.config.js, replacing Turbolinks with Turbo, updating dependencies in package.json, and migrating JavaScript modules from the packs/ directory structure to app/javascript/. The title is concise and clear enough for a developer scanning the repository history to understand the core transformation, though it contains a minor spelling error ("bundeling" instead of "bundling").
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch update/migrateWebpackerToJSBundeling

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d710d6 and 4e44d0c.

📒 Files selected for processing (3)
  • app/assets/stylesheets/application.scss (1 hunks)
  • app/javascript/application.js (1 hunks)
  • config/initializers/assets.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/javascript/application.js
  • app/assets/stylesheets/application.scss
  • config/initializers/assets.rb
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
config/environments/development.rb (1)

22-22: Bug: wrong caching toggle path prevents dev caching switch.
Should be tmp/caching-dev.txt (single segment), not nested twice.

-  if Rails.root.join('tmp', 'caching-dev.txt', 'caching-dev.txt').exist?
+  if Rails.root.join('tmp', 'caching-dev.txt').exist?
app/javascript/order_screen.js (1)

214-218: Fix typo in variable name.

Line 215: affilateKey should be affiliateKey.

         startSumupPayment(orderId, orderTotal) {
-          let affilateKey = element.dataset.sumupKey;
+          let affiliateKey = element.dataset.sumupKey;
           let callback = element.dataset.sumupCallback;
           
-          let sumupUrl = `sumupmerchant://pay/1.0?affiliate-key=${affilateKey}&currency=EUR&title=Bestelling ${element.dataset.siteName}&skip-screen-success=true&foreign-tx-id=${orderId}`;
+          let sumupUrl = `sumupmerchant://pay/1.0?affiliate-key=${affiliateKey}&currency=EUR&title=Bestelling ${element.dataset.siteName}&skip-screen-success=true&foreign-tx-id=${orderId}`;
🧹 Nitpick comments (16)
.gitignore (2)

46-51: Deduplicate ignore entries.
The entries for /public/packs, /public/packs-test, /node_modules, yarn logs, and .yarn-integrity appear twice. Remove the second block to reduce churn.

- /public/packs
- /public/packs-test
- /node_modules
- /yarn-error.log
- yarn-debug.log*
- .yarn-integrity

57-57: Scoped ignore for gemini.md.
If this is a local notes file, consider moving it under /docs or naming it more specifically (e.g., /docs/gemini.md) to avoid accidental clashes.

README.md (2)

8-8: Grammar tweak: “built-in”.
Change “builtin” → “built-in”. Short and clearer. Based on static analysis hints.

-The source code belonging to Alpha SOFIA. It is a system built with Ruby on Rails with Turbo and a little VueJS, used to manage orders in our own bar "Flux". Users authenticate via OAuth API (currently "Alpha AMBER") or through the builtin login portal to see how much credit they got left, or to be able to register new orders and/or payments.
+The source code belonging to Alpha SOFIA. It is a system built with Ruby on Rails with Turbo and a little VueJS, used to manage orders in our own bar "Flux". Users authenticate via OAuth API (currently "Alpha AMBER") or through the built-in login portal to see how much credit they have left, or to register new orders and/or payments.

24-31: Update dev run steps for jsbundling (watcher).
With jsbundling/webpack, add either bin/dev (Procfile.dev/foreman) or a parallel yarn build --watch step; otherwise JS won’t rebuild during development.

Example (if bin/dev exists):

  • Run: bin/dev
    Or add:
  • In one terminal: yarn build --watch
  • In another: bundle exec rails s -b 0.0.0.0 -p 5000
app/javascript/activity.js (2)

15-19: Import Bootstrap’s Tab module instead of relying on a global.
Avoids runtime no-undef and trims bundle.

+import Tab from 'bootstrap/js/dist/tab';
@@
-      /* eslint-disable no-undef */
-      const firstTab = new bootstrap.Tab(firstTabEl);
+      const firstTab = new Tab(firstTabEl);
       firstTab.show();

5-13: Tear down Vue on Turbo cache to prevent memory leaks.
Destroy the instance on turbo:before-cache; otherwise observers may linger.

 document.addEventListener('turbo:load', () => {
   const element = document.getElementById('activity');
   if (element) {
-    new Vue({
+    const app = new Vue({
       el: element,
       components: {
         ProductTotals
       }
     });
+
+    document.addEventListener('turbo:before-cache', () => {
+      app.$destroy();
+    }, { once: true });
.babelrc (1)

18-34: Improve test transpilation reliability.
Explicitly set modules: "commonjs" in env.test to help Node/Jest.

   "env": {
     "test": {
-      "presets": [
+      "presets": [
         [
           "@babel/preset-env",
           {
-            "targets": {
-              "node": "current"
-            }
+            "targets": { "node": "current" },
+            "modules": "commonjs"
           }
         ]
       ],
app/javascript/application.js (1)

23-27: Consider moving WebFont loading outside the turbo:load listener.

The WebFont.load() call will execute on every Turbo navigation. Since fonts only need to be loaded once per session, consider moving this outside the event listener or wrapping it with a guard to prevent redundant font loading requests.

Apply this diff to optimize font loading:

+WebFont.load({
+  google: {
+    families: ['Roboto:300,400,500']
+  }
+});
+
 document.addEventListener('turbo:load', () => {
   const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
 
   if (csrfToken) {
     Vue.http.headers.common['X-CSRF-TOKEN'] = csrfToken;
     axios.defaults.headers.common['X-CSRF-Token'] = csrfToken;
   }
-
-  /* eslint-disable no-undef */
-  WebFont.load({
-  google: {
-      families: ['Roboto:300,400,500']
-  }
-  });
-  /* eslint-enable no-undef */
 });
app/javascript/price_lists.js (1)

40-48: Inconsistent return value from newProduct method.

The method returns this.products.push(newProduct), which returns the new array length (a number) rather than the product or array. This is inconsistent with the pattern in other methods and may not be the intended behavior.

Consider one of these approaches:

Option 1: Return the new product

 newProduct () {
   const newProduct = {
     name: '',
     category: 0,
     editing: true,
     product_prices: [],
   };
-  return this.products.push(newProduct);
+  this.products.push(newProduct);
+  return newProduct;
 },

Option 2: Return nothing (if unused)

 newProduct () {
   const newProduct = {
     name: '',
     category: 0,
     editing: true,
     product_prices: [],
   };
-  return this.products.push(newProduct);
+  this.products.push(newProduct);
 },
app/javascript/invoices.js (1)

46-70: Consider using element-specific DOM methods for more robust row cloning.

The current implementation uses lastChild and childNodes, which include text nodes and comments. This could lead to unexpected behavior if the HTML structure changes.

Apply this diff for more robust element handling:

 addRow() {
   const invoiceRowContainer = document.getElementById('invoice_row');
-  const lastRow = invoiceRowContainer.lastChild;
+  const lastRow = invoiceRowContainer.lastElementChild;
   const newRow = lastRow.cloneNode(true);

   invoiceRowContainer.appendChild(newRow);
-  newRow.childNodes.forEach((fieldWrapper) => {
+  Array.from(newRow.children).forEach((fieldWrapper) => {
     let newIndex = -1;
-    if (fieldWrapper.nodeType === Node.ELEMENT_NODE && fieldWrapper.firstChild) {
-      const nameAttr = fieldWrapper.firstChild.name;
+    if (fieldWrapper.firstElementChild) {
+      const nameAttr = fieldWrapper.firstElementChild.name;
       if (nameAttr) {
         if (newIndex === -1) {
           const match = nameAttr.match(/invoice\[rows_attributes\]\[(\d+)\]/);
           if (match && match[1]) {
             newIndex = Number(match[1]) + 1;
           }
         }
         if (newIndex !== -1) {
-          fieldWrapper.firstChild.name = nameAttr.replace(/\[\d+\]/g, '[' + newIndex + ']');
-          fieldWrapper.firstChild.value = '';
+          fieldWrapper.firstElementChild.name = nameAttr.replace(/\[\d+\]/g, '[' + newIndex + ']');
+          fieldWrapper.firstElementChild.value = '';
         }
       }
     }
   });
 }
webpack.config.js (1)

20-20: Consider environment-aware source map configuration.

Source maps are currently generated for all builds. While helpful for debugging, exposing full source maps in production can leak proprietary code and increase bundle size.

Apply this diff for environment-aware source maps:

-  devtool: "source-map",
+  devtool: process.env.NODE_ENV === "production" ? "hidden-source-map" : "source-map",

This generates source maps in production for error tracking (uploaded to Sentry) but doesn't expose them to end users.

bin/dev (1)

3-6: Consider handling gem installation permissions.

The gem install foreman command may require elevated permissions on some systems. Consider suggesting bundle add foreman --group development in the error message as an alternative.

Optional improvement:

 if gem list --no-installed --exact --silent foreman; then
-  echo "Installing foreman..."
-  gem install foreman
+  echo "Foreman is not installed. Installing..."
+  gem install foreman || {
+    echo "Failed to install foreman globally. Try: bundle add foreman --group development"
+    exit 1
+  }
 fi
app/javascript/users.js (1)

4-23: Consider adding Vue instance cleanup for Turbo caching.

The Vue instance is created on every turbo:load event but is never explicitly destroyed. When Turbo caches pages for back/forward navigation, the old Vue instance may remain in memory, potentially causing memory leaks or duplicate event handlers.

Consider storing a reference to the Vue instance and destroying it before the page is cached:

+let vueInstance = null;
+
+document.addEventListener('turbo:before-cache', () => {
+  if (vueInstance) {
+    vueInstance.$destroy();
+    vueInstance = null;
+  }
+});
+
 document.addEventListener('turbo:load', () => {
   const element = document.getElementById('users-index');
   if (element !== null) {
     const manual_users = JSON.parse(element.dataset.manualUsers);
     const amber_users = JSON.parse(element.dataset.amberUsers);
     const inactive_users = JSON.parse(element.dataset.inactiveUsers);
 
-    new Vue({
+    vueInstance = new Vue({
       el: element,
       data: () => ({
         manual_users,
         amber_users,
         inactive_users
       }),
       components: {
         UsersTable
       },
     });
   }
 });
app/javascript/credit_mutations.js (1)

5-16: Consider adding Vue instance cleanup for Turbo caching.

Similar to other Vue initialization files in this PR, the Vue instance should be destroyed before Turbo caches the page to prevent memory leaks.

Apply the same pattern recommended for app/javascript/users.js:

+let vueInstance = null;
+
+document.addEventListener('turbo:before-cache', () => {
+  if (vueInstance) {
+    vueInstance.$destroy();
+    vueInstance = null;
+  }
+});
+
 document.addEventListener('turbo:load', () => {
-
   const element = document.getElementById('new_mutation_modal');
   if (element != null) {
-    new Vue({
+    vueInstance = new Vue({
       el: element,
       components: {
         UserInput
       }
     });
   }
 });
app/javascript/payment_add.js (1)

19-23: Consider extracting the minimum payment amount to a constant.

The minimum payment value 20 appears in both the default value (line 11) and validation logic (line 21). Extracting it to a constant would improve maintainability.

+const MIN_PAYMENT_AMOUNT = 20;
+
 document.addEventListener('turbo:load', () => {
   const element = document.getElementById('payment-add');
   if (element !== null) {
     new Vue({
       el: element,
       data: () => {
         return {
           currentCredit: parseFloat(element.dataset.userCredit) || 0,
-          paymentAmount: parseFloat(element.dataset.paymentAmount) || 20
+          paymentAmount: parseFloat(element.dataset.paymentAmount) || MIN_PAYMENT_AMOUNT
         };
       },
       computed: {
         newCredit() {
           return (this.currentCredit + this.paymentAmount).toFixed(2);
         }
       },
       methods: {
         amountValid() {
-          return this.paymentAmount >= 20;
+          return this.paymentAmount >= MIN_PAYMENT_AMOUNT;
         }
       },
     });
   }
 });
app/javascript/order_screen.js (1)

321-322: Consider extracting complex validation into a helper method.

The validation logic on lines 321-322 is quite dense and could be more readable if extracted into a separate method or broken into intermediate variables.

       methods: {
+        isFormInvalid() {
+          const formValid = document.getElementById('credit-mutation-modal-form').checkValidity();
+          const hasUser = !!app.selectedUser;
+          const hasAmount = !!this.creditMutationAmount;
+          const hasDescription = !!this.creditMutationDescription;
+          return !formValid || !hasUser || !hasAmount || !hasDescription;
+        },
+
         saveCreditMutation() {
           this.isSubmitting = true;
 
-          this.creditMutationFormInvalid = (!document.getElementById('credit-mutation-modal-form').checkValidity() 
-            || !app.selectedUser || !this.creditMutationAmount || !this.creditMutationDescription);
+          this.creditMutationFormInvalid = this.isFormInvalid();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 04a5d73 and 391300e.

⛔ Files ignored due to path filters (2)
  • Gemfile.lock is excluded by !**/*.lock
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (54)
  • .babelrc (1 hunks)
  • .gitignore (2 hunks)
  • Gemfile (2 hunks)
  • Procfile.dev (1 hunks)
  • README.md (1 hunks)
  • app/assets/config/manifest.js (1 hunks)
  • app/assets/javascripts/application.js (0 hunks)
  • app/assets/stylesheets/application.scss (1 hunks)
  • app/assets/stylesheets/order_screen.scss (1 hunks)
  • app/javascript/activities.js (3 hunks)
  • app/javascript/activity.js (1 hunks)
  • app/javascript/application.js (1 hunks)
  • app/javascript/credit_mutations.js (1 hunks)
  • app/javascript/invoices.js (1 hunks)
  • app/javascript/order_screen.js (2 hunks)
  • app/javascript/packs/activity.js (0 hunks)
  • app/javascript/packs/credit_mutations.js (0 hunks)
  • app/javascript/packs/invoices.js (0 hunks)
  • app/javascript/packs/payment_add.js (0 hunks)
  • app/javascript/packs/user.js (0 hunks)
  • app/javascript/packs/users.js (0 hunks)
  • app/javascript/payment_add.js (1 hunks)
  • app/javascript/price_lists.js (8 hunks)
  • app/javascript/user.js (1 hunks)
  • app/javascript/users.js (1 hunks)
  • app/views/activities/index.html.erb (1 hunks)
  • app/views/activities/order_screen.html.erb (1 hunks)
  • app/views/activities/show.html.erb (1 hunks)
  • app/views/credit_mutations/index.html.erb (1 hunks)
  • app/views/invoices/index.html.erb (1 hunks)
  • app/views/layouts/application.html.erb (1 hunks)
  • app/views/layouts/errors.html.erb (1 hunks)
  • app/views/payments/add.html.erb (1 hunks)
  • app/views/price_lists/index.html.erb (1 hunks)
  • app/views/users/index.html.erb (1 hunks)
  • app/views/users/show.html.erb (1 hunks)
  • babel.config.js (0 hunks)
  • bin/dev (1 hunks)
  • bin/webpack (0 hunks)
  • bin/webpack-dev-server (0 hunks)
  • config/environments/development.rb (1 hunks)
  • config/environments/production.rb (0 hunks)
  • config/webpack/development.js (0 hunks)
  • config/webpack/environment.js (0 hunks)
  • config/webpack/loaders/eslint.js (0 hunks)
  • config/webpack/luxproduction.js (0 hunks)
  • config/webpack/production.js (0 hunks)
  • config/webpack/staging.js (0 hunks)
  • config/webpack/test.js (0 hunks)
  • config/webpacker.yml (0 hunks)
  • lib/generators/rails/webpacker_assets/USAGE (0 hunks)
  • lib/generators/rails/webpacker_assets/webpacker_assets_generator.rb (0 hunks)
  • package.json (2 hunks)
  • webpack.config.js (1 hunks)
💤 Files with no reviewable changes (21)
  • config/webpack/environment.js
  • config/webpack/luxproduction.js
  • app/javascript/packs/credit_mutations.js
  • babel.config.js
  • config/webpacker.yml
  • config/webpack/development.js
  • app/javascript/packs/users.js
  • bin/webpack-dev-server
  • config/webpack/loaders/eslint.js
  • config/webpack/production.js
  • app/javascript/packs/activity.js
  • app/javascript/packs/payment_add.js
  • config/environments/production.rb
  • bin/webpack
  • config/webpack/test.js
  • app/javascript/packs/user.js
  • app/javascript/packs/invoices.js
  • lib/generators/rails/webpacker_assets/webpacker_assets_generator.rb
  • lib/generators/rails/webpacker_assets/USAGE
  • app/assets/javascripts/application.js
  • config/webpack/staging.js
🧰 Additional context used
🧬 Code graph analysis (2)
app/javascript/activities.js (1)
app/javascript/price_lists.js (2)
  • element (4-4)
  • priceLists (6-6)
app/javascript/credit_mutations.js (8)
app/javascript/activity.js (1)
  • element (6-6)
app/javascript/invoices.js (1)
  • element (7-7)
app/javascript/order_screen.js (1)
  • element (8-8)
app/javascript/user.js (1)
  • element (7-7)
app/javascript/users.js (1)
  • element (5-5)
app/javascript/activities.js (1)
  • element (4-4)
app/javascript/payment_add.js (1)
  • element (4-4)
app/javascript/price_lists.js (1)
  • element (4-4)
🪛 LanguageTool
README.md

[grammar] ~8-~8: Ensure spelling is correct
Context: ...currently "Alpha AMBER") or through the builtin login portal to see how much credit the...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (35)
app/views/layouts/errors.html.erb (1)

6-7: LGTM: Turbo tracking + deferred script.
Matches the Rails 7/Turbo setup and keeps error pages consistent.

.gitignore (1)

40-41: All verification checks passed; code changes are correct.

Builds are properly ignored while preserving the .keep file, the Sprockets manifest correctly links the builds directory, and the .keep file is committed.

Gemfile (1)

6-7: LGTM: Modern Rails 7 asset pipeline gems added correctly.

The addition of cssbundling-rails, dartsass-rails, jsbundling-rails, sprockets-rails, and turbo-rails aligns perfectly with the migration from Webpacker to the Rails 7+ asset pipeline and from Turbolinks to Turbo.

Also applies to: 15-15, 40-41

app/javascript/user.js (1)

6-20: LGTM: Correct Turbo-based Vue initialization pattern.

The element existence check and dataset-based initialization follow best practices for Turbo navigation. The structure ensures the Vue instance is only created when the required DOM element is present.

app/javascript/activities.js (1)

3-42: LGTM: Successful migration from Turbolinks to Turbo.

The event listener has been correctly updated to turbo:load, and the element existence guard ensures the Vue instance is only initialized when the modal is present. The data sourcing from element.dataset.priceLists follows the new pattern established across the codebase.

app/views/layouts/application.html.erb (2)

5-13: LGTM: Layout correctly updated for Turbo and Rails 7 asset pipeline.

The changes appropriately modernize the asset loading strategy:

  • Viewport meta tag for responsive design
  • data-turbo-track replacing data-turbolinks-track
  • yield(:head) enabling per-page script injection

10-11: All per-page scripts already use defer: true consistently.

Verification confirms that every javascript_include_tag call across the application—including the main layout and all per-page scripts—already includes the defer: true attribute. The codebase is correctly configured for Turbo initialization and consistent script loading behavior.

app/javascript/price_lists.js (1)

3-7: LGTM: Successful migration to Turbo with code quality improvements.

The migration to turbo:load is correct, and the refactoring from var to const/let modernizes the code and improves maintainability. The use of this.priceLists ensures proper Vue reactivity.

Also applies to: 31-31, 41-41, 59-59, 68-68, 71-71, 128-128

webpack.config.js (1)

9-16: The auto-discovery pattern is well-structured and appropriate.

Verification confirms the architecture intentionally separates entry points (root .js files) from shared code (subdirectories like components/). All 10 discovered files are legitimate page/feature bundles, not utilities or helpers. No changes needed.

package.json (6)

5-8: Build scripts look good for jsbundling-rails migration.

The webpack and sass build scripts are correctly configured for the Rails 7 asset pipeline. The build:css script appropriately outputs to app/assets/builds/ and loads from node_modules.


18-18: Good addition for Turbolinks migration.

Adding @hotwired/turbo-rails is correct for the migration from Turbolinks to Turbo in Rails 7.


46-46: Appropriate addition for Vue component styling.

The vue-style-loader is correctly added as a devDependency for handling Vue component styles.


23-23: Good additions for the build toolchain.

  • babel-plugin-macros enables Babel macro support
  • sass provides Dart Sass for stylesheet compilation via the build:css script

Also applies to: 33-33


17-17: No issues found—version is current and stable.

The specified version 7.1.0 is the latest stable release of @fortawesome/fontawesome-free, and the package.json specification is correct.


39-40: Webpack 5 upgrade verified as compatible.

All dependencies and configurations are compatible with webpack 5:

  • webpack.config.js uses webpack 5 native APIs correctly
  • vue-loader@^15.11.1 supports webpack 5 since v15.11.0 added explicit Webpack 5 compatibility
  • babel-loader@^8.4.1 and .babelrc configuration are webpack 5-compatible
  • webpack-cli@^6.0.1 is the correct version for webpack 5

No critical compatibility issues found.

app/assets/stylesheets/order_screen.scss (1)

1-1: Correct Bootstrap import path for Dart Sass.

The updated import path @import 'bootstrap/scss/bootstrap' is correct for loading Bootstrap from node_modules with the Dart Sass compiler.

bin/dev (2)

8-9: Good default PORT handling.

The PORT default is correctly set using shell parameter expansion.


11-11: Verify the --env /dev/null flag is intentional.

The --env /dev/null flag explicitly prevents foreman from loading a .env file. If your project uses .env files for configuration, this may skip important environment variables.

If you need environment variables from .env, consider:

-exec foreman start -f Procfile.dev --env /dev/null "$@"
+exec foreman start -f Procfile.dev "$@"

Or explicitly specify the .env file:

-exec foreman start -f Procfile.dev --env /dev/null "$@"
+exec foreman start -f Procfile.dev --env .env "$@"
app/views/activities/index.html.erb (1)

2-4: Correct pattern for Turbo-based JavaScript loading.

The content_for :head block with javascript_include_tag properly loads the activities script with Turbo tracking and defer.

app/views/activities/show.html.erb (1)

2-4: Correct pattern for Turbo-based JavaScript loading.

The JavaScript include uses the singular "activity" asset name, which should match the corresponding entrypoint file.

Procfile.dev (2)

2-2: JS watcher correctly configured.

The js process properly runs the webpack watcher with development environment.


3-3: CSS watcher correctly configured.

The css process properly runs the sass watcher for stylesheet compilation.

app/views/payments/add.html.erb (1)

2-4: Correct pattern for Turbo-based JavaScript loading.

The payment_add JavaScript is properly loaded in the head with Turbo tracking.

app/views/invoices/index.html.erb (1)

2-4: Correct pattern for Turbo-based JavaScript loading.

The invoices JavaScript is properly loaded in the head with Turbo tracking.

app/assets/stylesheets/application.scss (1)

1-6: LGTM! Clean migration to npm-based asset imports.

The Bootstrap and Font Awesome imports have been correctly updated to reference npm package paths, which aligns with the move away from Webpacker to the modern Rails asset pipeline.

app/views/users/show.html.erb (1)

2-4: LGTM! Proper asset loading for Turbo.

The head block correctly loads the user-specific JavaScript with data-turbo-track: "reload" and defer: true, which ensures proper script execution timing and enables Turbo Drive to track asset changes.

app/views/credit_mutations/index.html.erb (1)

2-4: LGTM! Consistent asset loading pattern.

The head block follows the same pattern as other views in this PR, properly integrating with Turbo Drive's asset tracking and deferred script loading.

app/views/activities/order_screen.html.erb (1)

2-4: LGTM! Consistent with the migration pattern.

The asset loading follows the established pattern across all migrated views.

app/views/price_lists/index.html.erb (1)

2-4: LGTM! Consistent asset loading.

app/assets/config/manifest.js (1)

1-2: LGTM! Correct manifest configuration for the new asset pipeline.

The change from link_directory to link_tree ../builds properly reflects the migration to jsbundling-rails and cssbundling-rails, where compiled assets are output to the app/assets/builds directory.

app/views/users/index.html.erb (1)

2-4: LGTM! Correct Turbo asset loading pattern.

The head content block correctly loads the JavaScript asset with Turbo tracking and deferred loading, following Rails 7 best practices for the migration from Webpacker.

app/javascript/order_screen.js (4)

1-6: LGTM! Import structure updated correctly.

The migration to relative imports for components aligns with the new module structure in the Turbo-based setup.


7-9: LGTM! Correct Turbo event handler pattern.

The migration from turbolinks:load to turbo:load with proper element existence checking follows the correct pattern for the Turbo migration.


10-13: LGTM! Improved variable declarations.

Changing from var to const for dataset-derived variables is a good practice that prevents accidental reassignment.


26-304: ****

Vue-resource is already properly installed and configured. The app/javascript/application.js file imports vue-resource and registers it with Vue.use(VueResource) on line 12. CSRF token setup for Vue.http is also in place on lines 15-20.

The code in order_screen.js will work correctly as this module loads after application.js executes its global setup. No changes are required.

Likely an incorrect or invalid review comment.

Comment on lines +5 to +6
import VueResource from 'vue-resource';
import axios from 'axios';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify the need for both VueResource and axios.

Both VueResource and axios are imported and configured with CSRF tokens. Having two HTTP clients can lead to maintenance confusion and increased bundle size. Please verify that both are necessary, or consider standardizing on one.

Run the following script to check usage patterns:

Also applies to: 12-12, 18-19


🏁 Script executed:

#!/bin/bash
# Check usage of VueResource vs axios in the codebase

echo "=== VueResource usage (this.\$http) ==="
rg -n --type=js --type=vue -C2 '\$http\.(get|post|put|delete|patch)'

echo -e "\n=== Axios usage ==="
rg -n --type=js --type=vue -C2 'axios\.(get|post|put|delete|patch)'

Length of output: 115


I need to retry with a broader search strategy since the .vue file type isn't recognized in the sandbox. Let me search more comprehensively:


---

🏁 Script executed:

```shell
#!/bin/bash
# Broader search for VueResource and axios usage without file type restrictions

echo "=== Checking all references to \$http (VueResource pattern) ==="
rg -n '\$http\.(get|post|put|delete|patch)' --max-count=10

echo -e "\n=== Checking all references to axios method calls ==="
rg -n 'axios\.(get|post|put|delete|patch)' --max-count=10

echo -e "\n=== Checking for Vue.use patterns ==="
rg -n 'Vue\.use' --max-count=10

echo -e "\n=== Checking for any import/require of these libraries ==="
rg -n '(vue-resource|axios)' app/javascript/ --max-count=15

echo -e "\n=== List all files in app/javascript directory ==="
find app/javascript -type f -name '*.js' -o -name '*.vue' -o -name '*.ts' -o -name '*.tsx' | head -20

Length of output: 3766


Both VueResource and axios are actively used; consolidate to a single HTTP client.

Your code imports and configures two separate HTTP clients: VueResource (10 usages across price_lists.js, order_screen.js, and Vue components) and axios (4 usages in newer Vue components). This creates maintenance overhead and increases bundle size.

Consolidate to a single client—axios is the modern standard. Migrate remaining VueResource calls (this.$http) to axios.

@codecov
Copy link

codecov bot commented Oct 28, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 75.27%. Comparing base (04a5d73) to head (4e44d0c).

Additional details and impacted files
@@             Coverage Diff             @@
##           staging    #1108      +/-   ##
===========================================
+ Coverage    74.10%   75.27%   +1.17%     
===========================================
  Files           51       50       -1     
  Lines         1093     1076      -17     
===========================================
  Hits           810      810              
+ Misses         283      266      -17     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/javascript/order_screen.js (1)

7-415: Critical: Potential memory leak with Turbo navigation.

The turbo:load event fires on every navigation, not just initial page load. When users navigate away and return to this page, new Vue instances will be created without destroying the previous ones, causing memory leaks and potential conflicts.

Recommended solution:

Add cleanup on Turbo's cache/navigation events:

+let vueInstances = [];
+
 document.addEventListener('turbo:load', () => {
   const element = document.getElementById('order-screen');
   if (element != null) {
+    // Clean up existing instances before creating new ones
+    vueInstances.forEach(instance => {
+      if (instance && typeof instance.$destroy === 'function') {
+        instance.$destroy();
+      }
+    });
+    vueInstances = [];
+
     const users = JSON.parse(element.dataset.users);
     // ... rest of setup ...
     
     const app = new Vue({
       // ... app config ...
     });
+    vueInstances.push(app);
     
     // For each additional Vue instance:
-    new Vue({
+    const creditMutationModal = new Vue({
       el: document.getElementById('credit-mutation-modal'),
       // ...
     });
+    vueInstances.push(creditMutationModal);
     
     // Repeat for other instances...
   }
 });
+
+// Clean up before Turbo caches the page
+document.addEventListener('turbo:before-cache', () => {
+  vueInstances.forEach(instance => {
+    if (instance && typeof instance.$destroy === 'function') {
+      instance.$destroy();
+    }
+  });
+  vueInstances = [];
+});
♻️ Duplicate comments (6)
app/javascript/application.js (1)

5-6: Dual HTTP client issue remains unresolved.

Both VueResource and axios are still imported and configured with CSRF tokens. As noted in the previous review, having two HTTP clients increases bundle size and maintenance overhead. This issue should be addressed by consolidating to a single HTTP client (axios is recommended as the modern standard).

Also applies to: 12-12, 24-25

Procfile.dev (1)

1-3: LGTM; resolves prior port mismatch.

No hardcoded -p; server will pick PORT from env/puma.rb. This addresses the earlier “-p 5000 vs bin/dev” concern.

Quick check that scripts exist:

rg -nP '"watch"\s*:|build:css' package.json -C1
webpack.config.js (3)

18-21: Do not hardcode production mode.

Use env/argv to switch modes for fast dev builds and correct prod optimizations.

-module.exports = {
-  mode: "production",
-  devtool: process.env.NODE_ENV === "production" ? "hidden-source-map" : "source-map",
+module.exports = (_env, argv) => {
+  const mode = argv?.mode || process.env.NODE_ENV || "development";
+  return {
+    mode,
+    devtool: mode === "production" ? "hidden-source-map" : "source-map",
     // ...
-};
+  };
+};

30-33: Resolve conflict: single chunk vs splitChunks.

LimitChunkCount(maxChunks: 1) disables code splitting while splitChunks: 'all' tries to split. Choose one.

Option A (single bundle per entry):

   plugins: [
-    new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
+    new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
     new VueLoaderPlugin(),
   ],
   // ...
   optimization: {
-    splitChunks: {
-      chunks: 'all',
-    },
+    // disabled (single file per entry)
   },

Option B (enable shared chunks):

   plugins: [
-    new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
     new VueLoaderPlugin(),
   ],
   optimization: {
     splitChunks: {
       chunks: 'all',
+      cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: 'vendors', priority: 10 } },
     },
   },

Also applies to: 66-70


51-57: SVGs are routed to webfonts; likely incorrect.

The font rule captures .svg and emits to webfonts/, which will also catch image SVGs.

Apply:

-      {
-        test: /\.(woff|woff2|eot|ttf|otf|svg)$/i,
+      {
+        test: /\.(woff|woff2|eot|ttf|otf)$/i,
         type: 'asset/resource',
         generator: {
           filename: 'webfonts/[name][ext]'
         }
       },
+      {
+        test: /\.svg$/i,
+        type: 'asset/resource',
+        generator: { filename: 'images/[name][ext]' }
+      },
.babelrc (1)

13-17: transform‑runtime requires @babel/runtime at runtime.

Add @babel/runtime to dependencies (not devDependencies). Also make intent explicit to avoid polyfill overlap with useBuiltIns.

#!/bin/bash
jq -r '.dependencies["@babel/runtime"] // .devDependencies["@babel/runtime"] // "MISSING: @babel/runtime"' package.json
-    "@babel/plugin-transform-runtime"
+    ["@babel/plugin-transform-runtime", { "corejs": false, "helpers": true, "regenerator": true }]
🧹 Nitpick comments (7)
config/initializers/assets.rb (1)

6-6: Consider using single quotes for static strings.

Ruby style conventions prefer single quotes for static strings without interpolation. While this is functionally equivalent, using single quotes here would be more consistent with typical Ruby style guides.

-Rails.application.config.assets.version = "1.0"
+Rails.application.config.assets.version = '1.0'
app/javascript/invoices.js (1)

46-70: Review the row cloning logic for robustness.

The addRow method manipulates the DOM directly to clone invoice rows and update field names. A few concerns:

  1. Line 64: The regex /\[\d+\]/g replaces ALL bracketed digit sequences in the name attribute, which could inadvertently affect unrelated parts of the field name if the naming convention changes.
  2. Line 48: lastElementChild assumes there's always at least one row; this could fail if the container is empty.
  3. The logic only updates firstElementChild of each field wrapper, which might miss nested inputs.

Consider adding defensive checks and making the regex more specific:

 addRow() {
   const invoiceRowContainer = document.getElementById('invoice_row');
+  if (!invoiceRowContainer || invoiceRowContainer.children.length === 0) {
+    console.error('Cannot add row: container is empty');
+    return;
+  }
   const lastRow = invoiceRowContainer.lastElementChild;
   const newRow = lastRow.cloneNode(true);

   invoiceRowContainer.appendChild(newRow);
   Array.from(newRow.children).forEach((fieldWrapper) => {
     let newIndex = -1;
     if (fieldWrapper.firstElementChild) {
       const nameAttr = fieldWrapper.firstElementChild.name;
       if (nameAttr) {
         if (newIndex === -1) {
-          const match = nameAttr.match(/invoice\[rows_attributes\]\[(\d+)\]/);
+          const match = nameAttr.match(/invoice\[rows_attributes\]\[(\d+)\]\[/);
           if (match && match[1]) {
             newIndex = Number(match[1]) + 1;
           }
         }
         if (newIndex !== -1) {
-          fieldWrapper.firstElementChild.name = nameAttr.replace(/\[\d+\]/g, '[' + newIndex + ']');
+          fieldWrapper.firstElementChild.name = nameAttr.replace(
+            /\[rows_attributes\]\[\d+\]/,
+            `[rows_attributes][${newIndex}]`
+          );
           fieldWrapper.firstElementChild.value = '';
         }
       }
     }
   });
 }
app/javascript/order_screen.js (1)

15-18: Minor: Global window.flash function could be better scoped.

The window.flash function is set on every turbo:load event. While reassignment is generally safe, consider using a namespaced approach (e.g., window.app.flash) or checking if it already exists to avoid potential conflicts with other pages or libraries.

Consider this pattern:

-   window.flash = function(message, actionText, type) {
+   if (!window.flash) {
+     window.flash = function(message, actionText, type) {
       const event = new CustomEvent('flash', { detail: { message: message, actionText: actionText, type: type } } );
       document.body.dispatchEvent(event);
+     };
-   };
+   }

Or use a namespace:

-   window.flash = function(message, actionText, type) {
+   window.orderScreen = window.orderScreen || {};
+   window.orderScreen.flash = function(message, actionText, type) {
     const event = new CustomEvent('flash', { detail: { message: message, actionText: actionText, type: type } } );
     document.body.dispatchEvent(event);
   };
webpack.config.js (3)

10-16: Entries only scan the top level; nested entry points are missed.

fs.readdirSync won’t find app/javascript/subdir/*.js. If you rely on nested entries, switch to a recursive glob.

Example:

-const entries = fs.readdirSync(javascriptsDir)
-  .filter(file => file.endsWith(".js"))
-  .reduce((acc, file) => {
-    const name = file.replace(/\.js$/, "");
-    acc[name] = path.join(javascriptsDir, file);
-    return acc;
-  }, {});
+const glob = require("glob");
+const entries = glob.sync(path.join(javascriptsDir, "**/*.js"))
+  .reduce((acc, absPath) => {
+    const rel = path.relative(javascriptsDir, absPath).replace(/\.js$/, "");
+    acc[rel] = absPath;
+    return acc;
+  }, {});

If you prefer staying top‑level, confirm all intended entries live directly under app/javascript.


61-64: Prefer Vue runtime‑only build if SFCs are compiled.

If templates are compiled by vue-loader, you don’t need the full compiler build; runtime is smaller.

-    alias: {
-      vue$: 'vue/dist/vue.esm.js'
-    }
+    alias: {
+      vue$: 'vue/dist/vue.runtime.esm.js'
+    }

Confirm no code relies on runtime template compilation (e.g., new Vue({ template: ... })).


61-62: Add .jsx to resolve extensions.

You transpile .jsx but it won’t resolve without explicit extension.

-    extensions: [".js", ".vue"],
+    extensions: [".js", ".jsx", ".vue"],
.babelrc (1)

14-14: Optional: remove syntax-dynamic-import.

Webpack handles dynamic import; Babel 7 parses it without extra plugin in most setups. Safe to drop if tests still pass.

-    "@babel/plugin-syntax-dynamic-import",
+    // "@babel/plugin-syntax-dynamic-import", // likely unnecessary
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 391300e and 126e986.

📒 Files selected for processing (27)
  • .babelrc (1 hunks)
  • .gitignore (1 hunks)
  • Procfile.dev (1 hunks)
  • README.md (1 hunks)
  • app/assets/stylesheets/application.scss (1 hunks)
  • app/javascript/activity.js (1 hunks)
  • app/javascript/application.js (1 hunks)
  • app/javascript/credit_mutations.js (1 hunks)
  • app/javascript/invoices.js (1 hunks)
  • app/javascript/order_screen.js (4 hunks)
  • app/javascript/payment_add.js (1 hunks)
  • app/javascript/users.js (1 hunks)
  • app/views/activities/_credit_mutation_modal.html.erb (1 hunks)
  • app/views/activities/index.html.erb (3 hunks)
  • app/views/activities/order_screen.html.erb (8 hunks)
  • app/views/activities/show.html.erb (3 hunks)
  • app/views/credit_mutations/index.html.erb (3 hunks)
  • app/views/index/index.html.erb (4 hunks)
  • app/views/partials/_navigation_bar.html.erb (3 hunks)
  • app/views/price_lists/index.html.erb (8 hunks)
  • app/views/users/index.html.erb (4 hunks)
  • app/views/users/show.html.erb (5 hunks)
  • bin/dev (1 hunks)
  • config/environments/development.rb (2 hunks)
  • config/initializers/assets.rb (1 hunks)
  • config/puma.rb (1 hunks)
  • webpack.config.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • app/views/users/index.html.erb
  • README.md
  • app/javascript/users.js
  • app/views/activities/index.html.erb
  • app/javascript/credit_mutations.js
  • app/javascript/payment_add.js
  • app/views/credit_mutations/index.html.erb
  • app/views/activities/order_screen.html.erb
  • app/views/users/show.html.erb
  • app/assets/stylesheets/application.scss
🧰 Additional context used
🧬 Code graph analysis (2)
app/javascript/activity.js (8)
app/javascript/credit_mutations.js (1)
  • element (16-16)
app/javascript/invoices.js (1)
  • element (7-7)
app/javascript/order_screen.js (2)
  • element (8-8)
  • app (26-304)
app/javascript/payment_add.js (1)
  • element (6-6)
app/javascript/users.js (1)
  • element (14-14)
app/javascript/activities.js (1)
  • element (4-4)
app/javascript/user.js (1)
  • element (7-7)
app/javascript/price_lists.js (1)
  • element (4-4)
config/initializers/assets.rb (1)
config/application.rb (1)
  • config (10-93)
🪛 ESLint
app/javascript/application.js

[error] 16-16: Expected indentation of 4 spaces but found 6.

(indent)


[error] 18-18: Expected indentation of 0 spaces but found 2.

(indent)

webpack.config.js

[error] 1-1: 'require' is not defined.

(no-undef)


[error] 2-2: 'require' is not defined.

(no-undef)


[error] 3-3: 'require' is not defined.

(no-undef)


[error] 4-4: 'require' is not defined.

(no-undef)


[error] 7-7: '__dirname' is not defined.

(no-undef)


[error] 18-18: 'module' is not defined.

(no-undef)


[error] 20-20: 'process' is not defined.

(no-undef)


[error] 27-27: '__dirname' is not defined.

(no-undef)


[error] 47-47: Expected indentation of 8 spaces but found 9.

(indent)


[error] 48-48: Expected indentation of 8 spaces but found 9.

(indent)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (21)
.gitignore (2)

35-36: Correct Webpacker→Rails 7 asset pipeline entries.

The removals of /public/packs* (Webpacker output) and addition of /app/assets/builds/* with a .keep exception correctly reflect the migration to Rails 7 JS bundling. This pattern is appropriate.

Also applies to: 40-41


35-41: Review comment is incorrect: /node_modules is NOT being removed from .gitignore.

The snippet you provided as the "final state" actually demonstrates that /node_modules (line 39), yarn-debug.log* (line 40), and .yarn-integrity (line 41) remain in .gitignore. Your concern about their removal contradicts the code snippet you quoted. The actual changes are correct: removing Webpacker entries (/public/packs*) and adding Rails 7+ jsbundling output (/app/assets/builds/*).

Likely an incorrect or invalid review comment.

config/initializers/assets.rb (1)

1-14: Asset migration is correctly configured—no issues found.

Verification confirms the Webpacker → webpack/Sass migration is complete and properly configured:

  • Webpack correctly resolves npm packages (webpack searches node_modules by default for imports/requires)
  • Sass build explicitly includes --load-path=node_modules for CSS imports from npm packages
  • All built assets output to app/assets/builds/ as configured in manifest.js
  • Asset pipeline serves pre-built assets through standard Rails asset tags (stylesheet_link_tag, javascript_include_tag)
  • No old Webpacker pack tags remain

Removing the node_modules asset load path from config/initializers/assets.rb is correct for this setup—the asset bundlers handle npm package resolution independently, and the Rails asset pipeline serves only the pre-built outputs.

app/views/activities/show.html.erb (2)

2-4: LGTM! Asset loading updated for Turbo.

The addition of the head content block to load the activity JavaScript with Turbo tracking and defer is consistent with the migration to Rails 7 + Turbo.


13-13: LGTM! Icon rendering modernized.

The replacement of fa_icon helpers with inline FontAwesome <i> elements is consistent with the broader migration away from the font-awesome-rails gem.

Also applies to: 17-17, 22-22, 81-81

app/views/activities/_credit_mutation_modal.html.erb (1)

19-19: LGTM! Icon rendering updated.

Consistent with the project-wide migration from fa_icon helpers to inline FontAwesome elements.

app/views/index/index.html.erb (1)

30-30: LGTM! Icon rendering modernized.

The replacement of fa_icon helpers with inline FontAwesome <i> elements is consistent with the project-wide migration.

Also applies to: 61-61, 89-89

app/views/price_lists/index.html.erb (3)

2-4: LGTM! Asset loading updated for Turbo.

The head content block properly loads the price_lists JavaScript with Turbo tracking and defer.


31-31: Good catch on the typo fix!

Correcting "gearchieveerde" to "gearchiveerde" improves the Dutch text quality.


22-22: LGTM! Icon rendering modernized.

All icon changes from fa_icon helpers to inline FontAwesome <i> elements are consistent with the project-wide migration.

Also applies to: 58-58, 61-61, 89-89, 103-103, 114-114, 121-121, 124-124

app/javascript/application.js (1)

20-27: LGTM! Proper CSRF token handling.

The turbo:load event properly reads the CSRF token from the meta tag and conditionally sets it on both HTTP clients, preventing errors when the token is missing.

app/views/partials/_navigation_bar.html.erb (1)

13-13: LGTM! Icon rendering modernized across navigation.

All navigation icons have been consistently migrated from fa_icon helpers to inline FontAwesome <i> elements, aligning with the project-wide migration.

Also applies to: 19-19, 26-26, 31-31, 38-38, 45-45, 52-52, 59-59, 66-66, 75-75

app/javascript/order_screen.js (4)

3-5: LGTM: Import paths correctly updated for the migration.

The relative import paths are appropriate for the Rails 7 JS bundling setup and follow standard ES module conventions.


214-226: Good fix: Typo corrected from affilateKey to affiliateKey.

The variable name now correctly spells "affiliate" and properly constructs the SumUp payment URL.


318-324: LGTM: Well-structured validation helper.

Extracting the validation logic into a dedicated isFormInvalid() method improves readability and maintainability. The method correctly combines native HTML5 form validation with business logic checks.


326-334: LGTM: Clean validation flow.

The refactored validation correctly uses the helper method and maintains the validation state flag for potential UI feedback.

bin/dev (1)

8-11: No issues found; code is correct as-is.

Verification confirms PORT is properly set in bin/dev (line 9) and correctly consumed by puma.rb (line 27) via ENV.fetch('PORT', 5000). No conflicting port overrides detected elsewhere in the codebase. The optional suggestion to explicitly pass -p "$PORT" in Procfile.dev remains a valid belt-and-suspenders approach, but is not required.

config/environments/development.rb (1)

22-22: Good fix: correct cache toggle path.

Switching to tmp/caching-dev.txt is the standard Rails dev cache toggle.

.babelrc (3)

18-33: Test env block looks solid.

Targets Node current and converts dynamic imports for tests; LGTM.


1-34: No action required—browserslist already configured.

A .browserslistrc file exists with the defaults configuration, which properly controls polyfill targets. The recommendation is already satisfied.


6-8: core-js@3 is properly specified — concern addressed.

Verification confirms core-js version 3 is present in package.json. The "useBuiltIns": "usage" configuration in .babelrc will correctly inject polyfills without runtime errors.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
app/javascript/activity.js (1)

1-13: Previous memory leak issue resolved; remove unused import.

The module-scoped vueInstance and top-level turbo:before-cache listener properly address the memory leak flagged in the previous review. This pattern now matches the approach used in invoices.js, credit_mutations.js, and users.js.

However, the Tab import on line 4 is unused since the redundant tab activation code has been removed.

Apply this diff to remove the unused import:

 import Vue from 'vue/dist/vue.esm';
 
 import ProductTotals from './components/activity/ProductTotals.vue';
-import Tab from 'bootstrap/js/dist/tab';
 
 let vueInstance = null;
config/initializers/assets.rb (1)

6-6: Optional: Prefer single quotes for static strings.

Ruby convention favors single quotes when there's no string interpolation. Consider using '1.0' instead of "1.0".

app/javascript/invoices.js (1)

4-4: Consider migrating from moment.js to a modern alternative.

Moment.js is in maintenance mode and has a large bundle size. Modern alternatives like date-fns or day.js offer better performance and tree-shaking.

Example with date-fns:

-import moment from 'moment';
+import { format } from 'date-fns';

 // ...

 formatDate(time) {
-  return moment(time).format('DD-MM-YY HH:mm');
+  return format(new Date(time), 'dd-MM-yy HH:mm');
 }

Also applies to: 52-54

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c75fe9e and b27ccc9.

📒 Files selected for processing (6)
  • app/javascript/activity.js (1 hunks)
  • app/javascript/application.js (1 hunks)
  • app/javascript/invoices.js (1 hunks)
  • bin/dev (1 hunks)
  • config/initializers/assets.rb (1 hunks)
  • webpack.config.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • webpack.config.js
  • app/javascript/application.js
  • bin/dev
🧰 Additional context used
🧬 Code graph analysis (3)
app/javascript/activity.js (3)
app/javascript/invoices.js (2)
  • vueInstance (6-6)
  • element (16-16)
app/javascript/credit_mutations.js (2)
  • vueInstance (5-5)
  • element (16-16)
app/javascript/users.js (2)
  • vueInstance (4-4)
  • element (14-14)
app/javascript/invoices.js (4)
app/javascript/activity.js (2)
  • vueInstance (6-6)
  • element (16-16)
app/javascript/credit_mutations.js (2)
  • vueInstance (5-5)
  • element (16-16)
app/javascript/users.js (2)
  • vueInstance (4-4)
  • element (14-14)
app/javascript/activities.js (1)
  • element (4-4)
config/initializers/assets.rb (1)
config/application.rb (1)
  • config (10-93)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (4)
app/javascript/activity.js (1)

15-25: LGTM! Redundant tab activation removed.

The Vue initialization logic is clean and defensive. The redundant programmatic tab activation from the previous review has been properly removed, allowing the server-rendered active classes to handle the initial tab state.

config/initializers/assets.rb (2)

1-2: Past issue resolved.

The work-in-progress marker "(CORRECT AND FINAL)" flagged in the previous review has been successfully removed. The file path comment is now clean and conventional.


10-10: LGTM! Correct Rails 7+ asset path configuration.

Adding app/assets/builds to the asset load path is the standard approach for Rails 7+ with JS bundling (replacing Webpacker). This aligns correctly with the webpack configuration and manifest changes in the PR.

app/javascript/invoices.js (1)

6-13: LGTM! Vue instance cleanup now properly implemented.

The module-level vueInstance variable with cleanup on turbo:before-cache addresses the memory leak concern from the previous review. The pattern now matches the implementations in activity.js, credit_mutations.js, and users.js.

Comment on lines +55 to +79
addRow() {
const invoiceRowContainer = document.getElementById('invoice_row');
const lastRow = invoiceRowContainer.lastElementChild;
const newRow = lastRow.cloneNode(true);

invoiceRowContainer.appendChild(newRow);
Array.from(newRow.children).forEach((fieldWrapper) => {
let newIndex = -1;
if (fieldWrapper.firstElementChild) {
const nameAttr = fieldWrapper.firstElementChild.name;
if (nameAttr) {
if (newIndex === -1) {
const match = nameAttr.match(/invoice\[rows_attributes\]\[(\d+)\]/);
if (match && match[1]) {
newIndex = Number(match[1]) + 1;
}
}
if (newIndex !== -1) {
fieldWrapper.firstElementChild.name = nameAttr.replace(/\[\d+\]/g, '[' + newIndex + ']');
fieldWrapper.firstElementChild.value = '';
}
}
}
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Add null check to prevent runtime error.

Line 57's lastElementChild will be null if the container is empty, causing a TypeError on line 58 when cloneNode is called.

Apply this diff to add a defensive check:

 addRow() {
   const invoiceRowContainer = document.getElementById('invoice_row');
   const lastRow = invoiceRowContainer.lastElementChild;
+  if (!lastRow) {
+    console.error('No invoice rows found to clone');
+    return;
+  }
   const newRow = lastRow.cloneNode(true);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
addRow() {
const invoiceRowContainer = document.getElementById('invoice_row');
const lastRow = invoiceRowContainer.lastElementChild;
const newRow = lastRow.cloneNode(true);
invoiceRowContainer.appendChild(newRow);
Array.from(newRow.children).forEach((fieldWrapper) => {
let newIndex = -1;
if (fieldWrapper.firstElementChild) {
const nameAttr = fieldWrapper.firstElementChild.name;
if (nameAttr) {
if (newIndex === -1) {
const match = nameAttr.match(/invoice\[rows_attributes\]\[(\d+)\]/);
if (match && match[1]) {
newIndex = Number(match[1]) + 1;
}
}
if (newIndex !== -1) {
fieldWrapper.firstElementChild.name = nameAttr.replace(/\[\d+\]/g, '[' + newIndex + ']');
fieldWrapper.firstElementChild.value = '';
}
}
}
});
}
addRow() {
const invoiceRowContainer = document.getElementById('invoice_row');
const lastRow = invoiceRowContainer.lastElementChild;
if (!lastRow) {
console.error('No invoice rows found to clone');
return;
}
const newRow = lastRow.cloneNode(true);
invoiceRowContainer.appendChild(newRow);
Array.from(newRow.children).forEach((fieldWrapper) => {
let newIndex = -1;
if (fieldWrapper.firstElementChild) {
const nameAttr = fieldWrapper.firstElementChild.name;
if (nameAttr) {
if (newIndex === -1) {
const match = nameAttr.match(/invoice\[rows_attributes\]\[(\d+)\]/);
if (match && match[1]) {
newIndex = Number(match[1]) + 1;
}
}
if (newIndex !== -1) {
fieldWrapper.firstElementChild.name = nameAttr.replace(/\[\d+\]/g, '[' + newIndex + ']');
fieldWrapper.firstElementChild.value = '';
}
}
}
});
}
🤖 Prompt for AI Agents
In app/javascript/invoices.js around lines 55-79, the code calls
lastElementChild.cloneNode but lastElementChild can be null when the container
is empty; add a defensive null check right after obtaining lastRow and bail out
or create/clone from a template when lastRow is null (e.g., if (!lastRow)
return; or use a predefined template element to clone), then proceed with the
existing renaming and clearing logic so cloneNode is only called on a non-null
element.

}
}
if (newIndex !== -1) {
fieldWrapper.firstElementChild.name = nameAttr.replace(/\[\d+\]/g, '[' + newIndex + ']');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Regex replacement may incorrectly modify nested array indices.

The global regex /\[\d+\]/g replaces all bracket-digit patterns in the field name. If the form contains nested arrays (e.g., invoice[rows_attributes][0][items][1][name]), this will incorrectly replace both the row index and nested indices.

Replace with a more specific pattern that targets only the rows_attributes index:

-fieldWrapper.firstElementChild.name = nameAttr.replace(/\[\d+\]/g, '[' + newIndex + ']');
+fieldWrapper.firstElementChild.name = nameAttr.replace(/(\[rows_attributes\]\[)\d+(\])/, '$1' + newIndex + '$2');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fieldWrapper.firstElementChild.name = nameAttr.replace(/\[\d+\]/g, '[' + newIndex + ']');
fieldWrapper.firstElementChild.name = nameAttr.replace(/(\[rows_attributes\]\[)\d+(\])/, '$1' + newIndex + '$2');
🤖 Prompt for AI Agents
In app/javascript/invoices.js around line 73, the current replacement uses a
global bracket-digit regex which will change every numeric index in nested
names; restrict the replacement to only the rows_attributes index by matching
the specific segment that contains rows_attributes and its following numeric
index (e.g. match the "[rows_attributes][<number>]" portion) and replace only
that captured numeric index with the newIndex while leaving other bracketed
numbers intact; implement the replacement using a regex that targets the
rows_attributes segment and a replacement that preserves the surrounding text so
nested array indices are not modified.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants