Skip to content

Interactive task list checkboxes#88

Merged
HamptonMakes merged 3 commits intomainfrom
feature/interactive-checkboxes
Apr 9, 2026
Merged

Interactive task list checkboxes#88
HamptonMakes merged 3 commits intomainfrom
feature/interactive-checkboxes

Conversation

@HamptonMakes
Copy link
Copy Markdown
Collaborator

Summary

Convert markdown task list items (- [ ] / - [x]) into interactive checkboxes in the plan viewer. Clicking a checkbox toggles it on the server, creating a new PlanVersion with full provenance.

How it works

  1. RenderingMarkdownHelper#make_checkboxes_interactive post-processes Commonmarker HTML: removes disabled, adds Stimulus bindings, maps each checkbox to its source markdown line via data-line-text
  2. Toggle — Stimulus checkbox_controller sends PATCH /plans/:id/toggle_checkbox with old_text, new_text, and base_revision for optimistic concurrency
  3. ServerPlansController#toggle_checkbox applies a replace_exact operation, creates a versioned PlanVersion attributed to the clicking user, broadcasts header update
  4. Conflict handling — On 409, the controller retries once with the fresh revision; on second failure, reverts the checkbox. In-flight guard prevents rapid double-clicks.

Details

  • Any user who can view a plan can toggle checkboxes
  • Supports all list markers (-, *, +) and case-insensitive [x]/[X]
  • Checked items render with muted text color (no strikethrough)
  • Header version count updates live via Turbo Stream broadcast

Tests

  • 10 helper specs (rendering, data attributes, classes)
  • 7 request specs (toggle, conflict, auth, errors)
  • 8 system specs (full browser tests: click → server → UI update)
  • All 633 specs pass

Convert markdown task list items (- [ ] / - [x]) into interactive
checkboxes in the plan viewer. Clicking a checkbox applies a
replace_exact operation on the server, creating a new PlanVersion
attributed to the user.

- Markdown helper post-processes Commonmarker HTML to make checkboxes
  interactive with Stimulus data attributes
- Stimulus checkbox_controller handles optimistic toggle with in-flight
  guard, sends PATCH, retries once on 409 conflict
- PlansController#toggle_checkbox applies replace_exact with optimistic
  concurrency (base_revision), broadcasts header update
- CSS for task lists: no bullets, muted color for checked items
- Supports all list markers (-, *, +) and case-insensitive [x]/[X]
- Helper, request, and system specs (13 new tests)

Amp-Thread-ID: https://ampcode.com/threads/T-019d7340-20dd-74b5-bcc4-d428c23883cc
Co-authored-by: Amp <amp@ampcode.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d61a7ccbb3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +76 to +79
result = Plans::ApplyOperations.call(
content: current_content,
operations: [{ "op" => "replace_exact", "old_text" => old_text, "new_text" => new_text }]
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Validate toggle payload before applying replace_exact

toggle_checkbox applies client-supplied old_text/new_text directly as a replace_exact operation, so any user who can hit this endpoint can replace arbitrary unique text in the plan by crafting the JSON payload instead of only flipping [ ]/[x] on a task item. This effectively bypasses the normal update? guard for non-checkbox content and turns a checkbox toggle API into a general edit primitive.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 26dd248 — added server-side validation that both old_text and new_text match the task list pattern (/\A\s*[*+-]\s+\[[ xX]\]\s/). Arbitrary text replacements now get a 422.

Comment on lines +50 to +53
task_lines = content.to_s.lines.map(&:rstrip).select { |l| l.match?(/^\s*[*+-]\s+\[[ xX]\]\s/) }

checkboxes.each_with_index do |cb, i|
line_text = task_lines[i]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Map checkboxes from parsed markdown, not raw line regex

The helper pairs rendered checkboxes to source lines purely by index (task_lines[i]), but task_lines is built from raw markdown text and includes task-like lines that do not produce checkbox nodes (for example, inside fenced code blocks). In those documents the indices drift, so clicking a visible checkbox can send old_text for a different line and mutate the wrong content.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 26dd248 — extracted extract_task_lines method that tracks fenced code blocks (```/~~~) and skips task-like lines inside them. Added a test confirming checkboxes inside code fences don't misalign the mapping.

cb.remove_attribute("disabled")
cb["data-action"] = "coplan--checkbox#toggle"
cb["data-coplan--checkbox-target"] = "checkbox"
cb["data-line-text"] = line_text.lstrip
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve indentation when storing checkbox source text

Using lstrip on the captured task line drops leading indentation, so nested task items (e.g., - [ ] subtask) are posted back as - [ ] subtask. The server then cannot find the exact text to replace and returns 422, which makes nested checklist items non-toggleable.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 26dd248 — removed lstrip, so data-line-text now preserves leading whitespace (e.g. - [ ] subtask). Added a test for nested task items confirming indentation is retained.

HamptonMakes and others added 2 commits April 9, 2026 15:23
…ve indentation

- Validate old_text/new_text match task list pattern to prevent
  using toggle_checkbox as a general edit endpoint
- Extract task lines skipping fenced code blocks to prevent
  checkbox-to-line misalignment
- Remove lstrip to preserve indentation for nested task items
- Add tests for all three fixes

Amp-Thread-ID: https://ampcode.com/threads/T-019d7340-20dd-74b5-bcc4-d428c23883cc
Co-authored-by: Amp <amp@ampcode.com>
- Wrap task list item contents in <label> so clicking anywhere on the
  text toggles the checkbox (not just the tiny input)
- Remove task-list-item--checked class and all checked styling — the
  checkbox state itself is the only visual indicator
- Add system test for label-click toggling

Amp-Thread-ID: https://ampcode.com/threads/T-019d7340-20dd-74b5-bcc4-d428c23883cc
Co-authored-by: Amp <amp@ampcode.com>
@HamptonMakes HamptonMakes merged commit 791ed25 into main Apr 9, 2026
5 checks passed
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.

1 participant