Skip to content

fix(ui): make appearance editor more cohesive#2897

Merged
Fake-User merged 9 commits into
mainfrom
Russell/settings-panel
Apr 27, 2026
Merged

fix(ui): make appearance editor more cohesive#2897
Fake-User merged 9 commits into
mainfrom
Russell/settings-panel

Conversation

@Fake-User
Copy link
Copy Markdown
Contributor

Screenshot 2026-04-27 at 3 07 24 PM

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d76af0a0-7946-4278-b448-210060f23e28

📥 Commits

Reviewing files that changed from the base of the PR and between 5b230db and 8d2c5d9.

📒 Files selected for processing (1)
  • js/app/packages/app/component/settings/Appearance.tsx

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added grid visualization overlay to the theme editor canvas preview.
  • Style

    • Redesigned theme settings layout with improved responsive panel structure.
    • Updated typography, font sizing, and spacing across theme editor components.
    • Refined theme list item design for improved visual hierarchy and clarity.
    • Enhanced visual polish with updated styling and borders.

Walkthrough

Appearance and theme UI files are refactored to use @theme/@ui aliases, reorganize layout into CSS-grid Panels with responsive two-column behavior, and apply styling updates. ThemeEditorBasic adds WebGL grid uniforms and a ResizeObserver-driven canvas resize path.

Changes

Cohort / File(s) Summary
Appearance Layout & Imports
js/app/packages/app/component/settings/Appearance.tsx
Switched imports to @theme/.../@ui, replaced previous Tailwind absolute/grid structure with a root CSS-grid wrapper and Panel containers; reorganized editors/lists into responsive two-column layout at ≥700px and enforced overflow/min-height constraints.
Theme editor — basic (WebGL & resize)
js/app/packages/theme/components/ThemeEditorBasic.tsx
Added gridX/gridY uniforms and dot-grid mask to fragment shader, integrated ResizeObserver to update canvas pixel size, viewport, and grid uniforms on resize, and adjusted ref/style formatting and inline input handlers.
Theme editor — advanced (typography)
js/app/packages/theme/components/ThemeEditorAdvanced.tsx
Changed container and row typography to use var(--text-xs) and var(--font-mono), adjusted font weights/sizes and input styling; no logic changes.
Theme list & tools (layout/styling)
js/app/packages/theme/components/ThemeList.tsx, js/app/packages/theme/components/ThemeTools.tsx
Removed hr dividers, updated headers to explicit sizes/weights, simplified list item grid and swatch/name layout, altered top-row grid template (removed icon column), refactored editable name markup and handlers; click/analytics behavior unchanged.
UI Panel tweak
js/app/packages/ui/components/Panel.tsx
Added min-h-0 Tailwind utility to Panel root to change min-height behavior without altering other logic.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows conventional commits format with 'fix:' prefix, is under 72 characters (45 characters), and accurately describes the main UI/cohesiveness improvements to the appearance editor.
Description check ✅ Passed The description contains a screenshot showing the updated Appearance settings interface with theme editor, directly related to the changeset's focus on improving the appearance editor UI.
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.


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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a 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

Caution

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

⚠️ Outside diff range comments (2)
js/app/packages/theme/components/ThemeEditorBasic.tsx (1)

264-300: 🧹 Nitpick | 🔵 Trivial

Guard resizeObserver in cleanup against early-unmount.

resizeObserver is typed as non-nullable but only assigned inside onMount. If the component is disposed before setupWebGL completes (e.g., a thrown WebGL2-not-supported error before line 278), onCleanup will dereference an undefined and throw, masking the original error.

♻️ Suggested change
-  let resizeObserver: ResizeObserver;
+  let resizeObserver: ResizeObserver | undefined;
   onCleanup(() => {
-    resizeObserver.disconnect();
+    resizeObserver?.disconnect();
     document.removeEventListener('pointermove', handlePointerMove);
     document.removeEventListener('pointerup', handlePointerUp);
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@js/app/packages/theme/components/ThemeEditorBasic.tsx` around lines 264 -
300, The cleanup currently assumes resizeObserver is always assigned in onMount
and calls resizeObserver.disconnect(), which can throw if setupWebGL fails and
the component unmounts early; make resizeObserver nullable (or initialize to
null) and in onCleanup guard the disconnect call (e.g., if (resizeObserver)
resizeObserver.disconnect() or use optional chaining) so disconnect is only
invoked when it was actually created; keep references like resizeObserver,
onMount, setupWebGL and onCleanup when making this change.
js/app/packages/theme/components/ThemeList.tsx (1)

70-78: 🧹 Nitpick | 🔵 Trivial

/* temp remove when long */ hack — capture as a follow-up.

The bottom border + negative margin only works for the short-list case; once the theme list grows (or scrolls), this will produce a stray underline above the scroll thumb. Worth tracking before this lands so it doesn't become permanent.

Want me to open a follow-up issue describing the long-list case (and proposing replacing the negative-margin compensation with a sticky header that owns its border), or sketch a stickier header implementation here?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@js/app/packages/theme/components/ThemeList.tsx` around lines 70 - 78, In
ThemeList.tsx locate the inline style block on the outer grid container that
adds "border-bottom: 1px solid ...; margin-bottom: -1px;" (the short-list hack)
and remove those two properties; instead give the header row (or the element
rendered as the list's top bar inside ThemeList) an explicit border-bottom and
set it to position: sticky with top: 0 and appropriate z-index so the header
owns the dividing line for long/scrolling lists. If you don't implement the
sticky header now, replace the hack with a clear TODO/follow-up comment
referencing ThemeList and the long-list case so we don't accidentally keep the
negative-margin workaround.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@js/app/packages/app/component/settings/Appearance.tsx`:
- Around line 27-30: The outer container div and the inner grid use different
gap sources (an inline gap: '20px' vs Tailwind gap-5 on the nested grid), so
pick one source-of-truth and apply it consistently: either replace the outer
inline gap with the Tailwind utility gap-5 (preferred) on the container div that
wraps Panel/ThemeList/ThemeEditorAdvanced, or change the nested grid to use the
same inline style; update the class on the container div or the nested div
accordingly so both grids use the same spacing unit (keep existing responsive
classes like @[700px]:grid-cols-2 intact).

In `@js/app/packages/theme/components/ThemeEditorBasic.tsx`:
- Around line 423-424: The input handlers currently wrap the event in an
unnecessary arrow (e.g. onInput={(e) => { handleChromaChange(e); }}); replace
these with direct function references (onInput={handleChromaChange}) for both
handleChromaChange and handleSaturationChange (the same pattern used by
handleContrastChange), ensuring the handler signatures remain compatible with
the input event type.

In `@js/app/packages/theme/components/ThemeTools.tsx`:
- Line 40: Remove the stray leading space in the grid-template-columns value in
ThemeTools.tsx: update the inline style where 'grid-template-columns' is set
(the template using columnCount()) so the template string starts with
"min-content" rather than " min-content" (i.e., adjust the template literal
around columnCount() in the component to trim the leading whitespace).
- Around line 72-79: The contentEditable name field in ThemeTools (the element
using ref themeName and contentEditable={true}) removes the native focus
indicator via "outline: 'none'"; restore accessible focus by removing that style
and either (a) delete the outline: 'none' entry so the native focus ring
remains, or (b) replace it with a class that applies an explicit :focus-visible
ring in CSS and add an aria-label (e.g., aria-label="Theme name") to the element
to ensure keyboard users can detect focus before typing/Enter; update the inline
style block or className on the element and add the aria-label accordingly.

---

Outside diff comments:
In `@js/app/packages/theme/components/ThemeEditorBasic.tsx`:
- Around line 264-300: The cleanup currently assumes resizeObserver is always
assigned in onMount and calls resizeObserver.disconnect(), which can throw if
setupWebGL fails and the component unmounts early; make resizeObserver nullable
(or initialize to null) and in onCleanup guard the disconnect call (e.g., if
(resizeObserver) resizeObserver.disconnect() or use optional chaining) so
disconnect is only invoked when it was actually created; keep references like
resizeObserver, onMount, setupWebGL and onCleanup when making this change.

In `@js/app/packages/theme/components/ThemeList.tsx`:
- Around line 70-78: In ThemeList.tsx locate the inline style block on the outer
grid container that adds "border-bottom: 1px solid ...; margin-bottom: -1px;"
(the short-list hack) and remove those two properties; instead give the header
row (or the element rendered as the list's top bar inside ThemeList) an explicit
border-bottom and set it to position: sticky with top: 0 and appropriate z-index
so the header owns the dividing line for long/scrolling lists. If you don't
implement the sticky header now, replace the hack with a clear TODO/follow-up
comment referencing ThemeList and the long-list case so we don't accidentally
keep the negative-margin workaround.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a509da49-bcac-47bd-9564-8697db19c48d

📥 Commits

Reviewing files that changed from the base of the PR and between e9a9032 and 5b230db.

📒 Files selected for processing (6)
  • js/app/packages/app/component/settings/Appearance.tsx
  • js/app/packages/theme/components/ThemeEditorAdvanced.tsx
  • js/app/packages/theme/components/ThemeEditorBasic.tsx
  • js/app/packages/theme/components/ThemeList.tsx
  • js/app/packages/theme/components/ThemeTools.tsx
  • js/app/packages/ui/components/Panel.tsx

Comment on lines +27 to 30
<div class="@container grid grid-cols-1 @[700px]:grid-cols-2 gap-5 overflow-hidden min-h-0">
<Panel><ThemeList /></Panel>
<Panel><ThemeEditorAdvanced /></Panel>
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🌐 Web query:

Are @containerand arbitrary container-query variants like@[700px]:grid-cols-2 stable in Tailwind CSS v4.2?

💡 Result:

Yes, both @container and arbitrary container query variants like @[700px]:grid-cols-2 (more precisely @min-[700px]:grid-cols-2) are stable in Tailwind CSS v4.2. Container queries have been a core feature since v4.0, with no breaking changes or deprecations noted in v4.2 release notes or changelogs. The official documentation explicitly supports arbitrary values using @min-[value] and @max-[value] variants, as shown in examples like @min-[475px]:flex-row and @[17.5rem]:underline. v4.2 changelogs (v4.2.0 to v4.2.3) focus on fixes and additions unrelated to container queries, confirming their stability. Multiple sources, including Tailwind docs and third-party tutorials, demonstrate usage without caveats for production.

Citations:


🏁 Script executed:

cat -n js/app/packages/app/component/settings/Appearance.tsx | head -50

Repository: macro-inc/macro

Length of output: 1185


Consider matching the gap unit source across nested grids for consistency.

Inner grid uses gap-5 (Tailwind utility, 1.25rem = 20px) while the outer container uses inline gap: '20px'. Although both currently resolve to the same value, using a single source-of-truth (either consistently Tailwind utilities or consistently inline CSS values) prevents unintended drift if the spacing scale changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@js/app/packages/app/component/settings/Appearance.tsx` around lines 27 - 30,
The outer container div and the inner grid use different gap sources (an inline
gap: '20px' vs Tailwind gap-5 on the nested grid), so pick one source-of-truth
and apply it consistently: either replace the outer inline gap with the Tailwind
utility gap-5 (preferred) on the container div that wraps
Panel/ThemeList/ThemeEditorAdvanced, or change the nested grid to use the same
inline style; update the class on the container div or the nested div
accordingly so both grids use the same spacing unit (keep existing responsive
classes like @[700px]:grid-cols-2 intact).

Comment on lines 423 to +424
<input
onInput={(e) => { handleChromaChange(e);}}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Nit: drop redundant arrow wrappers around handleChromaChange/handleSaturationChange.

These arrows just forward e to the named handler; the handleContrastChange slider directly uses the same pattern correctly inline. Pass the function reference instead.

-              onInput={(e) => { handleChromaChange(e);}}
+              onInput={handleChromaChange}
-              onInput={(e) => {handleSaturationChange(e);}}
+              onInput={handleSaturationChange}

Also applies to: 514-515

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@js/app/packages/theme/components/ThemeEditorBasic.tsx` around lines 423 -
424, The input handlers currently wrap the event in an unnecessary arrow (e.g.
onInput={(e) => { handleChromaChange(e); }}); replace these with direct function
references (onInput={handleChromaChange}) for both handleChromaChange and
handleSaturationChange (the same pattern used by handleContrastChange), ensuring
the handler signatures remain compatible with the input event type.

<div
style={{
'grid-template-columns': `min-content min-content 1fr repeat(${columnCount()}, min-content)`,
'grid-template-columns': ` min-content 1fr repeat(${columnCount()}, min-content)`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Nit: stray leading whitespace in grid-template-columns.

Harmless — CSS tolerates the leading space — but worth trimming for hygiene.

-        'grid-template-columns': ` min-content 1fr repeat(${columnCount()}, min-content)`,
+        'grid-template-columns': `min-content 1fr repeat(${columnCount()}, min-content)`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'grid-template-columns': ` min-content 1fr repeat(${columnCount()}, min-content)`,
'grid-template-columns': `min-content 1fr repeat(${columnCount()}, min-content)`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@js/app/packages/theme/components/ThemeTools.tsx` at line 40, Remove the stray
leading space in the grid-template-columns value in ThemeTools.tsx: update the
inline style where 'grid-template-columns' is set (the template using
columnCount()) so the template string starts with "min-content" rather than "
min-content" (i.e., adjust the template literal around columnCount() in the
component to trim the leading whitespace).

Comment on lines +72 to +79
style={{
'white-space': 'nowrap',
'font-size': '0.875rem',
'font-weight': '600',
'outline': 'none',
}}
contentEditable={true}
ref={themeName}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't strip the focus outline from the contentEditable name field.

outline: 'none' on this contentEditable removes the only visible focus indicator for keyboard users — they won't be able to tell when the theme name is focused before typing/Enter. The element also has no aria-label. Either drop outline: none and keep the native ring, or provide an explicit :focus-visible style via a class.

♿ Suggested fix
         style={{
           'white-space': 'nowrap',
           'font-size': '0.875rem',
           'font-weight': '600',
-          'outline': 'none',
         }}
         contentEditable={true}
+        aria-label="Theme name"
         ref={themeName}

If the native outline is visually undesirable, replace it with a custom :focus-visible ring in a stylesheet rather than removing it outright.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
style={{
'white-space': 'nowrap',
'font-size': '0.875rem',
'font-weight': '600',
'outline': 'none',
}}
contentEditable={true}
ref={themeName}
style={{
'white-space': 'nowrap',
'font-size': '0.875rem',
'font-weight': '600',
}}
contentEditable={true}
aria-label="Theme name"
ref={themeName}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@js/app/packages/theme/components/ThemeTools.tsx` around lines 72 - 79, The
contentEditable name field in ThemeTools (the element using ref themeName and
contentEditable={true}) removes the native focus indicator via "outline:
'none'"; restore accessible focus by removing that style and either (a) delete
the outline: 'none' entry so the native focus ring remains, or (b) replace it
with a class that applies an explicit :focus-visible ring in CSS and add an
aria-label (e.g., aria-label="Theme name") to the element to ensure keyboard
users can detect focus before typing/Enter; update the inline style block or
className on the element and add the aria-label accordingly.

@github-actions
Copy link
Copy Markdown

@Fake-User Fake-User merged commit 822efef into main Apr 27, 2026
23 checks passed
@Fake-User Fake-User deleted the Russell/settings-panel branch April 27, 2026 21:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant