Skip to content

Comments

Fix week display format persisting after Turbo requests#441

Merged
kaospr merged 3 commits intomainfrom
claude/issue-439-20260116-1400
Jan 19, 2026
Merged

Fix week display format persisting after Turbo requests#441
kaospr merged 3 commits intomainfrom
claude/issue-439-20260116-1400

Conversation

@kaospr
Copy link
Collaborator

@kaospr kaospr commented Jan 16, 2026

Summary

Fixed the bug where the week display in the time registration page changed from "Week 3" to a date format like "01-01-2026" after submitting a time registration.

Root Cause

The flatpickr date picker's input field had value: @chosen_date which rendered as a Ruby Date object. After Turbo requests, this would briefly display as a date string before flatpickr re-initialized to show the week format.

Solution

Changed the value to @chosen_date.iso8601 in app/views/time_regs/_date_picker.html.erb:2 to ensure proper ISO 8601 formatting (YYYY-MM-DD) that flatpickr can correctly parse and display with the configured week format.

Fixes #439

🤖 Generated with Claude Code

Changed the date picker value from @chosen_date to @chosen_date.iso8601
to ensure proper ISO 8601 formatting (YYYY-MM-DD). This prevents the
date from briefly displaying in the default Ruby Date format after
Turbo requests and before flatpickr re-initializes.

Fixes #439

Co-authored-by: Kasper Høglund <kaospr@users.noreply.github.com>
@claude
Copy link

claude bot commented Jan 16, 2026

Pull Request Review

Summary

This PR fixes a UI bug where the week display format ("Week 3") was reverting to a date format ("01-01-2026") after Turbo requests. The fix is minimal, targeted, and effective.

✅ Code Quality & Best Practices

Strengths:

  • Minimal change: The fix is surgical - only one character change (adding .iso8601)
  • Root cause addressed: The PR correctly identifies that passing a Ruby Date object directly to the input field causes rendering issues with flatpickr
  • Standards compliance: Using ISO 8601 format is the correct approach for date interchange
  • Well-documented: The PR description clearly explains the problem, root cause, and solution

Observations:

  • The flatpickr controller at app/javascript/controllers/flatpickr_controller.js:23 already expects "YYYY-MM-DD" format (dateFormat: "YYYY-MM-DD"), so .iso8601 ensures consistency with the JavaScript configuration
  • The controller uses moment.js for parsing with parseDate, which will correctly handle the ISO 8601 format

🐛 Potential Issues

None identified. The change is safe because:

  • Date#iso8601 returns a string in "YYYY-MM-DD" format, which matches the flatpickr configuration
  • The method is available on all Ruby Date objects (set in set_chosen_date at app/controllers/time_regs_controller.rb:148)
  • No edge cases where @chosen_date could be nil (it defaults to Date.today)

⚡ Performance Considerations

No concerns. The .iso8601 method call is:

  • A simple string formatting operation
  • Executed once per page render
  • Negligible performance impact

🔒 Security Concerns

None. This change:

  • Only affects display formatting
  • Uses a standard library method
  • Doesn't introduce any user input handling or XSS risks
  • Maintains the existing parameter parsing in the controller (Date.parse(params[:date]))

🧪 Test Coverage

Recommendation: Consider adding a test to prevent regression, though this is a minor UI fix.

A system test could verify:

  1. The date picker displays the week format ("Week X") on initial load
  2. After submitting a time registration via Turbo, the format persists
  3. The correct date is preserved when navigating

Example location: test/system/time_regs_test.rb

However, testing JavaScript-dependent UI behavior may require Selenium/Capybara setup that might be overkill for this specific fix. The CLAUDE.md indicates system tests are available with Capybara/Selenium, so infrastructure exists if desired.

📋 Additional Notes

Context validation:

  • ✅ Follows Rails conventions
  • ✅ Consistent with the codebase's flatpickr usage pattern
  • ✅ No impact on multi-tenant authorization (doesn't touch data access)
  • ✅ No localization concerns (ISO 8601 is locale-independent)

Minor observation: The redirect_back calls in the controller (app/controllers/time_regs_controller.rb:57,67,77) already pass date: @time_reg.date_worked, which means the date is preserved through the request cycle. This fix ensures the UI correctly handles that preserved date.


Verdict: ✅ APPROVED

This is a clean, well-understood fix for a specific UI bug. The change is minimal, safe, and addresses the root cause. No blocking issues identified.

Optional enhancement: Consider adding a regression test, but this is not blocking for merge.

Great work tracking down the Turbo/flatpickr interaction issue! 🤖

Copy link
Collaborator Author

@kaospr kaospr left a comment

Choose a reason for hiding this comment

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

@claude This did not fix the bug. I have a hunch that this is related to Turbo morph.

@claude
Copy link

claude bot commented Jan 19, 2026

Claude finished @kaospr's task in 1m 38s —— View job


Root Cause Identified! ✅

You were absolutely right about Turbo Morph! Here's what I found:

The Problem:

In app/views/layouts/base.html.erb:15, Turbo Morph is enabled:

<%= turbo_refresh_method_tag :morph %>

When flatpickr initializes, it creates an alternative input (altInput) that displays the formatted week text ("Week 3"), while keeping the original input hidden with the actual date value.

After form submission, Turbo Morph morphs the DOM instead of replacing it. During morphing, the altInput element that flatpickr created gets replaced by the morphing algorithm, but the Stimulus controller's flatpickr instance still references the old DOM element. This causes the raw date value to show through until the controller reconnects and re-initializes flatpickr.

The Solution:

We need to tell Turbo to skip morphing the date picker input by adding data-turbo-permanent attribute. This preserves the flatpickr-enhanced element across Turbo visits/morphs.

Proposed Fix

Add data-turbo-permanent and give it a unique id in app/views/time_regs/_date_picker.html.erb:

<%= form_with url: root_path, method: :get, class: "flex items-stretch" do |form| %>
  <%= form.text_field :date, 
    id: "date_picker_input",
    value: @chosen_date.iso8601, 
    class: "flex text-sm md:text-base px-1 md:px-2 cursor-pointer border-none text-center hover:text-primary-600 z-10 focus:ring-0 font-medium",
    data: {
      turbo_permanent: true,
      controller: "flatpickr",
      flatpickr_auto_submit_value: true,
      flatpickr_fit_width_value: true,
      flatpickr_locale_value: I18n.locale,
      flatpickr_week_numbers: true,
      flatpickr_alt_input: true,
      flatpickr_alt_format: "[#{t("common.week")}] W",
    }
  %>
<% end %>

The key changes:

  1. Added id: "date_picker_input" (required for data-turbo-permanent to work)
  2. Added data: { turbo_permanent: true } to prevent Turbo from morphing this element

This tells Turbo to skip this element during page morphs, preserving the flatpickr-enhanced DOM structure.

Todo List:

  • Read the current date picker implementation
  • Read the time_regs controller submission flow
  • Read the flatpickr Stimulus controller
  • Check Turbo configuration and morph settings
  • Investigate Turbo Morph + flatpickr interaction
  • Identify root cause and propose fix

Should I implement this fix?

Let me know if you'd like me to apply this change, or if you'd prefer to test it yourself first!


@kaospr
Copy link
Collaborator Author

kaospr commented Jan 19, 2026

Claude finished @kaospr's task in 1m 38s —— View job

Root Cause Identified! ✅

You were absolutely right about Turbo Morph! Here's what I found:

The Problem:

In app/views/layouts/base.html.erb:15, Turbo Morph is enabled:

<%= turbo_refresh_method_tag :morph %>

When flatpickr initializes, it creates an alternative input (altInput) that displays the formatted week text ("Week 3"), while keeping the original input hidden with the actual date value.

After form submission, Turbo Morph morphs the DOM instead of replacing it. During morphing, the altInput element that flatpickr created gets replaced by the morphing algorithm, but the Stimulus controller's flatpickr instance still references the old DOM element. This causes the raw date value to show through until the controller reconnects and re-initializes flatpickr.

The Solution:

We need to tell Turbo to skip morphing the date picker input by adding data-turbo-permanent attribute. This preserves the flatpickr-enhanced element across Turbo visits/morphs.

Proposed Fix

Add data-turbo-permanent and give it a unique id in app/views/time_regs/_date_picker.html.erb:

<%= form_with url: root_path, method: :get, class: "flex items-stretch" do |form| %>
  <%= form.text_field :date, 
    id: "date_picker_input",
    value: @chosen_date.iso8601, 
    class: "flex text-sm md:text-base px-1 md:px-2 cursor-pointer border-none text-center hover:text-primary-600 z-10 focus:ring-0 font-medium",
    data: {
      turbo_permanent: true,
      controller: "flatpickr",
      flatpickr_auto_submit_value: true,
      flatpickr_fit_width_value: true,
      flatpickr_locale_value: I18n.locale,
      flatpickr_week_numbers: true,
      flatpickr_alt_input: true,
      flatpickr_alt_format: "[#{t("common.week")}] W",
    }
  %>
<% end %>

The key changes:

  1. Added id: "date_picker_input" (required for data-turbo-permanent to work)
  2. Added data: { turbo_permanent: true } to prevent Turbo from morphing this element

This tells Turbo to skip this element during page morphs, preserving the flatpickr-enhanced DOM structure.

Todo List:

  • Read the current date picker implementation
  • Read the time_regs controller submission flow
  • Read the flatpickr Stimulus controller
  • Check Turbo configuration and morph settings
  • Investigate Turbo Morph + flatpickr interaction
  • Identify root cause and propose fix

Should I implement this fix?

Let me know if you'd like me to apply this change, or if you'd prefer to test it yourself first!

@claude Go ahead

@claude
Copy link

claude bot commented Jan 19, 2026

Claude finished @kaospr's task in 1m 2s —— View job


Turbo Morph Fix Implemented ✅

Changes Made:

Added data-turbo-permanent attribute and unique id to the date picker input in app/views/time_regs/_date_picker.html.erb:2-4:

<%= form.text_field :date, 
  id: "date_picker_input",
  value: @chosen_date.iso8601, 
  class: "...",
  data: {
    turbo_permanent: true,  # ← Added
    controller: "flatpickr",
    # ... other attributes
  }
%>

What This Does:

The data-turbo-permanent attribute tells Turbo to skip morphing this element during page updates. This preserves the flatpickr-enhanced DOM structure (including the altInput element that displays "Week 3" format) across Turbo Morph operations.

Without this fix, Turbo Morph would replace the flatpickr-created altInput element during form submissions, causing the raw date value to briefly show through until flatpickr re-initialized.

Testing:

Please test by:

  1. Loading the time registration page
  2. Submitting a time entry
  3. Verifying that the week format ("Week 3") persists instead of showing the date format ("01-01-2026")

Commit: e4c52c1 |

github-actions bot and others added 2 commits January 19, 2026 10:30
…reaking flatpickr

When Turbo Morph is enabled, it morphs the DOM instead of replacing it.
Flatpickr creates an alternative input element that displays the formatted
week text, but Turbo Morph replaces this element during page updates.

By adding data-turbo-permanent with a unique id, we tell Turbo to skip
morphing this element, preserving the flatpickr-enhanced DOM structure
across page updates.

Co-authored-by: Kasper Høglund <kaospr@users.noreply.github.com>
@claude
Copy link

claude bot commented Jan 19, 2026

Code Review for PR #441

I've reviewed the changes to fix the week display format issue. Here's my comprehensive feedback:

✅ Positive Aspects

1. Root Cause Correctly Identified
The issue of Ruby Date objects being rendered directly in the value attribute is correctly diagnosed. Using iso8601 ensures proper ISO 8601 formatting (YYYY-MM-DD) that flatpickr can parse.

2. Consistent Redirect Behavior
Changing from redirect_back to redirect_to time_regs_path(date: @time_reg.date_worked) in create, update, and destroy actions provides more predictable navigation behavior and maintains the selected date context.

3. Turbo Morph Handling
The event listener for turbo:morph properly reinitializes flatpickr, addressing the underlying framework behavior that was causing the display issue.


⚠️ Issues & Concerns

1. Memory Leak Risk - Event Listener (High Priority)

Location: app/javascript/controllers/flatpickr_controller.js:34,39

The turbo:morph event listener is added to the document object, but there's a potential issue with the cleanup:

handleTurboMorph = () => {
  if (this.fp) {
    this.fp.destroy();
  }
  super.connect();
  this.updateWidth();
}

Problem: The arrow function syntax creates a bound method, but calling super.connect() inside the event handler may create multiple instances of flatpickr if this controller connects/disconnects multiple times during a session. While disconnect() removes the event listener, if multiple date pickers exist on different pages, you could accumulate handlers.

Recommendation: Consider making the event listener more specific by checking if the morphed element is related to this controller's element:

handleTurboMorph = (event) => {
  // Only reinitialize if this controller's element was affected
  if (!this.element.isConnected || !document.contains(this.element)) {
    return;
  }
  if (this.fp) {
    this.fp.destroy();
  }
  super.connect();
  this.updateWidth();
}

2. Breaking Change in Error Handling (Medium Priority)

Location: app/controllers/time_regs_controller.rb:81

In the destroy action, the error handling still uses redirect_back:

rescue ActiveRecord::RecordNotDestroyed
  flash[:alert] = "Unable to delete time registration"
  redirect_back(fallback_location: time_regs_path)
end

Inconsistency: The error path uses redirect_back while the success path uses redirect_to time_regs_path(date: ...). This creates inconsistent user experience where errors might redirect to a different location than successful operations.

Recommendation:

rescue ActiveRecord::RecordNotDestroyed
  flash[:alert] = "Unable to delete time registration"
  redirect_to time_regs_path(date: @time_reg.date_worked)
end

3. Remaining redirect_back Usage (Low Priority)

Location: app/controllers/time_regs_controller.rb:47

In the new_modal action:

if current_user.current_organization.projects.empty?
  flash[:alert] = I18n.t("alert.create_project_before_registering_time")
  redirect_back fallback_location: time_regs_path
end

Consideration: For consistency with the other changes, this could also be changed to redirect_to time_regs_path, though the current implementation may be acceptable since it's an error case.

4. Missing ID Attribute Justification

Location: app/views/time_regs/_date_picker.html.erb:2

The addition of id: "date_picker_input" appears unused in the current code.

Question: Is this ID needed for future functionality, testing, or debugging? If not, consider removing it to keep the code clean.


🧪 Test Coverage Concerns

Missing Test Coverage:

  1. No tests verify that the date parameter is correctly preserved after create/update/destroy operations
  2. No tests verify the ISO 8601 format is rendered in the view
  3. No system tests covering the Turbo morph behavior and week display format persistence

Recommendation: Add a test like:

test "should preserve chosen date after creating time_reg" do
  chosen_date = 1.week.ago.to_date
  used_project = @user.current_organization.projects.first
  
  post :create, params: { 
    date: chosen_date,
    time_reg: { 
      date_worked: @current_date, 
      minutes: 60, 
      project_id: used_project.id, 
      assigned_task_id: used_project.assigned_tasks.first.id 
    } 
  }
  
  assert_redirected_to time_regs_path(date: @current_date)
end

📊 Performance & Security

Performance: ✅ No concerns. The changes are lightweight and don't introduce performance issues.

Security: ✅ No concerns. The date parameter is properly parsed in set_chosen_date with Date.parse, and there's no risk of injection attacks.


📝 Summary

Verdict: Approve with minor recommended changes

The PR successfully fixes the reported issue and improves redirect consistency. The main concerns are:

  1. Potential memory leak in event listener handling (consider adding element existence check)
  2. Inconsistent error redirect in destroy action
  3. Missing test coverage for the new behavior

The code follows Rails conventions and the CLAUDE.md guidelines for authorization and data scoping.


Suggested Priority:

  1. Fix the error redirect in destroy action for consistency
  2. Consider the event listener refinement for robustness
  3. Add test coverage for future regression prevention

Great work on identifying and fixing the root cause! 🎉

Review conducted by Claude Code

@kaospr kaospr merged commit 418bb16 into main Jan 19, 2026
4 checks passed
@kaospr kaospr deleted the claude/issue-439-20260116-1400 branch January 19, 2026 16:12
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.

Bug: Week changes to date after sending request

1 participant