Skip to content

Story: 2437 User Profile Edit UI#2478

Open
jlchilders11 wants to merge 8 commits into
developfrom
jc/user-edit-ui
Open

Story: 2437 User Profile Edit UI#2478
jlchilders11 wants to merge 8 commits into
developfrom
jc/user-edit-ui

Conversation

@jlchilders11

@jlchilders11 jlchilders11 commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Issue: #2437

Summary & Context

Adds an edit mode to the user profile for editing the individual components of the user profile.

NOTE: This includes no actual edit functionality, which will be added in several additional tickets.

Changes

  • Adds a template specifically for the edit mode v3/user_profile_edit.html, which is served when the appropriate query param is passed
  • Adds a new include v3/includes/_field_include.html which accepts a django bound field and renders it using the appropriate include field template.
  • Extends several components to use help_text
  • Extends the field_dropdown component with a disabled parameter and styling
  • Adds a new form to user/forms to capture the information from this page for processing.

‼️ Risks & Considerations ‼️

Please list any potential risks or areas that need extra attention during review/testing

Screenshots

Light Mode:
image

Dark Mode:
image

Mobile:
image

No Avatar Set:
image

Self-review Checklist

  • Link this PR to the related GitHub Project ticket

Frontend

  • UI implementation matches Figma design
  • Tested in light and dark mode
  • Responsive / mobile verified
  • Accessibility checked (keyboard navigation, etc.)
  • Ensure design tokens are used for colors, spacing, typography, etc. – No hardcoded values
  • Test without JavaScript (if applicable)
  • No console errors or warnings

Summary by CodeRabbit

  • New Features

    • User profile editing page with avatar management, biography editor, and preference controls.
    • Enhanced WYSIWYG editor with Markdown toggle and formatting toolbar for rich text editing.
    • Additional avatar size variant (xxxl) for larger profile images.
  • Improvements

    • Form fields now support disabled states and contextual help text.
    • Button styling enhancements including underline variant.

@jlchilders11 jlchilders11 linked an issue Jun 3, 2026 that may be closed by this pull request
@julioest julioest self-requested a review June 5, 2026 13:40
@herzog0 herzog0 self-requested a review June 8, 2026 15:59

@herzog0 herzog0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Heya! Thanks for this, the UI is looking great :D
Just a few things I think should be adjusted before shipping

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Heya! Is there any specific reason we need to duplicate the wysiwyg component here?
I also noticed there are a lot of imports not being used and a few function calls/refs that don't seem to exist in the scope of the script (e.g. isSafeUrl, parseMarkdownSafe and others..).

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.

So this was the best solution I found for the requirements of limiting the features on the wysiwig. I'm not super happy with this solution, but it is the best one I discovered.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Sure thing, no problem with that specifically. My comment is more related to the unused imports and the missing function references. Did you have the chance to take a look at those?

Comment thread templates/v3/includes/_field_checkbox.html
Comment thread templates/v3/user_profile_edit.html
Comment thread users/forms.py Outdated
Comment thread users/forms.py Outdated
Comment thread users/forms.py
Comment on lines +28 to +30
{% else %}
{{ type }}
{% endif %}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should we really display the raw type when it doesn't fit any case above?

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.

This is something of an opinionated option, but I think that all forms should be tested locally before they go live. This provides some feedback of "you have used an unsupported type" which the dev can then address.

{% if error %}aria-invalid="true" aria-describedby="field-{{ name }}-error"{% elif help_text %}aria-describedby="field-{{ name }}-help"{% endif %}
x-show="!jsReady"
:disabled="jsReady"
:disabled="jsReady || {{disabled}}"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If we don't pass disabled=field.disabled in the call to _field_dropdown from the _field_include.html, then this expression evaluates to jsReady || and it throws an error:

Image

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.

Good catch, added a default to "false" if the value is not passed.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a complete v3 user profile edit interface featuring a TipTap-based WYSIWYG editor with Markdown support, comprehensive form field templates, backend form definitions for profile links and commit emails, view routing logic, a two-column layout template, and supporting CSS/design system enhancements for forms, avatars, and responsive layout.

Changes

User Profile Edit Feature

Layer / File(s) Summary
WYSIWYG Editor Module: TipTap Integration with Toolbar, Markdown Mode, and Form Sync
frontend/user-profile-wysiwyg-editor.js
Initializes a TipTap editor with custom toolbar (formatting, links with URL validation), Markdown editing/preview toggle, Tab handling in code blocks, paste conversion, and form submission synchronization. Manages editor instance registry.
Export Editor Utilities and Build Configuration
frontend/wysiwyg-editor.js, package.json
Exports debounce, createToolbarButton, ICONS, and setupMermaidEditMode from the shared editor module; adds build:up-wysiwyg npm script to bundle via esbuild.
Form Field Template Components: Checkbox, Dropdown, and Field Dispatcher
templates/v3/includes/_field_checkbox.html, templates/v3/includes/_field_dropdown.html, templates/v3/includes/_field_include.html, templates/v3/includes/_form_include.html
Checkbox field with optional help text and outer wrapper; dropdown field with disabled state and lock icon; generic field dispatcher routing widget types to appropriate partials; form wrapper template iterating bound fields.
Form Styling: Token Refactoring, Help Text, and Checkbox Helpers
static/css/v3/forms.css, static/css/v3/wysiwyg-editor.css
Updates dropdown trigger styles to use token-only values without fallback literals; adds .field__checkbox__help for checkbox help text; refines checkbox selectors; adds .checkbox__multi-checkbox-column flex layout; sets WYSIWYG editor help text margin.
Backend Forms: V3 Profile, Link, and Commit Email Definitions
users/forms.py
Adds V3ProfileLinkChoices enum with form and formset; V3CommitEmailForm and formset; V3UserProfileForm with nested formset population and profile fields (tagline, bio, role, identity, notifications, terms acceptance).
View Logic: Template Routing and Edit Context
users/views.py
Imports V3UserProfileForm; adds get_template_names() override selecting v3 edit template when ?edit is present; adds early-return edit context path in get_v3_context_data() populating form and badge/connection data.
Profile Edit Page Template: Two-Column Layout with All Edit Sections
templates/v3/user_profile_edit.html
Renders complete v3 edit page with left-column Profile card (tagline, bio, links, role, avatar, badges, visibility, delete) and right-column Update Details, Commit Email (formset), Email Preferences, and Connections cards.
WYSIWYG Editor Template: Help Text Support
templates/v3/includes/_wysiwyg_editor.html
Adds optional help_text variable that renders a help paragraph with deterministic id when provided.
Design System Extensions: Avatar Size, Button Utilities, Icons
static/css/v3/avatar.css, static/css/v3/buttons.css, templates/includes/icon.html, templates/v3/includes/_avatar_v3.html
Adds --avatar-size-xxxl CSS custom property and .avatar--xxxl modifier; introduces .btn-underline utility; adds pixel-trash icon template branch; updates avatar size documentation.
Profile Page Layout: Button Groups, Edit Sections, and Responsive Ordering
static/css/v3/user-profile-page.css
Refactors button styling to .user-profile__button-group .btn; adds "Edit Page Styling" section (two-column input grid, heading styles, spacing helpers, form/avatar/badge layouts); adds mobile-only order rules for deterministic stacking; updates responsive breakpoint syntax.

🎯 4 (Complex) | ⏱️ ~60 minutes

🐰 A profile editor springs to life,
With Markdown's dance and WYSIWYG might,
Fields nested deep in formsets tight,
Colors token-true, no fallback strife—
Behold the edit page, complete and right!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly corresponds to the main change: adding a user profile edit UI based on Story 2437.
Description check ✅ Passed The description includes a Figma link, change summary, screenshots with theme/responsive variants, and a completed self-review checklist; however, Risks & Considerations section remains empty.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jc/user-edit-ui

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

@jlchilders11 jlchilders11 requested a review from herzog0 June 9, 2026 18:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Caution

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

⚠️ Outside diff range comments (2)
frontend/wysiwyg-editor.js (1)

35-36: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Export additional functions required by user-profile-wysiwyg-editor.js.

The user-profile editor (imported at line 19 of user-profile-wysiwyg-editor.js) calls several functions from this module that are not exported:

  • parseMarkdownSafe (line 35) - called at lines 157, 199, 239, 267, 287
  • turndown (line 53) - used at lines 253, 308
  • isSafeUrl (line 87) - called at line 79
  • openModal (line 96) - called at line 75
  • highlightPreviewCodeBlocks (line 655) - called at lines 240, 288
  • renderMermaidPreview (line 669) - called at lines 241, 289

Without exporting these, the user-profile editor will crash with ReferenceError at runtime.

📤 Proposed fix
-function parseMarkdownSafe(md) {
+export function parseMarkdownSafe(md) {
   return DOMPurify.sanitize(marked.parse(md));
 }
 
-function sanitizeSvg(svgString) {
+export function sanitizeSvg(svgString) {
   return DOMPurify.sanitize(svgString, { USE_PROFILES: { svg: true, svgFilters: true }, ADD_TAGS: ["use"] });
 }

 // ... later in file ...

-const turndown = new TurndownService({
+export const turndown = new TurndownService({
   headingStyle: "atx",

 // ... later in file ...

-const isSafeUrl = (url) => {
+export const isSafeUrl = (url) => {
   try {

 // ... later in file ...

-const openModal = (title, fields) =>
+export const openModal = (title, fields) =>
   new Promise((resolve) => {

 // ... later in file ...

-const highlightPreviewCodeBlocks = (container) => {
+export const highlightPreviewCodeBlocks = (container) => {
   container.querySelectorAll("pre code[class*='language-']").forEach((codeEl) => {

 // ... later in file ...

-const renderMermaidPreview = async (container) => {
+export const renderMermaidPreview = async (container) => {
   const mermaidCodes = container.querySelectorAll("code.language-mermaid");

Also applies to: 53-62, 87-94, 96-164, 655-667, 669-695

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/wysiwyg-editor.js` around lines 35 - 36, The module currently
defines utility functions used by user-profile-wysiwyg-editor.js but doesn't
export them, causing runtime ReferenceErrors; update the file to export the
required functions—add named exports for parseMarkdownSafe, turndown, isSafeUrl,
openModal, highlightPreviewCodeBlocks, and renderMermaidPreview (or export them
together at the bottom with export { parseMarkdownSafe, turndown, isSafeUrl,
openModal, highlightPreviewCodeBlocks, renderMermaidPreview };), ensuring the
functions referenced (parseMarkdownSafe, turndown, isSafeUrl, openModal,
highlightPreviewCodeBlocks, renderMermaidPreview) remain defined and available
to imports.
templates/v3/includes/_avatar_v3.html (1)

31-31: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add explicit intrinsic dimensions for size="xxxl" images.

Line 31 still falls back to 40x40 for xxxl, while CSS renders 112x112. Add an xxxl branch so intrinsic size matches rendered size.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@templates/v3/includes/_avatar_v3.html` at line 31, The img tag rendering
avatar intrinsic dimensions falls back to 40x40 for size=="xxxl"; update the
width and height template expressions in the <img> that produces class
"avatar__img" to add an elif branch for size == 'xxxl' and return 112 for both
width and height so the intrinsic dimensions match the CSS-rendered 112x112.
♻️ Duplicate comments (2)
frontend/user-profile-wysiwyg-editor.js (1)

1-19: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove unused imports.

Several imports are not used in this file:

  • Line 19: handleKeyDown and handlePaste are imported but never called (reimplemented inline at lines 177-187 and 188-207)
  • Lines 3, 6-10: TipTap extensions CodeBlockLowlight, Table, TableRow, TableCell, TableHeader, and Image are imported but never configured in the editor (lines 166-171 only use StarterKit, Underline, TaskList, TaskItem)
🧹 Proposed fix
 import { Editor } from "`@tiptap/core`";
 import StarterKit from "`@tiptap/starter-kit`";
-import CodeBlockLowlight from "`@tiptap/extension-code-block-lowlight`";
 import Underline from "`@tiptap/extension-underline`";
-import Link from "`@tiptap/extension-link`";
-import Table from "`@tiptap/extension-table`";
-import TableRow from "`@tiptap/extension-table-row`";
-import TableCell from "`@tiptap/extension-table-cell`";
-import TableHeader from "`@tiptap/extension-table-header`";
-import Image from "`@tiptap/extension-image`";
 import TaskList from "`@tiptap/extension-task-list`";
 import TaskItem from "`@tiptap/extension-task-item`";
-import { common, createLowlight } from "lowlight";
-import { toHtml } from "hast-util-to-html";
-import { marked } from "marked";
-import DOMPurify from "dompurify";
-import TurndownService from "turndown";
-import { gfm } from "turndown-plugin-gfm";
-import {handleKeyDown, handlePaste, createToolbarButton, ICONS, setupMermaidEditMode, debounce} from './wysiwyg-editor';
+import { createToolbarButton, ICONS, setupMermaidEditMode, debounce } from './wysiwyg-editor';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/user-profile-wysiwyg-editor.js` around lines 1 - 19, Remove the
unused imports or wire them up: either delete the unused symbols
(CodeBlockLowlight, Table, TableRow, TableCell, TableHeader, Image,
handleKeyDown, handlePaste) from the top of
frontend/user-profile-wysiwyg-editor.js, or instead reuse the shared
implementations by calling the imported handleKeyDown and handlePaste functions
where inline handlers are currently implemented (lines ~177-207) and add the
missing TipTap extensions (CodeBlockLowlight, Table, TableRow, TableCell,
TableHeader, Image) into the Editor configuration (the array passed to new
Editor / extensions at ~166-171) so they are actually used; update imports
accordingly to keep only what the file uses.
users/forms.py (1)

207-227: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Instantiate nested formsets per V3UserProfileForm instance, not as class attributes.

link_formset and commit_email_formset are currently created at class scope (Line 239 and Line 282), and if commit_emails: (Line 218) skips rebuilding when an empty list is passed. That leaves shared/default state in place and will produce incorrect per-user formset state.

Suggested fix
 class V3UserProfileForm(forms.Form):
     def __init__(self, *args, **kwargs):
-        links = kwargs.pop("user_links", None)
-        commit_emails = kwargs.pop("commit_emails", None)
+        links = kwargs.pop("user_links", None) or {}
+        commit_emails = kwargs.pop("commit_emails", None) or []
         super().__init__(*args, **kwargs)
-        if links:
-            self.link_formset = V3ProfileLinkFormset(
-                initial=[
-                    {"type": x, "value": links.get(x, "")}
-                    for x in V3ProfileLinkChoices.values
-                ],
-            )
-        if commit_emails:
-            self.commit_email_formset = V3CommitEmailFormSet(
-                initial=[
-                    {
-                        "email": x,
-                    }
-                    for x in commit_emails
-                ],
-            )
+        self.link_formset = V3ProfileLinkFormset(
+            initial=[
+                {"type": x, "value": links.get(x, "")}
+                for x in V3ProfileLinkChoices.values
+            ]
+        )
+        self.commit_email_formset = V3CommitEmailFormSet(
+            initial=[{"email": x} for x in commit_emails]
+        )
@@
-    link_formset = V3ProfileLinkFormset(
-        initial=[{"type": x, "value": ""} for x in V3ProfileLinkChoices.values],
-    )
@@
-    commit_email_formset = V3CommitEmailFormSet(
-        initial=[
-            {
-                "email": "abc@example.com",
-            },
-        ],
-    )

Also applies to: 239-241, 282-288

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@users/forms.py` around lines 207 - 227, The V3UserProfileForm currently
leaves link_formset and commit_email_formset as class-level/shared defaults and
skips rebuilding when an empty list is passed; in V3UserProfileForm.__init__
always assign instance attributes self.link_formset and
self.commit_email_formset (not rely on class attributes) and construct them
unconditionally or when the corresponding kwargs is not None (use "if
commit_emails is not None" to allow empty lists), creating V3ProfileLinkFormset
(using V3ProfileLinkChoices.values for initial) and V3CommitEmailFormSet (using
commit_emails for initial) so each form instance has its own fresh formsets
instead of shared state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/user-profile-wysiwyg-editor.js`:
- Around line 89-91: The function handleDocClick in buildToolbar references
undefined variables tableWrapper and gridPopup; remove this dead code (delete
the handleDocClick definition and any registrations like
document.addEventListener('click', handleDocClick)) or, if behavior is required,
replace it with a safe implementation that uses existing toolbar elements
instead; ensure no leftover event listeners or references to
tableWrapper/gridPopup remain in buildToolbar.
- Line 21: Remove the unused lowlight initialization: delete the const lowlight
= createLowlight(common) line (and remove the createLowlight and common imports
if they are no longer used) because CodeBlockLowlight is not included in this
editor configuration; ensure no other references to lowlight or createLowlight
remain in the file (search for lowlight, createLowlight, and common) and run the
linter/TypeScript checks to confirm no unused-import errors.
- Around line 75-87: The Link action uses openModal and isSafeUrl but those
helpers are only defined (const isSafeUrl, const openModal) in another module
without exports, causing runtime failures; either export them from
frontend/wysiwyg-editor.js and import them into this file, or implement
equivalents locally in frontend/user-profile-wysiwyg-editor.js, then update the
Link command to call the imported/local openModal and isSafeUrl before calling
editor.chain().focus().setLink(...). Ensure the exported names match exactly:
openModal and isSafeUrl.
- Around line 157-160: The file references undefined symbols parseMarkdownSafe,
highlightPreviewCodeBlocks, renderMermaidPreview, and turndown causing
ReferenceErrors; fix by exporting those helpers (or a single helper that returns
turndown) from frontend/wysiwyg-editor.js and importing them into
frontend/user-profile-wysiwyg-editor.js, or alternatively implement equivalent
local versions in user-profile-wysiwyg-editor.js that match the behavior used
around initialContent parsing and preview rendering (update the catch block
usage of parseMarkdownSafe, the preview calls to
highlightPreviewCodeBlocks/renderMermaidPreview, and any turndown
instantiation/usage to use the imported or locally defined identifiers).

In `@templates/v3/includes/_field_checkbox.html`:
- Around line 16-21: Change the hard-coded id/for pair to use an optional
override variable so IDs can be unique when the partial is reused: update the
label's for and the input's id from "checkbox-{{ name }}" to use something like
"{{ input_id or ('checkbox-' ~ name) }}", so the template will accept an
explicit input_id (unique per option) but fall back to the existing name-based
id when not provided; then in multi-checkbox callers (e.g.,
checkboxselectmultiple loops) pass a unique input_id (forloop.counter0 or
similar) for each option.

In `@templates/v3/includes/_field_dropdown.html`:
- Around line 103-106: The dropdown trigger element with class
"dropdown__trigger" and role="button" currently keeps tabindex="0" even when the
template variable disabled is true; update the template so that when disabled is
true the element is not tabbable (remove tabindex or set tabindex="-1") and add
aria-disabled="true" to indicate non-interactive state for assistive tech; apply
the same change to the other occurrence around the second trigger (the block
referenced at line ~115) so both disabled triggers are non-focusable and
properly exposed as disabled.
- Line 95: The Alpine :disabled expression currently renders Python True/False
and breaks JS logic; change the serialization of the Django `disabled` context
used in the `:disabled` attribute to emit lowercase `true`/`false` (e.g. use the
template filter like `{{ disabled|default:"false"|lower }}` or `{{
disabled|yesno:"true,false" }}`) so the expression becomes `jsReady ||
true/false`; additionally, when the trigger is disabled ensure it is not
focusable and exposes state: add aria-disabled="true" and set tabindex="-1" when
`disabled` is true, and remove/restore tabindex/aria-disabled when false (update
the same template element that contains the `:disabled` attribute).

In `@templates/v3/includes/_field_include.html`:
- Line 15: The checkbox include is using bound_field.initial as the value which
doesn't set the checked state; update the include invocation in
_field_include.html to pass the current boolean state (bound_field.value) as the
checked argument (e.g., include 'v3/includes/_field_checkbox.html' with ...
checked=bound_field.value ...) so the checkbox reflects the bound field's actual
value; keep other parameters (name, label, required, disabled, help_text)
unchanged.

In `@templates/v3/user_profile_edit.html`:
- Line 28: The WYSIWYG include is wired to textarea_name='content' and
textarea_id='id_content' but the form field is user_profile_form.bio, so update
the include invocation (the call to v3/includes/_wysiwyg_editor.html) to use
textarea_name='bio' and textarea_id='id_bio' so the editor binds to
user_profile_form.bio; also search for any client-side code that references
id_content and update it to id_bio to keep JS submit/wiring working.
- Line 121: The included bound field name is misspelled: change the include that
references user_profile_form.ovverride_commit_author_email to use the correct
field name user_profile_form.override_commit_author_email so the template
renders and wires the override_commit_author_email field properly (update the
bound_field in the include call).

In `@users/forms.py`:
- Around line 250-261: These BooleanField profile checkboxes currently validate
as required and cause errors when left unchecked; update the field definitions
for hide_github, hide_ml, hide_ach, indicate_last_login_method,
override_commit_author_name, override_commit_author_email, and
allow_notification_terms_updated in users/forms.py to include required=False so
unchecked boxes are accepted as False, and keep existing labels/help_text
unchanged to preserve behavior.

In `@users/views.py`:
- Around line 108-109: The current check self.request.GET.get("edit", False)
treats any non-empty value (e.g. ?edit=false) as true; change to an explicit
parse such as reading val = self.request.GET.get("edit") and enabling edit mode
only when val is exactly "true" (case-insensitive) or when you intentionally
want presence semantics use "if 'edit' in self.request.GET" — update the
V3UserProfileForm branch and duplicate the same fix at the other occurrence (the
check around lines 419-420) so both locations use the same explicit comparison
(e.g., val and val.lower() == "true" or presence check).

---

Outside diff comments:
In `@frontend/wysiwyg-editor.js`:
- Around line 35-36: The module currently defines utility functions used by
user-profile-wysiwyg-editor.js but doesn't export them, causing runtime
ReferenceErrors; update the file to export the required functions—add named
exports for parseMarkdownSafe, turndown, isSafeUrl, openModal,
highlightPreviewCodeBlocks, and renderMermaidPreview (or export them together at
the bottom with export { parseMarkdownSafe, turndown, isSafeUrl, openModal,
highlightPreviewCodeBlocks, renderMermaidPreview };), ensuring the functions
referenced (parseMarkdownSafe, turndown, isSafeUrl, openModal,
highlightPreviewCodeBlocks, renderMermaidPreview) remain defined and available
to imports.

In `@templates/v3/includes/_avatar_v3.html`:
- Line 31: The img tag rendering avatar intrinsic dimensions falls back to 40x40
for size=="xxxl"; update the width and height template expressions in the <img>
that produces class "avatar__img" to add an elif branch for size == 'xxxl' and
return 112 for both width and height so the intrinsic dimensions match the
CSS-rendered 112x112.

---

Duplicate comments:
In `@frontend/user-profile-wysiwyg-editor.js`:
- Around line 1-19: Remove the unused imports or wire them up: either delete the
unused symbols (CodeBlockLowlight, Table, TableRow, TableCell, TableHeader,
Image, handleKeyDown, handlePaste) from the top of
frontend/user-profile-wysiwyg-editor.js, or instead reuse the shared
implementations by calling the imported handleKeyDown and handlePaste functions
where inline handlers are currently implemented (lines ~177-207) and add the
missing TipTap extensions (CodeBlockLowlight, Table, TableRow, TableCell,
TableHeader, Image) into the Editor configuration (the array passed to new
Editor / extensions at ~166-171) so they are actually used; update imports
accordingly to keep only what the file uses.

In `@users/forms.py`:
- Around line 207-227: The V3UserProfileForm currently leaves link_formset and
commit_email_formset as class-level/shared defaults and skips rebuilding when an
empty list is passed; in V3UserProfileForm.__init__ always assign instance
attributes self.link_formset and self.commit_email_formset (not rely on class
attributes) and construct them unconditionally or when the corresponding kwargs
is not None (use "if commit_emails is not None" to allow empty lists), creating
V3ProfileLinkFormset (using V3ProfileLinkChoices.values for initial) and
V3CommitEmailFormSet (using commit_emails for initial) so each form instance has
its own fresh formsets instead of shared state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4f3a887f-0e3b-46eb-816b-1eba3b5f28d3

📥 Commits

Reviewing files that changed from the base of the PR and between f71f5d2 and 20ec3b3.

📒 Files selected for processing (19)
  • frontend/user-profile-wysiwyg-editor.js
  • frontend/wysiwyg-editor.js
  • package.json
  • static/css/v3/avatar.css
  • static/css/v3/buttons.css
  • static/css/v3/forms.css
  • static/css/v3/user-profile-page.css
  • static/css/v3/wysiwyg-editor.css
  • static/js/v3/user-profile-wysiwyg-editor.js
  • templates/includes/icon.html
  • templates/v3/includes/_avatar_v3.html
  • templates/v3/includes/_field_checkbox.html
  • templates/v3/includes/_field_dropdown.html
  • templates/v3/includes/_field_include.html
  • templates/v3/includes/_form_include.html
  • templates/v3/includes/_wysiwyg_editor.html
  • templates/v3/user_profile_edit.html
  • users/forms.py
  • users/views.py

import { gfm } from "turndown-plugin-gfm";
import {handleKeyDown, handlePaste, createToolbarButton, ICONS, setupMermaidEditMode, debounce} from './wysiwyg-editor';

const lowlight = createLowlight(common);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove unused lowlight initialization.

The lowlight constant is created but never used, since CodeBlockLowlight is not included in this editor's configuration.

🧹 Proposed fix
-const lowlight = createLowlight(common);
-
 const editorInstances = new Map();
📝 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
const lowlight = createLowlight(common);
const editorInstances = new Map();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/user-profile-wysiwyg-editor.js` at line 21, Remove the unused
lowlight initialization: delete the const lowlight = createLowlight(common) line
(and remove the createLowlight and common imports if they are no longer used)
because CodeBlockLowlight is not included in this editor configuration; ensure
no other references to lowlight or createLowlight remain in the file (search for
lowlight, createLowlight, and common) and run the linter/TypeScript checks to
confirm no unused-import errors.

Comment on lines +75 to +87
const result = await openModal("Insert Link", [
{ name: "url", label: "URL", type: "url", placeholder: "https://example.com" },
]);
if (!result || !result.url) return;
if (!isSafeUrl(result.url)) {
window.alert("Only http, https, and mailto URLs are allowed.");
return;
}
editor.chain().focus().setLink({ href: result.url }).run();
},
isActive: () => editor.isActive("link"),
})
);

Copy link
Copy Markdown

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

🏁 Script executed:

#!/bin/bash
# Verify that openModal and isSafeUrl are not exported from wysiwyg-editor.js

echo "=== Checking if openModal is exported ==="
rg -n "export.*openModal" frontend/wysiwyg-editor.js || echo "Not found as export"

echo ""
echo "=== Checking if isSafeUrl is exported ==="
rg -n "export.*isSafeUrl" frontend/wysiwyg-editor.js || echo "Not found as export"

echo ""
echo "=== Checking where openModal is defined ==="
rg -n "^(const|function|export const|export function)\s+openModal" frontend/wysiwyg-editor.js

echo ""
echo "=== Checking where isSafeUrl is defined ==="
rg -n "^(const|function|export const|export function)\s+isSafeUrl" frontend/wysiwyg-editor.js

Repository: boostorg/website-v2

Length of output: 350


Export/import openModal and isSafeUrl for the Link action to avoid runtime failures

frontend/user-profile-wysiwyg-editor.js (lines 75-87) uses openModal and isSafeUrl, but frontend/wysiwyg-editor.js only defines them (const isSafeUrl ... at 87 and const openModal ... at 96) and has no corresponding named exports, so these identifiers won’t be available in this module’s scope when the Link command runs.

Export openModal/isSafeUrl from frontend/wysiwyg-editor.js and import them here, or implement those helpers locally in frontend/user-profile-wysiwyg-editor.js.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/user-profile-wysiwyg-editor.js` around lines 75 - 87, The Link
action uses openModal and isSafeUrl but those helpers are only defined (const
isSafeUrl, const openModal) in another module without exports, causing runtime
failures; either export them from frontend/wysiwyg-editor.js and import them
into this file, or implement equivalents locally in
frontend/user-profile-wysiwyg-editor.js, then update the Link command to call
the imported/local openModal and isSafeUrl before calling
editor.chain().focus().setLink(...). Ensure the exported names match exactly:
openModal and isSafeUrl.

Comment on lines +89 to +91
const handleDocClick = (e) => {
if (!tableWrapper.contains(e.target)) gridPopup.style.display = "none";
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Remove dead code referencing undefined variables.

This handleDocClick function references tableWrapper and gridPopup which are not defined anywhere in the buildToolbar function scope. This appears to be leftover code from the full editor that wasn't cleaned up during the simplification.

🗑️ Proposed fix
   });
 
-  const handleDocClick = (e) => {
-    if (!tableWrapper.contains(e.target)) gridPopup.style.display = "none";
-  };
-
   const mdBtn = document.createElement("button");
📝 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
const handleDocClick = (e) => {
if (!tableWrapper.contains(e.target)) gridPopup.style.display = "none";
};
});
const mdBtn = document.createElement("button");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/user-profile-wysiwyg-editor.js` around lines 89 - 91, The function
handleDocClick in buildToolbar references undefined variables tableWrapper and
gridPopup; remove this dead code (delete the handleDocClick definition and any
registrations like document.addEventListener('click', handleDocClick)) or, if
behavior is required, replace it with a safe implementation that uses existing
toolbar elements instead; ensure no leftover event listeners or references to
tableWrapper/gridPopup remain in buildToolbar.

Comment on lines +157 to +160
initialContent = parseMarkdownSafe(initialContent);
} catch (_) {
initialContent = rawContent;
}

Copy link
Copy Markdown

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

🏁 Script executed:

#!/bin/bash
# Check if these required functions are exported from wysiwyg-editor.js

echo "=== Checking exports ==="
for func in parseMarkdownSafe highlightPreviewCodeBlocks renderMermaidPreview turndown; do
  echo "Checking $func:"
  rg -n "export.*(const|function)\s+$func" frontend/wysiwyg-editor.js || echo "  ❌ Not exported"
done

echo ""
echo "=== Where they are defined in wysiwyg-editor.js ==="
rg -n "^(const|function)\s+(parseMarkdownSafe|highlightPreviewCodeBlocks|renderMermaidPreview|turndown)" frontend/wysiwyg-editor.js

Repository: boostorg/website-v2

Length of output: 514


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== user-profile-wysiwyg-editor.js imports/exports near top ==="
sed -n '1,220p' frontend/user-profile-wysiwyg-editor.js | nl -ba | sed -n '1,220p'

echo ""
echo "=== user-profile-wysiwyg-editor.js: locate references ==="
rg -n "\bparseMarkdownSafe\b|\bhighlightPreviewCodeBlocks\b|\brenderMermaidPreview\b|\bturndown\b" frontend/user-profile-wysiwyg-editor.js -n

echo ""
echo "=== user-profile-wysiwyg-editor.js: check if identifiers are defined locally ==="
rg -n "function\s+parseMarkdownSafe|const\s+parseMarkdownSafe|let\s+parseMarkdownSafe|var\s+parseMarkdownSafe" frontend/user-profile-wysiwyg-editor.js || true
rg -n "function\s+highlightPreviewCodeBlocks|const\s+highlightPreviewCodeBlocks|let\s+highlightPreviewCodeBlocks|var\s+highlightPreviewCodeBlocks" frontend/user-profile-wysiwyg-editor.js || true
rg -n "function\s+renderMermaidPreview|const\s+renderMermaidPreview|let\s+renderMermaidPreview|var\s+renderMermaidPreview" frontend/user-profile-wysiwyg-editor.js || true
rg -n "const\s+turndown|let\s+turndown|var\s+turndown" frontend/user-profile-wysiwyg-editor.js || true

echo ""
echo "=== wysiwyg-editor.js: check exports mechanism ==="
rg -n "export\s+(default\s+)?|module\.exports|exports\." frontend/wysiwyg-editor.js

echo ""
echo "=== wysiwyg-editor.js: show definitions for referenced identifiers ==="
rg -n "function\s+parseMarkdownSafe|const\s+turndown\s*=|const\s+highlightPreviewCodeBlocks\s*=|const\s+renderMermaidPreview\s*=" frontend/wysiwyg-editor.js -n

echo ""
echo "=== wysiwyg-editor.js: show surrounding code for the definitions ==="
for line in 35 53 655 669; do
  echo "--- context around line $line ---"
  sed -n "$((line-5)),$((line+20))p" frontend/wysiwyg-editor.js | nl -ba | sed -n '1,80p'
done

Repository: boostorg/website-v2

Length of output: 188


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== user-profile-wysiwyg-editor.js: show first ~240 lines with line numbers ==="
awk '{printf "%d:%s\n", NR, $0}' frontend/user-profile-wysiwyg-editor.js | sed -n '1,240p'

echo ""
echo "=== user-profile-wysiwyg-editor.js: locate references to the identifiers ==="
rg -n "\bparseMarkdownSafe\b|\bhighlightPreviewCodeBlocks\b|\brenderMermaidPreview\b|\bturndown\b" frontend/user-profile-wysiwyg-editor.js

echo ""
echo "=== user-profile-wysiwyg-editor.js: locate any imports for wysiwyg-editor.js ==="
rg -n "from\s+['\"].*wysiwyg-editor['\"]|require\(['\"].*wysiwyg-editor['\"]\)" frontend/user-profile-wysiwyg-editor.js || true

echo ""
echo "=== user-profile-wysiwyg-editor.js: check whether any of these identifiers are declared locally ==="
rg -n "(function|const|let|var)\s+parseMarkdownSafe\b" frontend/user-profile-wysiwyg-editor.js || true
rg -n "(function|const|let|var)\s+highlightPreviewCodeBlocks\b" frontend/user-profile-wysiwyg-editor.js || true
rg -n "(function|const|let|var)\s+renderMermaidPreview\b" frontend/user-profile-wysiwyg-editor.js || true
rg -n "(const|let|var)\s+turndown\b" frontend/user-profile-wysiwyg-editor.js || true

echo ""
echo "=== wysiwyg-editor.js: check how these identifiers are declared and exported ==="
rg -n "function\s+parseMarkdownSafe|const\s+turndown\s*=|const\s+highlightPreviewCodeBlocks\s*=|const\s+renderMermaidPreview\s*=" frontend/wysiwyg-editor.js

echo ""
echo "=== wysiwyg-editor.js: check for exports of these identifiers ==="
rg -n "export\s*\{[^}]*\b(parseMarkdownSafe|highlightPreviewCodeBlocks|renderMermaidPreview|turndown)\b[^}]*\}|export\s+(const|function)\s+(parseMarkdownSafe|highlightPreviewCodeBlocks|renderMermaidPreview|turndown)\b|module\.exports|exports\." frontend/wysiwyg-editor.js || true

echo ""
echo "=== wysiwyg-editor.js: show context around each definition/export area ==="
for start in 1 20 40 620 640 650 660 670; do
  :
done

for line in 35 53 655 669; do
  echo "--- context around wysiwyg-editor.js:$line ---"
  awk -v s="$((line-10))" -v e="$((line+25))" 'NR>=s && NR<=e {printf "%d:%s\n", NR, $0}' frontend/wysiwyg-editor.js
done

Repository: boostorg/website-v2

Length of output: 15757


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== user-profile-wysiwyg-editor.js: check for any local declarations (whole file) ==="
rg -n "(function|const|let|var)\s+parseMarkdownSafe\b" frontend/user-profile-wysiwyg-editor.js || true
rg -n "(function|const|let|var)\s+highlightPreviewCodeBlocks\b" frontend/user-profile-wysiwyg-editor.js || true
rg -n "(function|const|let|var)\s+renderMermaidPreview\b" frontend/user-profile-wysiwyg-editor.js || true
rg -n "(const|let|var)\s+turndown\b" frontend/user-profile-wysiwyg-editor.js || true

echo ""
echo "=== user-profile-wysiwyg-editor.js: show context around reported usages (150-220) ==="
awk 'NR>=140 && NR<=220 {printf "%d:%s\n", NR, $0}' frontend/user-profile-wysiwyg-editor.js

echo ""
echo "=== user-profile-wysiwyg-editor.js: show context around reported usages (230-320) ==="
awk 'NR>=220 && NR<=340 {printf "%d:%s\n", NR, $0}' frontend/user-profile-wysiwyg-editor.js

echo ""
echo "=== wysiwyg-editor.js: show exports section (if any) ==="
rg -n "export\s*\{|export\s+default|module\.exports|exports\." frontend/wysiwyg-editor.js || true

Repository: boostorg/website-v2

Length of output: 8029


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for global assignments of these identifiers ==="
for sym in parseMarkdownSafe highlightPreviewCodeBlocks renderMermaidPreview turndown; do
  echo "--- $sym ---"
  rg -n "globalThis\.${sym}|window\.${sym}|global\.\${sym}|self\.${sym}" -S . || true
done

echo ""
echo "=== Search for re-exports from wysiwyg-editor.js ==="
rg -n "wysiwyg-editor['\"]" frontend -S || true
rg -n "export\s*\{[^}]*parseMarkdownSafe|export\s*\{[^}]*highlightPreviewCodeBlocks|export\s*\{[^}]*renderMermaidPreview|export\s*\{[^}]*turndown" frontend/wysiwyg-editor.js -S || true

echo ""
echo "=== Search for any import specifiers of parseMarkdownSafe etc from wysiwyg-editor ==="
rg -n "from\s+['\"].*wysiwyg-editor['\"]" frontend/user-profile-wysiwyg-editor.js -n
rg -n "parseMarkdownSafe|highlightPreviewCodeBlocks|renderMermaidPreview|turndown" frontend/user-profile-wysiwyg-editor.js -n

Repository: boostorg/website-v2

Length of output: 2403


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Check for any ES-module exports of these symbols in wysiwyg-editor.js
echo "=== wysiwyg-editor.js: export statements referencing these symbols ==="
for sym in parseMarkdownSafe highlightPreviewCodeBlocks renderMermaidPreview turndown; do
  echo "--- $sym ---"
  rg -n "export\s+(const|function)\s+$sym\b" frontend/wysiwyg-editor.js || true
  rg -n "export\s*\{[^}]*\b$sym\b[^}]*\}" frontend/wysiwyg-editor.js || true
  rg -n "\bexport\s*\*\s*from\s*['\"][^'\"]*['\"]" frontend/wysiwyg-editor.js || true
done

echo ""
echo "=== wysiwyg-editor.js: all occurrences of 'export' (context) ==="
rg -n "\bexport\b" frontend/wysiwyg-editor.js

echo ""
# 2) Check for global/window assignments in frontend for these symbols
echo "=== frontend/: global assignments for these symbols ==="
for sym in parseMarkdownSafe highlightPreviewCodeBlocks renderMermaidPreview turndown; do
  echo "--- $sym ---"
  rg -n "globalThis\.${sym}\b|window\.${sym}\b|global\.${sym}\b|self\.${sym}\b" frontend -S || true
done

echo ""
echo "=== user-profile-wysiwyg-editor.js: any imports of these symbols from wysiwyg-editor ==="
rg -n "from\s+['\"]\./wysiwyg-editor['\"]" frontend/user-profile-wysiwyg-editor.js
rg -n "import\s*\{[^}]*\b(parseMarkdownSafe|highlightPreviewCodeBlocks|renderMermaidPreview|turndown)\b[^}]*\}\s*from\s+['\"]\./wysiwyg-editor['\"]" frontend/user-profile-wysiwyg-editor.js || true

echo ""
echo "=== user-profile-wysiwyg-editor.js: whether these symbols are declared locally ==="
for sym in parseMarkdownSafe highlightPreviewCodeBlocks renderMermaidPreview turndown; do
  rg -n "(function|const|let|var)\s+$sym\b" frontend/user-profile-wysiwyg-editor.js || true
done

Repository: boostorg/website-v2

Length of output: 1010


Fix undefined identifiers in user-profile-wysiwyg-editor.js (will crash with ReferenceError)

frontend/user-profile-wysiwyg-editor.js calls parseMarkdownSafe(), highlightPreviewCodeBlocks(), renderMermaidPreview(), and uses turndown, but none of these are imported or defined in this file. They are defined in frontend/wysiwyg-editor.js but are not exported, so these references will throw at runtime when the code paths run.

Snippet at issue location (final state):

      initialContent = parseMarkdownSafe(initialContent);
    } catch (_) {
      initialContent = rawContent;
    }

Impacted identifiers/usages:

  • parseMarkdownSafe() (157, 199, 239, 267, 287)
  • highlightPreviewCodeBlocks() (240, 288)
  • renderMermaidPreview() (241, 289)
  • turndown (253, 308)

Solution: Export parseMarkdownSafe, highlightPreviewCodeBlocks, renderMermaidPreview, and turndown (or a helper that returns turndown) from frontend/wysiwyg-editor.js and import them here, or implement/instantiate the same logic locally in frontend/user-profile-wysiwyg-editor.js.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/user-profile-wysiwyg-editor.js` around lines 157 - 160, The file
references undefined symbols parseMarkdownSafe, highlightPreviewCodeBlocks,
renderMermaidPreview, and turndown causing ReferenceErrors; fix by exporting
those helpers (or a single helper that returns turndown) from
frontend/wysiwyg-editor.js and importing them into
frontend/user-profile-wysiwyg-editor.js, or alternatively implement equivalent
local versions in user-profile-wysiwyg-editor.js that match the behavior used
around initialContent parsing and preview rendering (update the catch block
usage of parseMarkdownSafe, the preview calls to
highlightPreviewCodeBlocks/renderMermaidPreview, and any turndown
instantiation/usage to use the imported or locally defined identifiers).

Comment on lines +16 to +21
<label class="checkbox{% if disabled %} checkbox--disabled{% endif %}{% if extra_class %} {{ extra_class }}{% endif %}"
for="checkbox-{{ name }}">
<input class="checkbox__input"
type="checkbox"
id="checkbox-{{ name }}"
name="{{ name }}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make checkbox IDs overridable to avoid duplicate IDs in multi-checkbox use.

id="checkbox-{{ name }}" is not unique when this partial is reused in loops (e.g., checkboxselectmultiple), which breaks label targeting and produces invalid HTML.

🔧 Proposed fix
-  <label class="checkbox{% if disabled %} checkbox--disabled{% endif %}{% if extra_class %} {{ extra_class }}{% endif %}"
-        for="checkbox-{{ name }}">
+  <label class="checkbox{% if disabled %} checkbox--disabled{% endif %}{% if extra_class %} {{ extra_class }}{% endif %}"
+        for="{{ input_id|default:'checkbox-'|add:name }}">
     <input class="checkbox__input"
           type="checkbox"
-          id="checkbox-{{ name }}"
+          id="{{ input_id|default:'checkbox-'|add:name }}"
           name="{{ name }}"

And in the multi-checkbox caller, pass a unique input_id per option (for example using forloop.counter0).

📝 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
<label class="checkbox{% if disabled %} checkbox--disabled{% endif %}{% if extra_class %} {{ extra_class }}{% endif %}"
for="checkbox-{{ name }}">
<input class="checkbox__input"
type="checkbox"
id="checkbox-{{ name }}"
name="{{ name }}"
<label class="checkbox{% if disabled %} checkbox--disabled{% endif %}{% if extra_class %} {{ extra_class }}{% endif %}"
for="{{ input_id|default:'checkbox-'|add:name }}">
<input class="checkbox__input"
type="checkbox"
id="{{ input_id|default:'checkbox-'|add:name }}"
name="{{ name }}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@templates/v3/includes/_field_checkbox.html` around lines 16 - 21, Change the
hard-coded id/for pair to use an optional override variable so IDs can be unique
when the partial is reused: update the label's for and the input's id from
"checkbox-{{ name }}" to use something like "{{ input_id or ('checkbox-' ~ name)
}}", so the template will accept an explicit input_id (unique per option) but
fall back to the existing name-based id when not provided; then in
multi-checkbox callers (e.g., checkboxselectmultiple loops) pass a unique
input_id (forloop.counter0 or similar) for each option.

{% elif type == 'text' %}
{% include 'v3/includes/_field_text.html' with name=bound_field.name value=bound_field.initial type='text' label=bound_field.label required=field.required disabled=field.disabled help_text=field.help_text placeholder=widget.attrs.placeholder only %}
{% elif type == 'checkbox' %}
{% include 'v3/includes/_field_checkbox.html' with name=bound_field.name value=bound_field.initial label=bound_field.label required=field.required disabled=field.disabled help_text=field.help_text only %}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Bind checkbox checked state from bound_field.value.

Line 15 passes initial as value but never sets checked, so boolean fields can render unchecked even when true.

🔧 Proposed fix
-    {% include 'v3/includes/_field_checkbox.html' with name=bound_field.name value=bound_field.initial label=bound_field.label required=field.required disabled=field.disabled help_text=field.help_text only %}
+    {% include 'v3/includes/_field_checkbox.html' with name=bound_field.name label=bound_field.label required=field.required disabled=field.disabled checked=bound_field.value help_text=field.help_text only %}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@templates/v3/includes/_field_include.html` at line 15, The checkbox include
is using bound_field.initial as the value which doesn't set the checked state;
update the include invocation in _field_include.html to pass the current boolean
state (bound_field.value) as the checked argument (e.g., include
'v3/includes/_field_checkbox.html' with ... checked=bound_field.value ...) so
the checkbox reflects the bound field's actual value; keep other parameters
(name, label, required, disabled, help_text) unchanged.

</div>
<hr class="card__hr" aria-hidden="true" />
<div class="card__column px-large">
{% include 'v3/includes/_wysiwyg_editor.html' with textarea_id='id_content' textarea_name='content' label='Biography' help_text='This text field supports Markdown and this content is what will appear on your public profile' only %}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Bind the WYSIWYG biography editor to user_profile_form.bio instead of content.

Line 28 hardcodes textarea_name='content', while the backing form defines bio. This breaks cross-layer field mapping and will drop/skip bio when wiring submit handling.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@templates/v3/user_profile_edit.html` at line 28, The WYSIWYG include is wired
to textarea_name='content' and textarea_id='id_content' but the form field is
user_profile_form.bio, so update the include invocation (the call to
v3/includes/_wysiwyg_editor.html) to use textarea_name='bio' and
textarea_id='id_bio' so the editor binds to user_profile_form.bio; also search
for any client-side code that references id_content and update it to id_bio to
keep JS submit/wiring working.

{% include 'v3/includes/_field_include.html' with bound_field=user_profile_form.country %}
{% include 'v3/includes/_field_include.html' with bound_field=user_profile_form.indicate_last_login_method %}
{% include 'v3/includes/_field_include.html' with bound_field=user_profile_form.override_commit_author_name %}
{% include 'v3/includes/_field_include.html' with bound_field=user_profile_form.ovverride_commit_author_email %}

@coderabbitai coderabbitai Bot Jun 9, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix misspelled bound field name for commit-author email override.

Line 121 uses ovverride_commit_author_email, but the form field is override_commit_author_email. This leaves the field unrendered/miswired.

Suggested fix
-            {% include 'v3/includes/_field_include.html' with bound_field=user_profile_form.ovverride_commit_author_email %}
+            {% include 'v3/includes/_field_include.html' with bound_field=user_profile_form.override_commit_author_email %}
📝 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
{% include 'v3/includes/_field_include.html' with bound_field=user_profile_form.ovverride_commit_author_email %}
{% include 'v3/includes/_field_include.html' with bound_field=user_profile_form.override_commit_author_email %}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@templates/v3/user_profile_edit.html` at line 121, The included bound field
name is misspelled: change the include that references
user_profile_form.ovverride_commit_author_email to use the correct field name
user_profile_form.override_commit_author_email so the template renders and wires
the override_commit_author_email field properly (update the bound_field in the
include call).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@jlchilders11 I think this is a good one to fix

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment thread users/forms.py
Comment on lines +250 to +261
hide_github = forms.BooleanField(
label="Hide GitHub activity from your profile",
help_text="Links your login to an existing commit-author email after verification",
)
hide_ml = forms.BooleanField(
label="Hide mailing list activity from your profile",
help_text="Links your login to an existing commit-author email after verification",
)
hide_ach = forms.BooleanField(
label="Hide achievements & badges from your profile",
help_text="Links your login to an existing commit-author email after verification",
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the file
ls -la users/forms.py

# Show the relevant sections with line numbers
sed -n '220,330p' users/forms.py | nl -ba | sed -n '1,130p'

# Also search for BooleanField definitions in the file
rg -n "forms\.BooleanField\(" users/forms.py

# Extract any occurrences that mention required= or validation around these booleans
rg -n "required\s*=" users/forms.py

Repository: boostorg/website-v2

Length of output: 189


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== File =="
ls -la users/forms.py

echo "== Lines 235-315 (with line numbers) =="
sed -n '235,315p' users/forms.py | cat -n

echo "== BooleanField occurrences =="
rg -n "forms\.BooleanField\(" users/forms.py

echo "== Any required= usage nearby =="
rg -n "required\s*=" users/forms.py

Repository: boostorg/website-v2

Length of output: 4157


🌐 Web query:

Django forms.BooleanField default required value

💡 Result:

In Django forms, BooleanField has required=True by default (inherited from Field), so an “empty” value causes a required-field ValidationError unless you set required=False [1][2]. For the checkbox behavior itself, BooleanField normalizes values to Python True/False and defines an “empty value” of False [3]. That means when the checkbox isn’t checked (i.e., the field is omitted from POST data), it’s treated as False; however, whether that’s allowed depends on required: - required=True (default): the field validates that the value is True, so unchecked (False) fails validation [1][3]. - required=False: unchecked (False) is accepted (cleaned_data will contain False) [1][3]. So the “required value” question is: BooleanField’s default behavior is required=True (must be checked / True), and its empty value is False [1][3].

Citations:


Set required=False for these unchecked profile checkboxes to avoid validation errors

In users/forms.py, these forms.BooleanField fields don’t specify required=False, so leaving a checkbox unchecked (False) is treated as an empty value and raises a validation error instead of accepting False:

  • hide_github, hide_ml, hide_ach (lines ~250-261)
  • indicate_last_login_method, override_commit_author_name, override_commit_author_email (lines ~271-279)
  • allow_notification_terms_updated (lines ~303-305)
Suggested fix
     hide_github = forms.BooleanField(
         label="Hide GitHub activity from your profile",
         help_text="Links your login to an existing commit-author email after verification",
+        required=False,
     )
@@
     hide_ml = forms.BooleanField(
         label="Hide mailing list activity from your profile",
         help_text="Links your login to an existing commit-author email after verification",
+        required=False,
     )
@@
     hide_ach = forms.BooleanField(
         label="Hide achievements & badges from your profile",
         help_text="Links your login to an existing commit-author email after verification",
+        required=False,
     )
@@
     indicate_last_login_method = forms.BooleanField(
-        help_text="The login page will indicate the last method you used to login"
+        help_text="The login page will indicate the last method you used to login",
+        required=False,
     )
@@
     override_commit_author_name = forms.BooleanField(
-        help_text="Globally replaces your git commit author name with username value set above"
+        help_text="Globally replaces your git commit author name with username value set above",
+        required=False,
     )
@@
     override_commit_author_email = forms.BooleanField(
-        help_text="Links your login to an existing commit-author email after verification"
+        help_text="Links your login to an existing commit-author email after verification",
+        required=False,
     )
@@
     allow_notification_terms_updated = forms.BooleanField(
-        label="The sites terms of use or privacy policy are changed"
+        label="The sites terms of use or privacy policy are changed",
+        required=False,
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@users/forms.py` around lines 250 - 261, These BooleanField profile checkboxes
currently validate as required and cause errors when left unchecked; update the
field definitions for hide_github, hide_ml, hide_ach,
indicate_last_login_method, override_commit_author_name,
override_commit_author_email, and allow_notification_terms_updated in
users/forms.py to include required=False so unchecked boxes are accepted as
False, and keep existing labels/help_text unchanged to preserve behavior.

Comment thread users/views.py
Comment on lines +108 to +109
if self.request.GET.get("edit", False):
ctx["user_profile_form"] = V3UserProfileForm(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

edit query handling is too permissive.

Line 108 and Line 419 treat any non-empty edit value as enabled, so ?edit=false still renders edit mode. Parse explicitly for "true" (or presence semantics) to match the intended route contract.

Also applies to: 419-420

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@users/views.py` around lines 108 - 109, The current check
self.request.GET.get("edit", False) treats any non-empty value (e.g.
?edit=false) as true; change to an explicit parse such as reading val =
self.request.GET.get("edit") and enabling edit mode only when val is exactly
"true" (case-insensitive) or when you intentionally want presence semantics use
"if 'edit' in self.request.GET" — update the V3UserProfileForm branch and
duplicate the same fix at the other occurrence (the check around lines 419-420)
so both locations use the same explicit comparison (e.g., val and val.lower() ==
"true" or presence check).

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.

Webpage UI: Edit User Profile

3 participants