## Problem The theme's primary hue can change in CSS, but Appearance had no way to try other spot colours. That makes it hard to find controls whose colour still depends on the fixed Supabase brand palette. ## Solution Add a **Spot color** control under Appearance → Theme colors (employee-only via ConfigCat `appearanceSpotColor`, targeted to Supabase Team Email). ### Spot color UX - Rainbow spectrum track with a thin outline so pale tracks stay visible - Live trifecta swatches for `--primary-solid`, `--primary`, and `--primary-bright` (darkest → lightest) next to the degree readout - Drag updates are rAF-batched so React paint and CSS preview stay to one frame ### Canvas tint coupling - `--surface-hue` is derived in CSS as `calc(var(--primary-hue) + var(--surface-hue-offset))` - Dark: offset `0` (same hue as spot) - Light: offset `180` (complementary canvas tint; brand green ≈157.5° → rose ≈337.5°) - No JS override of `--surface-hue`. Changing Spot color moves primary controls and the low-chroma canvas tint together ### Other theme sliders - Renamed **Color intensity** → **Surface tint** (it only drives the neutral ramp via `--chroma`, not spot chroma) - Meaning-shaped tracks for every knob (spectrum, grey→tint, soft→hard, dark→light, flat→lift) - Same outline treatment on those tracks | Before | After | | --- | --- | | <img width="1476" height="2174" alt="CleanShot 2026-10-05 at 15 02 21@2x" src="https://github.com/user-attachments/assets/d08b0fa8-32af-450e-adce-861f59c9d6ca" /> | <img width="1474" height="2354" alt="CleanShot 2026-10-05 at 14 56 35@2x" src="https://github.com/user-attachments/assets/9a1e6183-a4ba-4c8e-a458-bb0eea946db8" /> | | _Anyone else_ | _With staff flag, custom settings_ | ## Review instructions 1. Confirm ConfigCat flag `appearanceSpotColor` is on for your staff account (or flip it in the Dev Toolbar). 2. Open `/account/me` → **Appearance → Theme colors**. 3. Without the flag: Spot color is hidden; other theme sliders still work. 4. With the flag: drag Spot color in light and dark. Primary controls and canvas tint should move together; Supabase brand assets should stay fixed. 5. Raise Surface tint and confirm the canvas hue follows the complementary (light) or same-hue (dark) offset. 6. Refresh, switch modes, and use **Reset** to check persistence and defaults.
UI Testing Notes
Rules
-
All tests should be run consistently (avoid situations whereby tests fails "sometimes")
-
Group tests in folders based on the feature they are testing. Avoid file/folder based folder names since those can change and we will forget to update the tests.
Examples: /logs /reports /projects /database-settings /auth
Custom Render and Custom Render Hook
customRender and customRenderHook are wrappers around render and renderHook that add some necessary providers like QueryClientProvider, TooltipProvider and NuqsTestingAdapter.
Generally use those instead of the default render and renderHook functions.
import { customRender, customRenderHook } from 'tests/lib/custom-render'
customRender(<MyComponent />)
customRenderHook(() => useMyHook())
Mocking API Requests
To mock API requests, we use the msw library.
Global mocks can be found in tests/lib/msw-global-api-mocks.ts.
To mock an endpoint you can use the addAPIMock function. Make sure to add the mock in the beforeEach hook. It won't work with beforeAll if you have many tests.
beforeEach(() => {
addAPIMock({
method: 'get',
path: '/api/my-endpoint',
response: {
data: { foo: 'bar' },
},
})
})
API Mocking Tips:
- Keep mocks in the same folder as the tests that use them
- Add a test to verify the mock is working
This will make debugging and updating the mocks easier.
test('mock is working', async () => {
const response = await fetch('/api/my-endpoint')
expect(response.json()).resolves.toEqual({ data: { foo: 'bar' } })
})
Mocking Nuqs URL Parameters
To render a component that uses Nuqs with some predefined query parameters, you can use customRender with the nuqs prop.
customRender(<MyComponent />, {
nuqs: {
searchParams: {
search: 'hello world',
},
},
})
<Popover> vs <Dropdown>
When simulating clicks on these components, do the following:
// for Popovers
import userEvent from '@testing-library/user-event'
await userEvent.click('Hello world')
// for Dropdowns
import clickDropdown from 'tests/helpers'
clickDropdown('Hello world')