In the admin interface I'm seeing the following issues
In dark mode the selected tab name is not visible (no contrast)
When adding/editing an item the category selector interface doesn't follow the selected theme UI
The Category tab doesn't follow the selected theme UI
The Tag tab doesn't follow the selected theme UI
The tab tab also shows that the UI is not in American english (colour vs color). The UI should be in American english
On the Customers tab I need to know the number of items that customer currently has in Reserved status. That should be a link with a popup that displays the item name, and a button to release the item.
The Inventory tab should also support the same filtering that the main UI does, and it should also allow for Reserved filtering
Category
Tag(s)
Price Arrange
In the admin interface I'm seeing the following issues
- In dark mode the selected tab name is not visible (no contrast)
- When adding/editing an item the category selector interface doesn't follow the selected theme UI
- The Category tab doesn't follow the selected theme UI
- The Tag tab doesn't follow the selected theme UI
- The tab tab also shows that the UI is not in American english (colour vs color). The UI should be in American english
- On the Customers tab I need to know the number of items that customer currently has in `Reserved` status. That should be a link with a popup that displays the item name, and a button to release the item.
- The Inventory tab should also support the same filtering that the main UI does, and it should also allow for `Reserved` filtering
- Category
- Tag(s)
- Price Arrange
<img width="1440" alt="image.png" src="attachments/3abbdfb3-6a7a-45d7-a348-21f0ac63230c">
<img width="1440" alt="image.png" src="attachments/4b4ee04c-7230-41a2-a685-ff51d1352fdb">
<img width="1440" alt="image.png" src="attachments/e9f3bbad-79e6-4d2f-8c17-02f08aba0d30">
Both confirmed by measuring computed styles in dark mode rather than by inspection:
Measurement
Value
Meaning
Active tab label color
rgb(25,25,25)
colorPrimary: '#1a1a1a' applied in both themes — near-black text on a dark background
Categories tree background
rgb(255,255,255)
Pure white while in dark mode
Categories tree text
rgba(0,0,0,0.88)
Light theme, so the component never saw ConfigProvider
Bullet 1 — dark tab invisible.colorPrimary is hardcoded to #1a1a1a regardless of theme.
Bullets 2, 3, 4 — category selector, Category tab, Tag tab don't follow the theme. One cause for all three. antd's package.json declares main: lib/index.js and module: es/index.js, so import … from 'antd' resolves to the ES build while import … from 'antd/lib/…' loads the CommonJS build. Two copies of antd, two React context instances — components imported from antd/lib/* never receive the theme from ConfigProvider.
The affected files are exactly the reported ones, plus two on the storefront that have the same latent bug and hadn't been noticed:
src/admin/Categories.tsx <- Category tab
src/admin/Tags.tsx <- Tag tab
src/admin/CategoryTreeSelect.tsx <- category selector in the item form
src/components/FilterDrawer.tsx <- storefront (unreported)
src/components/ActiveFilterChips.tsx <- storefront (unreported)
src/customer/VerifyEmail.tsx <- pre-existing, same bug
Fix is antd/lib/* → antd/es/*, which keeps the deep-import convention and shares the module instance. Stating it plainly: this was introduced by my own use of the lib path when applying the deep-import convention; es is the correct path for a Vite build.
Bullet 5 — American English. Three occurrences of "Colour", two of them user-visible (the Tags column header and the edit-modal label).
Decisions on the remaining items
Q1 — dark mode accent colour
Option
A. Light neutral in dark mode
#1a1a1a in light, #f0f0f0 in dark — the accent inverts, keeping the monochrome brand in both themes
B. One mid-gray for both
Single token, but primary buttons look washed out on white
C. antd default blue in dark
High contrast, but introduces a hue the brand doesn't use
Answer: A.
Q2 — Inventory tab filter UI, and how Reserved is handled
Option
A. Inline filter row + Status dropdown
Controls sit above the table, always visible; Reserved becomes one option in a Status filter (All / Available / Reserved / Sold) rather than a standalone toggle
B. Reuse the storefront drawer + chips
Consistent, but hides controls behind a click and overlays a wide table
C. antd per-column filter dropdowns
Compact, but price range doesn't fit the pattern
Answer: A. Filtering is server-side, reusing the same query builder as the storefront, so results stay correct across pagination.
Q3 — what Release does
Option
A. Return to available, no email
Removes the cart row and sets the item back to available — same effect as the customer removing it or the expiry sweep firing
B. Also email the customer
Emails about an action they didn't take, and SMTP is optional so it may silently not send
C. Only allow releasing expired reservations
The 5-minute sweep already handles those, so the button would rarely do anything
Answer: A.
Work
Theme-aware colorPrimary (bullet 1)
antd/lib/* → antd/es/* across six files (bullets 2–4)
"Colour" → "Color" (bullet 5)
Customers tab: reserved count as a link, popup listing items with Release (bullet 6)
## Root causes and design decisions
### Bullets 1–4 are two root causes, not four bugs
Both confirmed by measuring computed styles in dark mode rather than by inspection:
| Measurement | Value | Meaning |
| --- | --- | --- |
| Active tab label color | `rgb(25,25,25)` | `colorPrimary: '#1a1a1a'` applied in *both* themes — near-black text on a dark background |
| Categories tree background | `rgb(255,255,255)` | Pure white while in dark mode |
| Categories tree text | `rgba(0,0,0,0.88)` | Light theme, so the component never saw `ConfigProvider` |
**Bullet 1 — dark tab invisible.** `colorPrimary` is hardcoded to `#1a1a1a` regardless of theme.
**Bullets 2, 3, 4 — category selector, Category tab, Tag tab don't follow the theme.** One cause for all three. `antd`'s `package.json` declares `main: lib/index.js` and `module: es/index.js`, so `import … from 'antd'` resolves to the **ES** build while `import … from 'antd/lib/…'` loads the **CommonJS** build. Two copies of antd, two React context instances — components imported from `antd/lib/*` never receive the theme from `ConfigProvider`.
The affected files are exactly the reported ones, plus two on the storefront that have the same latent bug and hadn't been noticed:
```
src/admin/Categories.tsx <- Category tab
src/admin/Tags.tsx <- Tag tab
src/admin/CategoryTreeSelect.tsx <- category selector in the item form
src/components/FilterDrawer.tsx <- storefront (unreported)
src/components/ActiveFilterChips.tsx <- storefront (unreported)
src/customer/VerifyEmail.tsx <- pre-existing, same bug
```
Fix is `antd/lib/*` → `antd/es/*`, which keeps the deep-import convention *and* shares the module instance. Stating it plainly: this was introduced by my own use of the `lib` path when applying the deep-import convention; `es` is the correct path for a Vite build.
**Bullet 5 — American English.** Three occurrences of "Colour", two of them user-visible (the Tags column header and the edit-modal label).
---
### Decisions on the remaining items
**Q1 — dark mode accent colour**
| Option | |
| --- | --- |
| **A. Light neutral in dark mode** | `#1a1a1a` in light, `#f0f0f0` in dark — the accent inverts, keeping the monochrome brand in both themes |
| B. One mid-gray for both | Single token, but primary buttons look washed out on white |
| C. antd default blue in dark | High contrast, but introduces a hue the brand doesn't use |
**Answer: A.**
**Q2 — Inventory tab filter UI, and how Reserved is handled**
| Option | |
| --- | --- |
| **A. Inline filter row + Status dropdown** | Controls sit above the table, always visible; Reserved becomes one option in a Status filter (All / Available / Reserved / Sold) rather than a standalone toggle |
| B. Reuse the storefront drawer + chips | Consistent, but hides controls behind a click and overlays a wide table |
| C. antd per-column filter dropdowns | Compact, but price range doesn't fit the pattern |
**Answer: A.** Filtering is server-side, reusing the same query builder as the storefront, so results stay correct across pagination.
**Q3 — what Release does**
| Option | |
| --- | --- |
| **A. Return to available, no email** | Removes the cart row and sets the item back to available — same effect as the customer removing it or the expiry sweep firing |
| B. Also email the customer | Emails about an action they didn't take, and SMTP is optional so it may silently not send |
| C. Only allow releasing expired reservations | The 5-minute sweep already handles those, so the button would rarely do anything |
**Answer: A.**
---
### Work
- [ ] Theme-aware `colorPrimary` (bullet 1)
- [ ] `antd/lib/*` → `antd/es/*` across six files (bullets 2–4)
- [ ] "Colour" → "Color" (bullet 5)
- [ ] Customers tab: reserved count as a link, popup listing items with Release (bullet 6)
- [ ] Inventory tab: category / tags / price / status filtering, server-side (bullet 7)
Branch fix/admin-theme-and-filters, one commit, not yet pushed.
#
Item
Status
1
Dark mode selected tab invisible
Fixed
2
Category selector ignores theme
Fixed
3
Category tab ignores theme
Fixed
4
Tag tab ignores theme
Fixed
5
"Colour" → "Color"
Fixed
6
Customers: reserved count, popup, Release
Implemented
7
Inventory: category / tag / price / Reserved filtering
Implemented
Bullet 1 — dark tab contrast
colorPrimary was #1a1a1a in both themes. The accent now inverts with the theme. colorTextLightSolid had to invert with it — otherwise a near-white accent gets antd's default white label on primary buttons and disappears in the opposite direction.
Bullets 2–4 — one root cause
antd/lib/* loaded a second copy of antd with its own React context, so nothing deep-imported ever saw ConfigProvider. Six files moved to antd/es/*, which preserves the deep-import convention and shares the module instance. Two of those were storefront components with the same latent bug that hadn't been noticed yet.
Bullet 6 — reserved items
Release mirrors the customer's own cart removal: drop the cart_items row and return the item to available, in one transaction, guarded on status = 'reserved' so it can never resurrect a sold item. No email, per the decision above.
The count is a scalar subquery rather than another LEFT JOIN — joining a second one-to-many relation alongside orders would have multiplied rows and inflated the existing order_count and total_spent_cents columns.
Bullet 7 — inventory filtering
GET /api/admin/items now takes category, tags, min_price, max_price, and status, reusing the storefront's parser and query builder so admin and customer filtering cannot drift. Category matching still walks descendants; tags still require all. Filtering is server-side, so results stay correct across pagination.
Two further defects the screenshots exposed
Neither was reported, both found while verifying:
The reserved-count link bubbled to the row's click handler, so clicking it opened the customer detail drawer behind the dialog it had just opened.
.admin-category-node was referenced in Categories.tsx but the CSS class never existed, so each tree node rendered as one unbroken string: Decor fmsxpc67051 item Add child Rename Delete. Now the name, muted item count, and right-aligned actions are laid out properly.
Verification
Suite
Result
Backend unit
52 passed
Backend integration
74 passed (17 new)
Frontend e2e
46 passed, stable over 3 consecutive runs
tsc --noEmit backend / frontend
clean
npm run build
clean
The theme fixes have real regression tests rather than screenshots: they measure computed WCAG relative luminance, so they assert readability rather than exact hex values and won't break on a palette tweak. Each was confirmed failing against the old code first.
Note
Running the backend integration suite truncated the shared local dev database again mid-session — it defaults to the same redefined_test database the README uses for local development. Harmless locally, but it's a standing footgun worth its own issue.
## All seven items implemented
Branch `fix/admin-theme-and-filters`, one commit, not yet pushed.
| # | Item | Status |
| --- | --- | --- |
| 1 | Dark mode selected tab invisible | Fixed |
| 2 | Category selector ignores theme | Fixed |
| 3 | Category tab ignores theme | Fixed |
| 4 | Tag tab ignores theme | Fixed |
| 5 | "Colour" → "Color" | Fixed |
| 6 | Customers: reserved count, popup, Release | Implemented |
| 7 | Inventory: category / tag / price / Reserved filtering | Implemented |
### Bullet 1 — dark tab contrast
`colorPrimary` was `#1a1a1a` in both themes. The accent now inverts with the theme. `colorTextLightSolid` had to invert with it — otherwise a near-white accent gets antd's default white label on primary buttons and disappears in the opposite direction.
### Bullets 2–4 — one root cause
`antd/lib/*` loaded a second copy of antd with its own React context, so nothing deep-imported ever saw `ConfigProvider`. Six files moved to `antd/es/*`, which preserves the deep-import convention and shares the module instance. Two of those were storefront components with the same latent bug that hadn't been noticed yet.
### Bullet 6 — reserved items
Release mirrors the customer's own cart removal: drop the `cart_items` row and return the item to available, in one transaction, guarded on `status = 'reserved'` so it can never resurrect a sold item. No email, per the decision above.
The count is a scalar subquery rather than another `LEFT JOIN` — joining a second one-to-many relation alongside `orders` would have multiplied rows and inflated the existing `order_count` and `total_spent_cents` columns.
### Bullet 7 — inventory filtering
`GET /api/admin/items` now takes `category`, `tags`, `min_price`, `max_price`, and `status`, reusing the storefront's parser and query builder so admin and customer filtering cannot drift. Category matching still walks descendants; tags still require all. Filtering is server-side, so results stay correct across pagination.
### Two further defects the screenshots exposed
Neither was reported, both found while verifying:
- The reserved-count link bubbled to the row's click handler, so clicking it opened the customer detail drawer *behind* the dialog it had just opened.
- `.admin-category-node` was referenced in `Categories.tsx` but the CSS class never existed, so each tree node rendered as one unbroken string: `Decor fmsxpc67051 item Add child Rename Delete`. Now the name, muted item count, and right-aligned actions are laid out properly.
### Verification
| Suite | Result |
| --- | --- |
| Backend unit | 52 passed |
| Backend integration | 74 passed (17 new) |
| Frontend e2e | 46 passed, stable over 3 consecutive runs |
| `tsc --noEmit` backend / frontend | clean |
| `npm run build` | clean |
The theme fixes have real regression tests rather than screenshots: they measure computed WCAG relative luminance, so they assert readability rather than exact hex values and won't break on a palette tweak. Each was confirmed failing against the old code first.
### Note
Running the backend integration suite truncated the shared local dev database again mid-session — it defaults to the same `redefined_test` database the README uses for local development. Harmless locally, but it's a standing footgun worth its own issue.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
In the admin interface I'm seeing the following issues
Reservedstatus. That should be a link with a popup that displays the item name, and a button to release the item.ReservedfilteringRoot causes and design decisions
Bullets 1–4 are two root causes, not four bugs
Both confirmed by measuring computed styles in dark mode rather than by inspection:
rgb(25,25,25)colorPrimary: '#1a1a1a'applied in both themes — near-black text on a dark backgroundrgb(255,255,255)rgba(0,0,0,0.88)ConfigProviderBullet 1 — dark tab invisible.
colorPrimaryis hardcoded to#1a1a1aregardless of theme.Bullets 2, 3, 4 — category selector, Category tab, Tag tab don't follow the theme. One cause for all three.
antd'spackage.jsondeclaresmain: lib/index.jsandmodule: es/index.js, soimport … from 'antd'resolves to the ES build whileimport … from 'antd/lib/…'loads the CommonJS build. Two copies of antd, two React context instances — components imported fromantd/lib/*never receive the theme fromConfigProvider.The affected files are exactly the reported ones, plus two on the storefront that have the same latent bug and hadn't been noticed:
Fix is
antd/lib/*→antd/es/*, which keeps the deep-import convention and shares the module instance. Stating it plainly: this was introduced by my own use of thelibpath when applying the deep-import convention;esis the correct path for a Vite build.Bullet 5 — American English. Three occurrences of "Colour", two of them user-visible (the Tags column header and the edit-modal label).
Decisions on the remaining items
Q1 — dark mode accent colour
#1a1a1ain light,#f0f0f0in dark — the accent inverts, keeping the monochrome brand in both themesAnswer: A.
Q2 — Inventory tab filter UI, and how Reserved is handled
Answer: A. Filtering is server-side, reusing the same query builder as the storefront, so results stay correct across pagination.
Q3 — what Release does
Answer: A.
Work
colorPrimary(bullet 1)antd/lib/*→antd/es/*across six files (bullets 2–4)All seven items implemented
Branch
fix/admin-theme-and-filters, one commit, not yet pushed.Bullet 1 — dark tab contrast
colorPrimarywas#1a1a1ain both themes. The accent now inverts with the theme.colorTextLightSolidhad to invert with it — otherwise a near-white accent gets antd's default white label on primary buttons and disappears in the opposite direction.Bullets 2–4 — one root cause
antd/lib/*loaded a second copy of antd with its own React context, so nothing deep-imported ever sawConfigProvider. Six files moved toantd/es/*, which preserves the deep-import convention and shares the module instance. Two of those were storefront components with the same latent bug that hadn't been noticed yet.Bullet 6 — reserved items
Release mirrors the customer's own cart removal: drop the
cart_itemsrow and return the item to available, in one transaction, guarded onstatus = 'reserved'so it can never resurrect a sold item. No email, per the decision above.The count is a scalar subquery rather than another
LEFT JOIN— joining a second one-to-many relation alongsideorderswould have multiplied rows and inflated the existingorder_countandtotal_spent_centscolumns.Bullet 7 — inventory filtering
GET /api/admin/itemsnow takescategory,tags,min_price,max_price, andstatus, reusing the storefront's parser and query builder so admin and customer filtering cannot drift. Category matching still walks descendants; tags still require all. Filtering is server-side, so results stay correct across pagination.Two further defects the screenshots exposed
Neither was reported, both found while verifying:
.admin-category-nodewas referenced inCategories.tsxbut the CSS class never existed, so each tree node rendered as one unbroken string:Decor fmsxpc67051 item Add child Rename Delete. Now the name, muted item count, and right-aligned actions are laid out properly.Verification
tsc --noEmitbackend / frontendnpm run buildThe theme fixes have real regression tests rather than screenshots: they measure computed WCAG relative luminance, so they assert readability rather than exact hex values and won't break on a palette tweak. Each was confirmed failing against the old code first.
Note
Running the backend integration suite truncated the shared local dev database again mid-session — it defaults to the same
redefined_testdatabase the README uses for local development. Harmless locally, but it's a standing footgun worth its own issue.