Skip to content

Story: 2437 User Profile Edit UI#2478

Merged
jlchilders11 merged 23 commits into
developfrom
jc/user-edit-ui
Jun 24, 2026
Merged

Story: 2437 User Profile Edit UI#2478
jlchilders11 merged 23 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
    • Added V3 “Edit Profile” page with avatar upload/replace/delete, rich biography editing (WYSIWYG + Markdown toggle), profile links, badge/privacy/notification controls, and delete-account flow.
    • Added in-editor Mermaid diagram support plus link/media insertion tooling.
  • Improvements
    • Introduced xxxl avatar size, new underline button styling, and improved checkbox/dropdown styling (including optional field help text).
    • Refined user profile edit page layout and mobile ordering; ensured v3 WYSIWYG auto-initializes on relevant pages.
  • Chores
    • Added a build step to bundle the V3 WYSIWYG editor.

@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

Comment thread frontend/user-profile-wysiwyg-editor.js Outdated

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 thread templates/v3/includes/_field_include.html
Comment thread templates/v3/includes/_field_dropdown.html Outdated
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 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, supporting CSS/design system enhancements for forms and avatars, and automatic editor initialization in news templates.

Changes

User Profile Edit Feature

Layer / File(s) Summary
WYSIWYG Editor Core: TipTap Integration with Toolbar, Markdown Mode, and Form Sync
frontend/user-profile-wysiwyg-editor.js
Initializes a TipTap editor with Turndown HTML-to-Markdown conversion rules, custom toolbar (bold/italic/underline, lists, link insertion with URL validation, undo/redo), Markdown editing/preview toggle, Tab handling in code blocks, paste conversion via parseMarkdownSafe, Mermaid diagram rendering, and form submission synchronization. Manages editor instance registry for cleanup. Includes autoInit function for discovering and initializing wrappers.
Editor Export Refactoring and Build Script
frontend/wysiwyg-editor.js, static/js/v3/wysiwyg-editor.js, package.json
Exports helper functions (parseMarkdownSafe, openModal, debounce, createToolbarButton, ICONS, setupMermaidEditMode, highlightPreviewCodeBlocks, renderMermaidPreview) from frontend/wysiwyg-editor.js for reuse. Makes initWysiwyg local. Refactors autoInit to accept optional elId and expose via window.autoInit. Updates bundled static module exports. Adds build:up-wysiwyg npm script for esbuild bundling with ESM output and external mermaid dependency.
Form Field Template Components: Checkbox, Dropdown, and 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 outer wrapper, optional id support, and optional help text; dropdown field with disabled state binding and conditional lock icon; generic field dispatcher routing widget types (email, text, checkbox, checkbox-select-multiple, select) to appropriate partials with UUID generation for multi-choice IDs; form wrapper iterating bound fields.
Form Styling: Token Refactoring, Help Text, and Checkbox Enhancements
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 (whitespace removal); adds .checkbox__multi-checkbox-column flex layout; introduces .wysiwyg-v3-container wrapper; sets WYSIWYG editor help text margin.
WYSIWYG Editor Template: Help Text Support and Initialization
templates/v3/includes/_wysiwyg_editor.html
Adds optional help_text variable rendering a help paragraph below the editor; adds outer wysiwyg-v3-container wrapper; refactors initialization to set jsReady and schedule autoInit() call on next tick.
Backend Forms: Profile, Links, and Commit Email Definitions
users/forms.py
Adds V3ProfileLinkChoices enum with disabled form and formset; V3CommitEmailForm and formset; V3UserProfileForm with nested formset population and profile fields (tagline, bio, role, identity, visibility toggles, notifications, terms acceptance).
View Logic: Template Routing and Edit Mode Context
users/views.py
Imports V3UserProfileForm; adds get_template_names() override selecting v3 edit template when ?edit is present; implements early-return edit context 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 WYSIWYG, links formset, role, avatar, badges, visibility, delete) and right-column Update Details, Commit Email (formset), Email Preferences, and Connections cards.
Design System Extensions: Avatar Size, Button Utilities, Icons
static/css/v3/avatar.css, static/css/v3/buttons.css, static/css/v3/user-profile.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 and removes conditional width/height; adds .user-profile__link-section-heading typography.
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.
Template Initialization Scripts: News Form and Create Page
templates/news/form.html, templates/news/v3/create.html
Adds inline <script> blocks to conditionally initialize WYSIWYG editors via autoInit(), guarding on document availability and choosing between immediate invocation or DOMContentLoaded event registration.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • herzog0
  • julhoang
  • kattyode
  • julioest

Poem

🐰 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 'Story: 2437 User Profile Edit UI' clearly and specifically identifies the main objective of the PR, which is to add an edit mode UI to the user profile.
Description check ✅ Passed The PR description covers key requirements including issue reference (#2437), a clear summary of what was added (edit mode UI with no edit functionality), Figma design link, localhost access link, detailed changes list, comprehensive self-review checklist completion, and multiple screenshots across themes and responsive layouts.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jc/user-edit-ui

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.

@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

Comment thread frontend/user-profile-wysiwyg-editor.js Outdated
Comment thread frontend/user-profile-wysiwyg-editor.js Outdated
Comment thread frontend/user-profile-wysiwyg-editor.js Outdated
Comment thread frontend/user-profile-wysiwyg-editor.js Outdated
Comment thread templates/v3/includes/_field_checkbox.html
Comment thread templates/v3/includes/_field_include.html Outdated
Comment thread templates/v3/user_profile_edit.html Outdated
Comment thread templates/v3/user_profile_edit.html Outdated
Comment thread users/forms.py
Comment thread users/views.py Outdated

@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: 5

🤖 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 15-61: The setupMermaidEditMode function references three
undefined identifiers that will cause runtime crashes: getMermaid (called within
the renderMermaid handler), mermaidIdCounter (incremented when rendering), and
sanitizeSvg (called to process SVG output). These are defined in
frontend/wysiwyg-editor.js but not exported. Either export all three from
frontend/wysiwyg-editor.js and add import statements at the top of
frontend/user-profile-wysiwyg-editor.js, or implement local equivalents of these
in the current file. Ensure that whichever approach is chosen, all three
identifiers are available in scope before setupMermaidEditMode is called.

In `@static/js/v3/wysiwyg-editor.js`:
- Line 247: The link/image modal created by the iE function lacks proper
semantic attributes and keyboard support. Add role="dialog" and
aria-modal="true" attributes to the modal overlay and modal container elements
respectively. Associate each label with its corresponding input by setting the
label's htmlFor attribute to match the input's id, and ensure each input has a
unique id. Track the previously focused element before the modal opens (store it
before appending to document.body) and restore focus to that element when the
modal closes by calling previousElement.focus() in the cleanup function d.
Implement Tab key trapping within the dialog by capturing Tab keydowns on the
modal, and preventing default behavior while cycling focus between the first and
last focusable elements (the inputs and buttons) using keyboard event handlers.
Additionally, make the table picker cells in the Pv function keyboard accessible
by converting the span elements to buttons with role="button" and implementing
keyboard event listeners to handle Enter and arrow keys for navigation and
selection instead of relying solely on click handlers.
- Line 247: The initialization code in the Uv function uses `n.value.trim()` and
a simplistic HTML detection check (`s.startsWith("<") && s.includes(">")`) that
incorrectly identifies Markdown constructs like `<https://example.com>` as HTML,
and loses meaningful whitespace from the original textarea value. To fix this,
remove the trim() call to preserve the original value's whitespace intact, and
replace the broad HTML detection logic with a more precise check that
distinguishes actual HTML (like `<!DOCTYPE`, `<html>`, `<div>`, etc.) from
Markdown patterns. This ensures content isn't corrupted during initial load and
that meaningful whitespace in code blocks or indentation is preserved through
the edit cycle.
- Line 247: The `dE` function (Mermaid loader) can initiate multiple CDN loads
concurrently if called while a load is already in flight, since it doesn't cache
the in-flight promise, and the debounced render function `n` in `Fv`
(setupMermaidEditMode) can have renders complete out of order, allowing older
diagram previews to overwrite newer ones. Fix this by caching the loader promise
in `dE` so that concurrent calls wait for the same in-flight load to complete,
and add a generation/version tracking mechanism in the debounced render function
in `Fv` so that stale render results are ignored if a newer render has been
initiated since they were queued.
- Line 247: The URL validation function `rE` currently accepts http, https, and
mailto protocols for both links and images, but images should only allow http
and https since mailto URLs create broken img elements. Create two separate
validator functions - one that accepts all three protocols for links and one
that only accepts http and https for images. Update the image insertion handler
(the onClick callback in the image button creation) to use the stricter
validator that rejects mailto, and update the Link extension configuration
(`xm.configure`) to include an `isAllowedUri` validator function that enforces
the same three-protocol policy to prevent bypassing the URL policy through
pasted or imported HTML content.
🪄 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: f6e1fcb8-7075-4dda-8628-c801f4a4fcc2

📥 Commits

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

📒 Files selected for processing (4)
  • frontend/user-profile-wysiwyg-editor.js
  • frontend/wysiwyg-editor.js
  • static/js/v3/user-profile-wysiwyg-editor.js
  • static/js/v3/wysiwyg-editor.js

Comment thread frontend/user-profile-wysiwyg-editor.js Outdated
Comment thread static/js/v3/wysiwyg-editor.js Outdated

@julhoang julhoang 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.

Hi @jlchilders11 , thanks for tackling this huge UI task! This is awesome work so far. I've left some styling discrepancies that I noticed here and there and also +1 to all of Teo's comments above 🙏

Comment thread static/css/v3/forms.css Outdated
Comment thread templates/v3/user_profile_edit.html Outdated
Comment on lines +58 to +60
{% else %}
{% include 'v3/includes/_button.html' with style='secondary' type='button' label='Choose File' only %}
{% 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.

Might we be able to add a form action to upload profile image in this PR, and the floaty "Replace Image" UI section too?
image

Comment thread templates/v3/includes/_field_dropdown.html Outdated
Comment on lines +69 to +70
{% include 'v3/includes/_badge_v3.html' with token='badge-tier-'|add:tier.tier label=tier.name|add:' Badge' size='large' %}
<span>{{ tier.name }} Badge</span>

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.

This badge should have an opacity of 0.45 when it is disabled.

Comment thread users/forms.py
Comment thread static/css/v3/user-profile-page.css Outdated
Comment thread templates/v3/user_profile_edit.html Outdated
Comment thread templates/v3/user_profile_edit.html
Comment thread users/views.py Outdated
Comment thread templates/v3/user_profile_edit.html Outdated

@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: 1

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 (2)

87-94: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing isSafeUrl export causes runtime crash in the Link insertion flow.

isSafeUrl is defined in frontend/wysiwyg-editor.js but not exported, while frontend/user-profile-wysiwyg-editor.js references it without importing. When a user clicks the Link button on the profile edit page and enters a URL, a ReferenceError will be thrown.

  • frontend/wysiwyg-editor.js#L87-L94: Export isSafeUrl by changing const isSafeUrl to export const isSafeUrl.
  • frontend/user-profile-wysiwyg-editor.js#L100-L113: Add isSafeUrl to the import statement on line 14.
🤖 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 87 - 94, The `isSafeUrl` function is
defined but not exported in `frontend/wysiwyg-editor.js` (lines 87-94), causing
a runtime ReferenceError when referenced in
`frontend/user-profile-wysiwyg-editor.js` (lines 100-113). In
`frontend/wysiwyg-editor.js`, change `const isSafeUrl` to `export const
isSafeUrl` to make the function exportable. In
`frontend/user-profile-wysiwyg-editor.js`, add `isSafeUrl` to the import
statement on line 14 to properly import the exported function from
wysiwyg-editor.js before it is used in the Link insertion flow.

697-702: ⚠️ Potential issue | 🔴 Critical

The removal of the initWysiwyg export breaks existing consumers that are already in the codebase.

The initWysiwyg function was exported from frontend/wysiwyg-editor.js but has been changed to a local const. However, templates/v3/examples/_v3_example_section_wysiwyg.html still dynamically imports and calls this function at lines 168 and 186:

import('/static/js/v3/wysiwyg-editor.js').then(m => m.initWysiwyg('id_demo_content'))

This will now fail with m.initWysiwyg is not a function. Either restore the export or update the HTML template to use the alternative autoInit pattern instead.

🤖 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 697 - 702, The initWysiwyg function
in frontend/wysiwyg-editor.js is declared as a local const but is still being
dynamically imported and called by
templates/v3/examples/_v3_example_section_wysiwyg.html at lines 168 and 186,
which will fail since the function is no longer exported. Either export the
initWysiwyg function from frontend/wysiwyg-editor.js to restore the export so it
can be imported by the HTML template, or update the HTML template's dynamic
import calls at those two locations to use the autoInit pattern instead,
whichever approach is preferred for the codebase.
🧹 Nitpick comments (2)
frontend/user-profile-wysiwyg-editor.js (1)

346-358: 💤 Low value

Unescaped elId in CSS selector may fail for IDs with special characters.

document.querySelector(\textarea[id=${elId}]`)will break ifelIdcontains CSS special characters like], [, or spaces. Use CSS.escape()orgetElementById()` instead.

♻️ Suggested fix using CSS.escape
 const autoInit = (elId) => {
   if (typeof document === "undefined" || !document.querySelector) return;
   if (elId && elId !== null) {
-    const ta = document.querySelector(`textarea[id=${elId}]`);
+    const ta = document.getElementById(elId);
     if (ta && ta.id) initWysiwyg(ta.id);
   }
🤖 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 346 - 358, In the
autoInit function, the elId parameter is directly interpolated into a CSS
selector without escaping, which will cause the querySelector to fail when elId
contains special CSS characters like spaces, brackets, or other special
characters. Fix this by wrapping the elId with CSS.escape() when constructing
the selector string in the querySelector call, so that any special characters in
the elId are properly escaped for use in CSS selectors.
frontend/wysiwyg-editor.js (1)

904-916: 💤 Low value

Same unescaped selector issue as in user-profile-wysiwyg-editor.js.

document.querySelector(\textarea[id=${elId}]`)will fail for IDs with special CSS characters. Usedocument.getElementById(elId)` for robustness.

♻️ Suggested fix
 const autoInit = (elId) => {
   if (typeof document === "undefined" || !document.querySelector) return;
   if (elId && elId !== null) {
-    const ta = document.querySelector(`textarea[id=${elId}]`);
+    const ta = document.getElementById(elId);
     if (ta && ta.id) initWysiwyg(ta.id);
   }
🤖 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 904 - 916, The querySelector method
in the autoInit function is using an unescaped template literal to construct a
CSS selector with the elId parameter, which will fail if the ID contains special
CSS characters. Replace the document.querySelector call that uses the template
literal `textarea[id=${elId}]` with document.getElementById(elId) for a more
robust and direct approach to finding the textarea element by its ID.
🤖 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 `@templates/news/form.html`:
- Around line 10-18: The direct calls to `autoInit` will fail with a
ReferenceError because ES6 module exports do not automatically create global
references. In templates/news/form.html lines 10-18 (anchor), replace the direct
`autoInit()` calls in the DOMContentLoaded event listener and the else branch
with a guarded callback that checks `typeof window.autoInit === 'function'`
before invoking it. In templates/news/v3/create.html lines 33-41 (sibling),
apply the same guarded callback pattern for the `autoInit` function calls, and
additionally add `{ once: true }` as the third parameter to the
`addEventListener` call to ensure the listener fires only once.

---

Outside diff comments:
In `@frontend/wysiwyg-editor.js`:
- Around line 87-94: The `isSafeUrl` function is defined but not exported in
`frontend/wysiwyg-editor.js` (lines 87-94), causing a runtime ReferenceError
when referenced in `frontend/user-profile-wysiwyg-editor.js` (lines 100-113). In
`frontend/wysiwyg-editor.js`, change `const isSafeUrl` to `export const
isSafeUrl` to make the function exportable. In
`frontend/user-profile-wysiwyg-editor.js`, add `isSafeUrl` to the import
statement on line 14 to properly import the exported function from
wysiwyg-editor.js before it is used in the Link insertion flow.
- Around line 697-702: The initWysiwyg function in frontend/wysiwyg-editor.js is
declared as a local const but is still being dynamically imported and called by
templates/v3/examples/_v3_example_section_wysiwyg.html at lines 168 and 186,
which will fail since the function is no longer exported. Either export the
initWysiwyg function from frontend/wysiwyg-editor.js to restore the export so it
can be imported by the HTML template, or update the HTML template's dynamic
import calls at those two locations to use the autoInit pattern instead,
whichever approach is preferred for the codebase.

---

Nitpick comments:
In `@frontend/user-profile-wysiwyg-editor.js`:
- Around line 346-358: In the autoInit function, the elId parameter is directly
interpolated into a CSS selector without escaping, which will cause the
querySelector to fail when elId contains special CSS characters like spaces,
brackets, or other special characters. Fix this by wrapping the elId with
CSS.escape() when constructing the selector string in the querySelector call, so
that any special characters in the elId are properly escaped for use in CSS
selectors.

In `@frontend/wysiwyg-editor.js`:
- Around line 904-916: The querySelector method in the autoInit function is
using an unescaped template literal to construct a CSS selector with the elId
parameter, which will fail if the ID contains special CSS characters. Replace
the document.querySelector call that uses the template literal
`textarea[id=${elId}]` with document.getElementById(elId) for a more robust and
direct approach to finding the textarea element by its ID.
🪄 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: 2be8655a-ab60-4885-8cc8-e6513d26a71e

📥 Commits

Reviewing files that changed from the base of the PR and between 46c8873 and a548c99.

📒 Files selected for processing (11)
  • frontend/user-profile-wysiwyg-editor.js
  • frontend/wysiwyg-editor.js
  • static/js/v3/user-profile-wysiwyg-editor.js
  • static/js/v3/wysiwyg-editor.js
  • templates/news/form.html
  • templates/news/v3/create.html
  • templates/v3/includes/_field_dropdown.html
  • templates/v3/includes/_field_include.html
  • templates/v3/includes/_wysiwyg_editor.html
  • templates/v3/user_profile_edit.html
  • users/views.py
✅ Files skipped from review due to trivial changes (1)
  • templates/v3/includes/_wysiwyg_editor.html
🚧 Files skipped from review as they are similar to previous changes (4)
  • templates/v3/includes/_field_include.html
  • templates/v3/includes/_field_dropdown.html
  • templates/v3/user_profile_edit.html
  • users/views.py

Comment thread templates/news/form.html Outdated

@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: 3

🤖 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 `@templates/v3/includes/_avatar_v3.html`:
- Line 31: The img tag in the avatar template has a duplicate class attribute
declaration. The second class attribute overrides the first, causing the size
modifier avatar--{{ size }} to be lost and breaking the avatar sizing CSS. Merge
both class declarations into a single class attribute that includes all three
class names: avatar, avatar--{{ size }}, and avatar__img, separated by spaces.

In `@templates/v3/includes/_field_include.html`:
- Line 17: Remove the `value=bound_field.value` parameter from the include
statement for _field_checkbox.html on line 17. Checkbox inputs use the `checked`
attribute (already present as `checked=bound_field.value`) to control their
boolean state, not the `value` attribute. Passing the Python boolean value
through the `value` parameter renders semantically incorrect HTML like
`value="True"` or `value="False"`. Keep all other parameters (name, label,
required, disabled, help_text, checked) intact and only delete the `value`
parameter and its value.

In `@templates/v3/includes/_wysiwyg_editor.html`:
- Around line 20-33: The wysiwyg-v3 editor template sets jsReady to true
immediately before autoInit() completes asynchronously, causing the fallback
textarea to be hidden via x-show="!jsReady" even if initialization fails. Move
the jsReady = true assignment to execute after autoInit() completes successfully
(likely within a callback or promise chain inside the autoInit() function itself
or in the x-init block after awaiting it), and change the hardcoded
:aria-hidden="true" attribute on the textarea to :aria-hidden="jsReady" so the
fallback textarea only becomes hidden from screen readers when initialization
actually succeeds. This ensures the textarea remains visible and accessible to
users if the editor fails to initialize.
🪄 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: 18263a56-5090-463e-aaab-134c9a9b541f

📥 Commits

Reviewing files that changed from the base of the PR and between a548c99 and e44cacf.

📒 Files selected for processing (9)
  • frontend/user-profile-wysiwyg-editor.js
  • frontend/wysiwyg-editor.js
  • static/css/v3/user-profile.css
  • static/css/v3/wysiwyg-editor.css
  • 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/_wysiwyg_editor.html
🚧 Files skipped from review as they are similar to previous changes (3)
  • templates/v3/includes/_field_dropdown.html
  • frontend/wysiwyg-editor.js
  • frontend/user-profile-wysiwyg-editor.js

Comment thread templates/v3/includes/_avatar_v3.html Outdated
Comment thread templates/v3/includes/_field_include.html Outdated
Comment on lines +20 to +33
<div class="wysiwyg-v3" data-wysiwyg="v3" x-data="{ jsReady: false }" x-init="
jsReady = true;
$nextTick(() => {
autoInit('{{textarea_id}}');
})"
>
<textarea
id="{{ textarea_id }}"
name="{{ textarea_name }}"
class="w-full min-h-[200px]"
placeholder=""
x-show="!jsReady"
:aria-hidden="true"
{% if disabled_expr %}:disabled="{{ disabled_expr }}"{% endif %}

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:

cd /tmp/repo 2>/dev/null || cd /root/codebase 2>/dev/null || pwd
find . -name "_wysiwyg_editor.html" -o -name "*wysiwyg*" -type f | head -20

Repository: boostorg/website-v2

Length of output: 369


🏁 Script executed:

git ls-files | grep -i wysiwyg | head -20

Repository: boostorg/website-v2

Length of output: 336


🏁 Script executed:

cat -n templates/v3/includes/_wysiwyg_editor.html | head -50

Repository: boostorg/website-v2

Length of output: 1965


🏁 Script executed:

cat -n static/js/v3/wysiwyg-editor.js | head -100

Repository: boostorg/website-v2

Length of output: 50376


🏁 Script executed:

rg "autoInit\s*=" static/js/v3/ -A 5 -B 2

Repository: boostorg/website-v2

Length of output: 25983


🏁 Script executed:

rg "autoInit" frontend/wysiwyg-editor.js -A 8 -B 2

Repository: boostorg/website-v2

Length of output: 524


🏁 Script executed:

rg "const initWysiwyg" frontend/wysiwyg-editor.js -A 20 | head -50

Repository: boostorg/website-v2

Length of output: 862


🏁 Script executed:

rg "window\.autoInit" static/js/v3/wysiwyg-editor.js -B 5 -A 5

Repository: boostorg/website-v2

Length of output: 45


🏁 Script executed:

grep -n "x-" templates/v3/includes/_wysiwyg_editor.html

Repository: boostorg/website-v2

Length of output: 323


🏁 Script executed:

rg "jsReady" templates/v3/includes/_wysiwyg_editor.html

Repository: boostorg/website-v2

Length of output: 333


Move fallback textarea visibility and accessibility to depend on successful editor initialization.

Lines 20–32 set jsReady = true before autoInit() completes. If initialization fails or autoInit is unavailable, the fallback textarea becomes hidden (via x-show="!jsReady") and inaccessible to screen readers (:aria-hidden="true" is always set), leaving users with no functional control.

Set jsReady = true only after initialization succeeds, and bind aria-hidden to the jsReady state:

Proposed fix
-  <div class="wysiwyg-v3" data-wysiwyg="v3" x-data="{ jsReady: false }" x-init="
-      jsReady = true;
-      $nextTick(() => {
-        autoInit('{{textarea_id}}');
-      })"
+  <div class="wysiwyg-v3" data-wysiwyg="v3" x-data="{ jsReady: false }" x-init="
+      $nextTick(() => {
+        if (typeof autoInit === 'function') {
+          autoInit('{{ textarea_id|escapejs }}');
+          jsReady = true;
+        }
+      })"
     >
     <textarea
       id="{{ textarea_id }}"
       name="{{ textarea_name }}"
       class="w-full min-h-[200px]"
       placeholder=""
       x-show="!jsReady"
-      :aria-hidden="true"
+      :aria-hidden="jsReady"
🧰 Tools
🪛 HTMLHint (1.9.2)

[error] 26-26: Special characters must be escaped : [ < ].

(spec-char-escape)

🤖 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/_wysiwyg_editor.html` around lines 20 - 33, The
wysiwyg-v3 editor template sets jsReady to true immediately before autoInit()
completes asynchronously, causing the fallback textarea to be hidden via
x-show="!jsReady" even if initialization fails. Move the jsReady = true
assignment to execute after autoInit() completes successfully (likely within a
callback or promise chain inside the autoInit() function itself or in the x-init
block after awaiting it), and change the hardcoded :aria-hidden="true" attribute
on the textarea to :aria-hidden="jsReady" so the fallback textarea only becomes
hidden from screen readers when initialization actually succeeds. This ensures
the textarea remains visible and accessible to users if the editor fails to
initialize.

@julhoang julhoang 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.

Hi @jlchilders11, thank you for all the updates you've made! 🙌 I believe there's one remaining request here from your thread with @herzog0 . I believe resolving that should also clear up the current issue where the V3 demo page doesn't render the WYSIWYG correctly:

Image

Additionally, I was curious if we could refactor wysiwyg-editor.js to support toolbar customization rather than duplicating the editor into a separate minimal-variant file. Making the toolbar configurable (e.g. a preset arg so this profile page just asks for a smaller set of tools) feels like a more maintainable long-term approach – it'd let us spin up new variants without forking the file each time. I pushed my proposed approach to this branch – I would love to hear what you think.

That said, I don't think this should hold up your PR considering we have several other tasks relying on it, so I'm definitely happy to leave it to a follow-up improvement task!

@jlchilders11 jlchilders11 requested a review from julhoang June 18, 2026 21:13
@jlchilders11

Copy link
Copy Markdown
Collaborator Author

Hi @jlchilders11, thank you for all the updates you've made! 🙌 I believe there's one remaining request here from your thread with @herzog0 . I believe resolving that should also clear up the current issue where the V3 demo page doesn't render the WYSIWYG correctly:
Image

Additionally, I was curious if we could refactor wysiwyg-editor.js to support toolbar customization rather than duplicating the editor into a separate minimal-variant file. Making the toolbar configurable (e.g. a preset arg so this profile page just asks for a smaller set of tools) feels like a more maintainable long-term approach – it'd let us spin up new variants without forking the file each time. I pushed my proposed approach to this branch – I would love to hear what you think.

That said, I don't think this should hold up your PR considering we have several other tasks relying on it, so I'm definitely happy to leave it to a follow-up improvement task!

Julia, thank you so much for that commit, that in fact was an improvement that Teo and I had been discussing as a future upgrade, that you just made happen! I'm happy to incorporate it into this PR, since it works for me.

@julhoang julhoang 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.

As discussed in the team chat, we'll leave the Avatar UI and functionality to a follow-up PR as we potentially have design adjustments pending. Other than that everything looks great to me. Thanks a ton for your awesome work on this ticket! 🙌

@javiercoronadonarvaez javiercoronadonarvaez force-pushed the jc/user-edit-ui branch 2 times, most recently from 46daef2 to 54a52a7 Compare June 23, 2026 14:03

@kattyode kattyode 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.

QA Approved

@jlchilders11 jlchilders11 merged commit c40f9d5 into develop Jun 24, 2026
5 checks passed
@jlchilders11 jlchilders11 deleted the jc/user-edit-ui branch June 24, 2026 16:31
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

5 participants