For the past two years, software developers have been told that AI might replace us. Others argue it won’t replace developers but will instead become a powerful tool. Recently, I ran into a particularly complex UI bug and decided to test that claim for myself: is AI a replacement, or just another tool in the toolbox?
For context, I am a full-stack senior software developer with more than 10 years of experience in tech. After graduating from university with a Bachelor of Computer Science, I worked as both an IC and a team lead in various industries such as healthcare, finance, and education. Along the way, I’ve seen numerous “disruptive” technologies come and go, from blockchain to no-code platforms.
This latest wave of AI, however, feels different. Its capabilities are undeniably impressive, and I wanted to see how it would perform in a real-world scenario.
In this article, I will go over the live debugging journey with the back-and-forth discussion with the Claude model. At the end, I will share my reflections along with the pros and cons.
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
Note: The conversations were abbreviated for ease of reading.
To give some context, I work for a wealth management company that specializes in generating investment proposals. After a user, in this case, a financial advisor, chooses a template, they are prompted to complete a form that will generate their ready-for-use proposal. At which point, they can decide to switch templates, which will bring them back to the form. At which point, the form is frozen, and none of the options are clickable. The page is a straightforward Next.js page with a top bar, two questions comprising multiple-choice answers, and a create button. At this point, all I know is that I cannot click any of the answers.
Initial prompt to Claude: I have a weird bug. When I first landed on this page, it worked fine. But if I return to this page during the flow, the page renders, but I can’t click on any of the form options, and the button is disabled. I have to refresh the page for it to work.
At first, Claude looks into the Form component and spots two local states: isProcessing and isCreating. Since he’s only seeing the component in isolation, his initial hypothesis points to an issue with the mutation action.
Claude: Looking at your AutoCreatePage component, I can see the issue. The problem is likely that the mutation states (autoCreatePresentationMutation and createPresentationMutation) from tRPC are retaining their loading or error states when you navigate back to the page, and the local isCreating state may not be resetting correctly.
That theory doesn’t hold up. To dig deeper, Claude adds a series of console logs to track the state, and I share the results back with him.
After a few rounds of back-and-forth, Claude eventually identifies the real issue: the question form has a disabledclassname applied. He fixes it by explicitly setting the disabled prop to false.
className={clsx(' px-4', {
'pointer-events-none select-none opacity-15': disabled,
})}
This doesn’t fix anything, so Claude’s first theory is clearly off. I do a bit of investigating myself to push against the limits of Claude’s context.
Prompt: So I kept testing and noticed the whole page seems frozen, not just the question form. I can’t click the back button in the top bar or even the set up manually button. What could make a Next.js page this unresponsive? I can still scroll up and down.
Claude then broadens its context by reading through the full page and discovers that there’s a loading screen sitting on top of it with a fixed position.
<div role="status" data-testid="loading-screen" className="fixed left-0 top-0 z-50 flex h-full w-full flex-col items-center justify-center bg-primary-navy/[.65] p-3" >
Since Claude still remembers the two variables, isProcessing and isCreating, his new theory is that isCreating doesn’t get reset back to false after the first form submission, which is why the page is unresponsive.
This theory is easy to challenge, as what I am witnessing is the form, not the loading state.
By that point, Claude is all out of ideas and needs my help debugging.
Claude: Now, can you help me debug this by checking these things in the browser:
Claude didn’t realize it yet, but he was starting to be on the right track.
Prompt: I think I found the cause of the unresponsiveness. Something is adding a pointer-events: none; on the whole body
Claude: Based on my investigation, the issue is most likely related to Radix UI modals. Radix UI (which [our local library] Modal component uses) typically applies pointer-events: none to the body when a modal is open to prevent interactions with content behind the modal.
Claude now assumes the issue is that the modal isn’t closing properly and adds a useEffect to set isModalOpen to false. For context, a user can switch templates by selecting the “Change template” option, which opens a modal displaying the list of templates. Once a user selects one, they are brought back to the form.
Claude: Looking at the onSelectHandler function in the TemplateSelectorModal, there’s the same pattern we fixed in SaveTemplateModal – if an error occurs in the catch block, the modal never gets closed.
It is a valid assumption, but there is just a clinch with this approach. This theory assumes I am getting an error, which I am not.
At that point, Claude adds a bunch of console logs to track if there are open modals, and I report that there are none.
// Check for any Radix dialog overlays (these would cause pointer-events: none on body)
console.log('Dialog overlays:', document.querySelectorAll('[data-radix-dialog-overlay]').length);
// Check for open modal dialogs
console.log('Open modals:', document.querySelectorAll('[role="dialog"]').length);
// Check body styles
console.log('Body pointer-events:', getComputedStyle(document.body)['pointer-events']);
// Look for any element with aria-hidden=true that might be blocking
console.log('Hidden overlays:', document.querySelectorAll('[aria-hidden="true"]'));
After a bit more investigating, we discover that the pointer-events: none is being set via inline style by JavaScript (document.body.style.pointerEvents = 'none'), but there are no CSS rules applying it.
At that point, Claude had me add a tracker to figure out when the style is being applied.
Object.defineProperty(document.body.style, 'pointerEvents', {
get: function() {
return this._pointerEvents || '';
},
set: function(value) {
console.log('Setting pointerEvents to:', value);
if (value === 'none') {
console.trace('Stack trace for setting pointer-events: none');
}
this._pointerEvents = value;
}
});
console.log('Trace set up - now trigger the issue');
The tracker uncovers that the pointer-events is being added when I open a modal to select a new template and navigate back to the form.
Claude: Perfect! The issue happens when you open the TemplateSelectorModal. This is actually expected behavior – Radix UI modals set pointer-events: none on the body when they open to prevent background interactions.
The problem, then, is that the style isn’t getting removed when the modal closes.
At that point, I explain that the navigation happens as soon as an item is selected in the modal. So we try a few different ways to make sure the modal fully closes before the redirect. From there, he suggests cleaning up the inline styling.
First fix:
in Modal.tsx
// Ensure pointer-events is reset when modal closes
// Adding this as when a modal is open and a redirection happens, the modal might not unmount and
// thus the pointer-events:none on body would still be in effect, blocking interactions on the new page such as the case of the Switch template action.
useEffect(() => {
if (open === false) {
// Reset pointer-events when modal is explicitly closed
document.body.style.pointerEvents = '';
}
}, [open]);
Finally, we have a fix that actually works. It’s a bit hacky, though, and I tell Claude to challenge him a bit.
Claude: Looking at your Modal component, it’s a wrapper around Radix UI’s dialog primitives. The issue is that Radix UI sets pointer-events: none on the body when opening, but the cleanup might not happen reliably when redirections occur.
It feels like a valid point, so I open a PR for the team to review. By then, it is lunchtime, so I close my computer and set out to start cooking. One of the perks of working remotely. As it often happens when I do, my mind starts to wander and question: Why was it only a problem now?
We never had a pointer events problem before developing this feature. We have used modal in other parts of the product and never ran into that issue. Was it really only due to navigation? But if it were, Radix would have addressed an issue so fundamental. I kept feeling like I hadn’t reached the root of the issue, so I did some testing.
I discovered that when I open the modal and click outside of it, the modal closes, and the pointer-events: none issue is there. This invalidates the theory that the redirection was the cause of the issue.
Prompt: Ok, so I did some more testing about this. I commented out the fix we did in the modal to test, as I wanted to see if I could reproduce the pointer-events not being cleared in other ways. I found out that I can also reproduce the bug with other modals that I open and close by clicking outside of the modal page. For some reason, even though the modal closes, the pointer-events: none gets added even if there is no redirection.
Claude: That’s a really valuable discovery! This confirms that the issue isn’t specifically about redirections – it’s a more fundamental problem with how Radix UI’s modal cleanup works when closing via overlay click vs other methods.
By then, Claude is convinced that it is a Radix issue. I point out that there is a cancel button, and clicking it still causes the issue. I also point out to him that it feels weird that it is only happening now.
It gives me a few options, such as checking the latest changes to the Radix UI library to see if something could have caused it.
By that point, Claude is out of ideas and stuck on the idea that the Radix library must be the cause. So, I went ahead and did some good old-fashioned googling.
I stumbled on a GitHub issue, where someone commented:
This issue occurs because of a conflict between the modal and another modal-supported component you’re using to trigger it, most likely a dropdown. To fix it, simply add modal={false} to the trigger component.
And as it turned out, the modal was triggered from inside a dropdown menu, which is why it was conflicting and adding the pointer-events: none despite the modal closing.
By this point, I didn’t need Claude anymore, so I went ahead and made the fix.
Final fix:
<DropdownMenu ariaLabel={ariaLabel} items={menu} dataTestId={dataTestId} modal={modal}>
...
</DropdownMenu>
Overall, the experience was quite positive. It reminded me a lot of pair programming with other developers.
A couple of years ago, when I joined a new company, I spent my first month pairing with another new joiner so we could learn the codebase together. I really enjoyed that process. Not only did I learn a lot, but it was also rewarding to collaborate, test different ideas, and figure things out while essentially stumbling in the dark. In that sense, working with Claude felt very similar to that kind of back-and-forth investigation.
That said, there are some downsides. Claude doesn’t really challenge your approach. Which makes sense, it’s an executor. You give it a prompt, and it follows through. It’s great for brainstorming, but it won’t tell you if you’re heading in the wrong direction.
It can also get a bit stuck on its own ideas. At one point, it was convinced the issue was with Radix and kept circling back to that. With people, there’s usually more give and take in how ideas evolve.
At the end of the day, Claude is a tool, and the real question becomes how to use it effectively.
That leads to another reflection. If I had done a bit more initial investigation, would I have reached the root cause faster? We spent time focusing on the form early on. If I had realized sooner that the entire page was frozen, my prompts would have been more precise, and that likely would have sped things up. The quality of Claude’s output really depends on the quality of the input.
Even with that, I found the experience genuinely useful. As a brainstorming partner, it added real value. What could have taken me an entire day to debug was resolved by early afternoon. Realistically, it saved me around four hours.
For those who want a TL: DR, here are some of the pros and cons I noted down.
Claude was great at:
Claude struggled with:
In conclusion, I would use Claude again for debugging, but with a more deliberate approach. I’ve found it’s most effective after I’ve done some initial investigation and developed a basic understanding of the issue. At that point, Claude becomes a valuable source of ideas, especially ones I hadn’t considered.
This is especially true for UI bugs, where assumptions can easily lead the conversation astray. Having a solid grasp of what is happening in the browser helps guide the discussion. As mentioned earlier, the quality of Claude’s responses is directly tied to the quality of the prompt. The clearer and more precise the input, the more useful and relevant the output will be.

CSS wasn’t built for dynamic UIs. Pretext flips the model by measuring text before rendering, enabling accurate layouts, faster performance, and better control in React apps.

Why do real-time frontends break at scale? Learn how event-driven patterns reduce drift, race conditions, and inconsistent UI state.

Test out Auth.js, Clerk, WorkOS, and Better Auth in Next.js 16 to see which auth library fits your app in 2026.

When should you move API logic out of Next.js? Learn when Route Handlers stop scaling and how ElysiaJS helps.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now