-
Notifications
You must be signed in to change notification settings - Fork 786
Add a troubleshooting panel #2951
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughAdds a Troubleshooting UI: a new modal component that collects browser, rendering, and power diagnostics, integrates into the Help modal and main app flow, provides copy-to-clipboard, and adds localization strings and a diagnostic utility. Changes
Sequence DiagramsequenceDiagram
participant User
participant HelpModal
participant TroubleshootingModal
participant Diagnostic
participant BrowserAPIs as Browser APIs
User->>HelpModal: Click "Go to Troubleshooting"
HelpModal->>TroubleshootingModal: open()
TroubleshootingModal->>TroubleshootingModal: onOpen() / init guard
TroubleshootingModal->>Diagnostic: collectGraphicsDiagnostics(canvas)
Diagnostic->>BrowserAPIs: probe userAgent / getContext('webgl2') / getExtension('WEBGL_debug_renderer_info') / navigator.getBattery()
BrowserAPIs-->>Diagnostic: returns platform, context, GPU info, battery
Diagnostic-->>TroubleshootingModal: GraphicsDiagnostics object
TroubleshootingModal->>TroubleshootingModal: render diagnostics
TroubleshootingModal-->>User: display environment/rendering/power info
User->>TroubleshootingModal: Click "Copy"
TroubleshootingModal->>BrowserAPIs: navigator.clipboard.writeText(formattedDiagnostics)
BrowserAPIs-->>TroubleshootingModal: success
TroubleshootingModal-->>User: dispatch copy success event
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@src/client/TroubleshootingModal.ts`:
- Around line 208-222: The toast is shown before navigator.clipboard.writeText
completes and there is no error handling; in copyDiagnostics move the
window.dispatchEvent(...) success toast to after the awaited
navigator.clipboard.writeText(formatted) call, wrap the writeText call in a
try/catch, and on catch fire an error toast (use
translateText("troubleshooting.copy_failed") and type "error") while keeping the
existing success toast (translateText("troubleshooting.copied_to_clipboard"))
only on successful await; reference copyDiagnostics,
navigator.clipboard.writeText, and the CustomEvent("show-message") usage when
implementing the change.
- Around line 22-33: Remove the redundant canvas null-check and avoid
double-loading by picking one loading entry point: delete the call to
loadDiagnostics() from connectedCallback and move lazy initialization into
onOpen(), or alternately have connectedCallback set initialized = true after
loading; specifically, remove the dead if (!canvas) return; in loadDiagnostics
(document.createElement("canvas") never returns null), and update
loadDiagnostics (or onOpen) so that after awaiting
collectGraphicsDiagnostics(canvas) you set this.diagnostics and this.initialized
= true to prevent subsequent onOpen() calls from reloading; ensure references to
connectedCallback, loadDiagnostics, onOpen, initialized, diagnostics, and
collectGraphicsDiagnostics are adjusted accordingly.
- Around line 55-60: Replace the hardcoded button label "Copy for Discord" with
a translated string using the existing translation helper (e.g., call
translateText('copy_for_discord') or the project's equivalent) inside the render
template in TroubleshootingModal (where the button with
`@click`=${this.copyDiagnostics} is defined), and add the key "copy_for_discord":
"Copy for Discord" to en.json; ensure any necessary import for translateText is
present at the top of src/client/TroubleshootingModal.ts if not already
imported.
In `@src/client/utilities/Diagnostic.ts`:
- Around line 65-74: The instanceof check against WebGL2RenderingContext can
throw if that constructor is undefined in some browsers; update the block that
sets gl/type (variables gl, type of RendererType) so you first guard with typeof
WebGL2RenderingContext !== "undefined" before using instanceof (e.g., only
evaluate gl instanceof WebGL2RenderingContext when the typeof check passes),
leaving the canvas.getContext logic unchanged and ensuring older browsers fall
back to "WebGL1" or "Canvas2D" without throwing.
🧹 Nitpick comments (4)
src/client/TroubleshootingModal.ts (4)
15-15: Unused propertymarkdown.This property is declared but never used in the component. Consider removing it to keep the code clean.
- `@property`({ type: String }) markdown = "Loading...";
163-176: Missingaltattribute on image (accessibility).The
<img>at line 172 needs analtattribute for screen readers. Also, consider usingTemplateResultas the return type instead ofunknownfor better type safety.Suggested fix
- private infoTip(text: string, warning?: boolean): unknown { + private infoTip(text: string, warning?: boolean): TemplateResult { return html` <div class="mt-2 ${warning ? "bg-orange-500/10" : "bg-white/10"} flex gap-2 text-white py-1 px-3 rounded-sm border-1 ${warning ? "border-orange-400" : "border-white/40"}" > - <img src="${infoIcon}" class="w-4" /> + <img src="${infoIcon}" class="w-4" alt="" aria-hidden="true" /> ${text} </div> `; }Add the import at the top:
import { html, TemplateResult } from "lit";
178-184: Remove debugconsole.log.The console.log at line 180 looks like a leftover debug statement. Consider removing it for production.
protected onOpen(): void { if (!this.initialized) { - console.log("Loading diagnostics..."); this.initialized = true; this.loadDiagnostics(); } }
224-232: Use proper type instead ofany.The
renderingparameter is typed asany, butRenderingInfoexists inDiagnostic.ts. Import and use it for type safety.Suggested fix
Update the import:
import { collectGraphicsDiagnostics, GraphicsDiagnostics, + RenderingInfo, } from "./utilities/Diagnostic";Then fix the method signature:
- private describeRenderer(rendering: any): string { + private describeRenderer(rendering: RenderingInfo): string {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/client/HelpModal.ts`:
- Line 123: The HTML/JSX comment "// Hotkeys Section" inside the HelpModal
component is incorrect for the block that renders troubleshooting content;
update that comment to "// Troubleshooting Section" (or "<!-- Troubleshooting
Section -->" if using HTML comments) in the HelpModal component (e.g., within
the HelpModal render/return block) so the comment matches the actual
Troubleshooting content.
In `@src/client/TroubleshootingModal.ts`:
- Around line 52-57: The anchor in TroubleshootingModal that reads <a ...
`@click`=${this.close}> is invalid because it has target="_blank" but no href and
the click calls the component's close() method; replace it with a semantic
<button type="button"> (or an element with role="button") styled with the same
classes and keep `@click`=${this.close}, and remove the target attribute; update
any references in the TroubleshootingModal component (e.g., the close method and
the template where the anchor lived) so the button behaves identically and
remains keyboard accessible.
♻️ Duplicate comments (2)
src/client/TroubleshootingModal.ts (2)
22-30: Diagnostics may load twice.
connectedCallback()callsloadDiagnostics()but does not setinitialized = true. WhenonOpen()runs,initializedis stillfalse, soloadDiagnostics()runs again.Pick one entry point. If you want lazy loading on first open, remove the call from
connectedCallback:Suggested fix (lazy load on open)
connectedCallback() { super.connectedCallback(); - this.loadDiagnostics(); }Also applies to: 183-188
212-226: Missing error handling for clipboard write.If
navigator.clipboard.writeText()fails (e.g., permissions denied, insecure context), the user gets no feedback. Wrap in try/catch to show an error toast on failure.Suggested fix
private async copyDiagnostics() { if (!this.diagnostics) return; const formatted = "```json\n" + JSON.stringify(this.diagnostics, null, 2) + "\n```"; + try { await navigator.clipboard.writeText(formatted); window.dispatchEvent( new CustomEvent("show-message", { detail: { message: html`${translateText("troubleshooting.copied_to_clipboard")}`, type: "info", duration: 3000, }, }), ); + } catch { + window.dispatchEvent( + new CustomEvent("show-message", { + detail: { + message: html`${translateText("troubleshooting.copy_failed")}`, + type: "error", + duration: 3000, + }, + }), + ); + } }Note: You will need to add the
troubleshooting.copy_failedkey toen.json.
🧹 Nitpick comments (4)
src/client/TroubleshootingModal.ts (3)
15-15: Unused propertymarkdown.The
markdownproperty is declared but never used in therender()method or anywhere else in this component. Consider removing it to keep the code clean.Suggested fix
`@customElement`("troubleshooting-modal") export class TroubleshootingModal extends BaseModal { - `@property`({ type: String }) markdown = "Loading..."; - `@property`({ type: Object }) diagnostics?: GraphicsDiagnostics;
238-249: Redundant conditional check.Line 243 checks
if (window.showPage)and line 244 useswindow.showPage?.(). The optional chaining is redundant since you already knowshowPageexists inside theifblock.Suggested fix
if (this.inline) { this.style.pointerEvents = "none"; - if (window.showPage) { - window.showPage?.("page-help"); - } + window.showPage?.("page-help"); } else { this.modalEl?.close(); }
228-236: Replaceanytype withRenderingInfofor better type safety and editor support.The
renderingparameter usesany, but the typeRenderingInfoalready exists in your importedDiagnosticutilities. This gives TypeScript complete information about the properties you access (gpu?.software,type, etc.).Suggested fix
import { collectGraphicsDiagnostics, GraphicsDiagnostics, + RenderingInfo, } from "./utilities/Diagnostic"; ... - private describeRenderer(rendering: any): string { + private describeRenderer(rendering: RenderingInfo): string {src/client/HelpModal.ts (1)
162-172: Usingdata-i18nis correct and will populate the button text automatically.The codebase processes
data-i18nattributes through LangSelector, which queries all elements with this attribute and populates their text. The button renders correctly. While the file also uses${translateText()}directly in templates elsewhere, both approaches are valid and used throughout the codebase. Consistency within a single template can be improved, but this is a style preference, not a functional issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/client/TroubleshootingModal.ts`:
- Line 152: In the TroubleshootingModal template literal a stray semicolon (`;`)
is included inside the outer backticks and will render as visible text; remove
that semicolon from the template string (or, if you intended statement
termination in JS, place the semicolon outside the closing backtick) so the
content string produced by the function/method that builds the modal (the
template literal in TroubleshootingModal) no longer contains the extraneous
character.
♻️ Duplicate comments (2)
src/client/TroubleshootingModal.ts (2)
45-49: Use a<button>instead of an<a>for in-app navigation.The
<a>element here lacks anhrefattribute, making it semantically incorrect HTML. Since clicking this triggersthis.close()for in-app navigation (not external linking), a<button>styled as a link is more appropriate.Suggested fix
- <a - class="hover:text-blue-200 text-blue-400 cursor-pointer" - `@click`=${this.close} - >${translateText("main.help")}</a - > + <button + type="button" + class="hover:text-blue-200 text-blue-400 cursor-pointer bg-transparent border-none p-0 font-inherit text-xl sm:text-2xl md:text-3xl font-bold uppercase tracking-widest" + `@click`=${this.close} + >${translateText("main.help")}</button>
216-230: Add error handling for clipboard write failure.The
navigator.clipboard.writeText()can fail (permission denied, unsupported context). Without a try/catch, users get no feedback when the copy fails.Suggested fix
private async copyDiagnostics() { if (!this.diagnostics) return; const formatted = "```json\n" + JSON.stringify(this.diagnostics, null, 2) + "\n```"; - await navigator.clipboard.writeText(formatted); - window.dispatchEvent( - new CustomEvent("show-message", { - detail: { - message: html`${translateText("troubleshooting.copied_to_clipboard")}`, - type: "info", - duration: 3000, - }, - }), - ); + try { + await navigator.clipboard.writeText(formatted); + window.dispatchEvent( + new CustomEvent("show-message", { + detail: { + message: html`${translateText("troubleshooting.copied_to_clipboard")}`, + type: "info", + duration: 3000, + }, + }), + ); + } catch { + window.dispatchEvent( + new CustomEvent("show-message", { + detail: { + message: html`Copy failed`, + type: "error", + duration: 3000, + }, + }), + ); + } }Note: You may want to add a
troubleshooting.copy_failedkey toen.jsonfor the error message.
🧹 Nitpick comments (2)
src/client/TroubleshootingModal.ts (2)
245-250: Redundant nullish check.Line 247 already checks
if (window.showPage), so the optional chaining on line 248 is unnecessary.Suggested fix
if (this.inline) { this.style.pointerEvents = "none"; if (window.showPage) { - window.showPage?.("page-help"); + window.showPage("page-help"); }
232-240: Use typed parameter instead ofany.The
renderingparameter should useRenderingInfotype from your diagnostics utility for better type safety and editor support. Simply addRenderingInfoto the existing import from"./utilities/Diagnostic"and update the parameter type.Suggested fix
import { collectGraphicsDiagnostics, GraphicsDiagnostics, + RenderingInfo, } from "./utilities/Diagnostic"; - private describeRenderer(rendering: any): string { + private describeRenderer(rendering: RenderingInfo): string {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/client/TroubleshootingModal.ts`:
- Around line 207-214: The row method uses invalid Tailwind classes (e.g.,
"max-w-100") that have no effect; update the class string in the private
row(label: string, value: unknown) method to use Tailwind arbitrary-width
utilities (for example replace "max-w-100" with "max-w-[100px]" and any
"min-w-75" usage with "min-w-[75px]" or a size consistent with other components
like "max-w-[200px]") so the intended widths apply; ensure you update only the
class attribute inside row and keep the rest of the markup intact.
♻️ Duplicate comments (2)
src/client/TroubleshootingModal.ts (2)
45-49: Anchor withouthrefis not semantic.The
<a>element here has nohref, which makes it invalid HTML. A<button>styled as a link would be more appropriate for a click action that navigates within the app.Suggested fix
- <a - class="hover:text-blue-200 text-blue-400 cursor-pointer" - `@click`=${this.close} - >${translateText("main.help")}</a - > + <button + type="button" + class="hover:text-blue-200 text-blue-400 cursor-pointer bg-transparent border-none p-0 font-inherit" + `@click`=${this.close} + >${translateText("main.help")}</button>
216-230: Add error handling for clipboard write.If
navigator.clipboard.writeText()fails (permissions denied, insecure context, etc.), the user gets no feedback. Wrap the call in try/catch and show an error toast on failure.Suggested fix
private async copyDiagnostics() { if (!this.diagnostics) return; const formatted = "```json\n" + JSON.stringify(this.diagnostics, null, 2) + "\n```"; - await navigator.clipboard.writeText(formatted); - window.dispatchEvent( - new CustomEvent("show-message", { - detail: { - message: html`${translateText("troubleshooting.copied_to_clipboard")}`, - type: "info", - duration: 3000, - }, - }), - ); + try { + await navigator.clipboard.writeText(formatted); + window.dispatchEvent( + new CustomEvent("show-message", { + detail: { + message: html`${translateText("troubleshooting.copied_to_clipboard")}`, + type: "info", + duration: 3000, + }, + }), + ); + } catch { + window.dispatchEvent( + new CustomEvent("show-message", { + detail: { + message: html`${translateText("troubleshooting.copy_failed")}`, + type: "error", + duration: 3000, + }, + }), + ); + } }
🧹 Nitpick comments (3)
src/client/TroubleshootingModal.ts (3)
15-15: Unused propertymarkdown.This property is declared but never referenced in
render()or elsewhere. Consider removing it to keep the component clean.- `@property`({ type: String }) markdown = "Loading...";
172-172: Prefer explicit return typeTemplateResult.Using
unknownloses type safety. ReturningTemplateResult(fromlit) makes the contract clear.- private infoTip(text: string, warning?: boolean): unknown { + private infoTip(text: string, warning?: boolean): TemplateResult {
232-240: UseRenderingInfotype instead ofanyfor compile-time type safety.The
renderingparameter should use theRenderingInfotype from./utilities/Diagnosticinstead ofany. This enables TypeScript to catch typos at compile time. For example, accessing a non-existent property likerendering.gupwould be caught immediately.Add
RenderingInfoto the existing import:import { collectGraphicsDiagnostics, GraphicsDiagnostics, + RenderingInfo, } from "./utilities/Diagnostic";Then update the method signature:
- private describeRenderer(rendering: any): string { + private describeRenderer(rendering: RenderingInfo): string {
| private row(label: string, value: unknown) { | ||
| return html` | ||
| <div class="flex justify-between gap-4 text-sm"> | ||
| <span class="text-slate-400">${label}</span> | ||
| <span class="text-right text-white max-w-100">${value}</span> | ||
| </div> | ||
| `; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for max-w-100 usage
echo "=== Searching for max-w-100 usage ==="
rg "max-w-100" --type ts --type tsx --type js --type jsx --type css -n
echo -e "\n=== Searching for Tailwind config files ==="
fd "tailwind" --type f
echo -e "\n=== Checking for postcss config ==="
fd "postcss" --type f
echo -e "\n=== Checking for tailwind in package.json ==="
fd "package.json" -x head -50 {}Repository: openfrontio/OpenFrontIO
Length of output: 190
🏁 Script executed:
#!/bin/bash
# Also search more broadly for any config that might define max-w values
echo "=== Searching for max-w definitions in CSS/config ==="
rg "max-w" --type css --type scss -A 2 -B 2
echo -e "\n=== Searching in TypeScript for max-w patterns ==="
rg "max-w-\[" --type ts --type tsx -nRepository: openfrontio/OpenFrontIO
Length of output: 246
🏁 Script executed:
#!/bin/bash
# Search for max-w-100 using basic patterns
echo "=== Searching for max-w-100 ==="
rg "max-w-100" -n
echo -e "\n=== Finding Tailwind config ==="
find . -type f -name "*tailwind*" 2>/dev/null | head -20
echo -e "\n=== Finding postcss config ==="
find . -type f -name "postcss.config.*" 2>/dev/null
echo -e "\n=== Checking package.json for tailwindcss ==="
find . -type f -name "package.json" -exec grep -l "tailwindcss" {} \; 2>/dev/null
echo -e "\n=== Reading the specific file mentioned ==="
cat -n src/client/TroubleshootingModal.ts | sed -n '207,214p'Repository: openfrontio/OpenFrontIO
Length of output: 827
🏁 Script executed:
#!/bin/bash
# Search for any max-w definitions in the project
echo "=== Searching for max-w patterns in config files ==="
rg "max-w" -A 2 -B 2 | head -50
echo -e "\n=== Searching for arbitrary Tailwind values ==="
rg "max-w-\[" -n
echo -e "\n=== Checking if this is a real issue by searching for similar patterns ==="
rg "text-slate-400|text-right|text-white" -c | head -10Repository: openfrontio/OpenFrontIO
Length of output: 6154
🏁 Script executed:
#!/bin/bash
# Check for any config files that might define max-w-100
echo "=== Searching for all config files ==="
find . -type f \( -name "*.config.*" -o -name "tailwind.*" -o -name "vite.*" \) 2>/dev/null
echo -e "\n=== Checking globals or CSS imports ==="
fd -e css -e scss -e less | head -20
echo -e "\n=== Check if max-w-100 or min-w-75 is defined in CSS ==="
rg "max-w-100|min-w-75" -B 5 -A 5
echo -e "\n=== Check the PlayerPanel.ts file where max-w-100 appears ==="
rg "min-w-75 max-w-100" -B 3 -A 3Repository: openfrontio/OpenFrontIO
Length of output: 2871
🏁 Script executed:
#!/bin/bash
# Look for Tailwind theme configuration inline
echo "=== Searching for theme config or extend patterns ==="
rg "theme|extend" --type ts | grep -i "max\|width" | head -20
echo -e "\n=== Check actual imported styles ==="
find . -name "*.css" -type f | head -20Repository: openfrontio/OpenFrontIO
Length of output: 597
🏁 Script executed:
#!/bin/bash
# Check main styles file
echo "=== Checking src/client/styles.css ==="
cat src/client/styles.css | head -100
echo -e "\n=== Checking variables file ==="
cat src/client/styles/core/variables.css
echo -e "\n=== Checking vite.config.ts for Tailwind setup ==="
cat vite.config.tsRepository: openfrontio/OpenFrontIO
Length of output: 6517
🏁 Script executed:
#!/bin/bash
# Search for layer or component directives in CSS files
echo "=== Searching for Tailwind directives in CSS ==="
rg "@layer|@apply" src/client/styles/ -B 2 -A 5
echo -e "\n=== Search for min-w or max-w in CSS files ==="
rg "min-w|max-w" src/client/styles/Repository: openfrontio/OpenFrontIO
Length of output: 926
🏁 Script executed:
#!/bin/bash
# Check if there's a tailwind.config file that might define max-w-100
echo "=== Searching for tailwind.config ==="
find . -type f -name "tailwind.config.*"
echo -e "\n=== Check if max-w-100 or min-w-75 work by searching Tailwind docs patterns ==="
# Look for any `@layer` component or utilities that define these
rg "@layer" src/client/styles/ -A 10
echo -e "\n=== Check Tailwind v4 theme syntax ==="
cat src/client/styles.css | grep -A 50 "@theme"Repository: openfrontio/OpenFrontIO
Length of output: 1110
🏁 Script executed:
#!/bin/bash
# Check for any PostCSS plugins that might add custom utilities
echo "=== Checking package.json for custom Tailwind plugins ==="
cat package.json | grep -A 50 "dependencies\|devDependencies" | head -100
echo -e "\n=== Final confirmation: does max-w-100 or min-w-75 appear in Tailwind defaults? ==="
# Standard Tailwind spacing values (4, 6, 8, 10, 12, 14, 16, 20, 24, 28, 32, etc.)
# But NOT 100 or 75 for arbitrary numeric scales
echo "Standard Tailwind max-width utilities do not include max-w-100 or min-w-75"
echo "These would need to be defined in a tailwind.config.js theme.extend.maxWidth or theme.extend.minWidth"
echo -e "\n=== Verify both occurrences ==="
rg "min-w-75|max-w-100" -B 1 -A 1Repository: openfrontio/OpenFrontIO
Length of output: 3702
Replace invalid Tailwind classes with proper syntax.
max-w-100 and min-w-75 are not standard Tailwind utilities and are not defined in this project's config. These classes will have no effect. Use arbitrary values instead: max-w-[100px] or similar, matching the pattern used elsewhere in the codebase (e.g., max-w-[200px]).
🤖 Prompt for AI Agents
In `@src/client/TroubleshootingModal.ts` around lines 207 - 214, The row method
uses invalid Tailwind classes (e.g., "max-w-100") that have no effect; update
the class string in the private row(label: string, value: unknown) method to use
Tailwind arbitrary-width utilities (for example replace "max-w-100" with
"max-w-[100px]" and any "min-w-75" usage with "min-w-[75px]" or a size
consistent with other components like "max-w-[200px]") so the intended widths
apply; ensure you update only the class attribute inside row and keep the rest
of the markup intact.
| const troubleshootingModal = document.querySelector( | ||
| "troubleshooting-modal", | ||
| ) as TroubleshootingModal; | ||
| if ( | ||
| !troubleshootingModal || | ||
| !(troubleshootingModal instanceof TroubleshootingModal) | ||
| ) { | ||
| console.warn("Troubleshooting modal element not found"); | ||
| } | ||
| document | ||
| .getElementById("troubleshooting-button") | ||
| ?.addEventListener("click", () => { | ||
| if ( | ||
| troubleshootingModal && | ||
| troubleshootingModal instanceof TroubleshootingModal | ||
| ) { | ||
| troubleshootingModal.open(); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we instead put this logic in the onClick() in HelpModal? Just to keep Main.ts from getting too large.
Description:
Add a troobleshooting panel with the most common problems, and a button to copy the infos for better sharing
Please complete the following:
Please put your Discord username so you can be contacted if a bug or regression is found:
Mr. Box