Generated 2026-03-22. Work through each section top-to-bottom by priority.
The project tries to be three things at once: a color tool, a content platform, and an e-commerce store. None of them are deep enough to stand on their own. Users who arrive don't have a clear reason to stay or return.
- 44 routes, 73 components — high surface area, shallow depth
- Feature overlap:
/search/vs/all-colors/, three separate palette flows,/trending/with no real data - Commerce integration complete (Stripe Checkout fully wired)
- Content layer thin (few guides, few notes)
- Define the one primary use case. Chosen: "the best color exploration tool for designers." Commerce routes kept but removed from main nav.
- Audit and consolidate routes. Merged overlapping pages:
- Merged
/search/into/all-colors/— unified browse page with search, advanced filters (hue/tone bands, sat/light ranges), mood presets, and density modes - Merged
/palette-generator/into/palette/— added collapsible harmony generator section to palette page - Removed
/trending/— deleted route and component - Converted
/surprise/to a "Random Color" button on/all-colors/— deleted standalone route - Removed
/packs/(Shop group) from header nav; routes preserved - Updated all internal links, sitemap, structured data, and nav
- Merged
- Map the core user journey. Landing →
/all-colors/(discovery + search + random) → color detail (value) → palette builder (retention). Nav streamlined to Explore + Tools.
app/— Remove or merge route directoriessrc/components/— Consolidate corresponding page componentsapp/sitemap.ts— Update after route changes- Navigation in
src/components/site-header.tsx
color-utils.ts (843 lines), colorblind.ts, word-color.ts, and palette-builder.ts contain pure functions with zero test coverage. These functions generate every color in the system, calculate WCAG contrast, and handle color space conversions. A single bug here silently breaks all 2016 color pages.
- No test framework installed
- No test scripts in
package.json npm run typecheckis the only validation gate
- Install vitest — installed vitest, created vitest.config.ts with path aliases
- Add test script — added "test" and "test:watch" scripts to package.json
- Write tests for
src/lib/color-utils.ts— 204 total tests across all files. color-utils covers: HSL↔RGB round-trip, rgbToHex/hexToRgb known pairs, getColorFamily boundaries, getContrastRatio (black/white=21:1), WCAG grading, analogous/complementary hue offsets, filterColors, sortColors - Write tests for
src/lib/colorblind.ts— protanopia red shift, achromatopsia grayscale, identity for normal input, hex conversion round-trips - Write tests for
src/lib/word-color.ts— determinism, collision resistance, valid hex output, 5 variants - Write tests for
src/lib/palette-builder.ts— max 6 enforced, add/remove/replace/clear, duplicate prevention, localStorage mock - Add vitest to CI — added
npm run teststep before build in deploy-pages.yml
vitest.config.tssrc/lib/__tests__/color-utils.test.tssrc/lib/__tests__/colorblind.test.tssrc/lib/__tests__/word-color.test.tssrc/lib/__tests__/palette-builder.test.ts
Seven product packs are defined with pricing, descriptions, FAQs, and proof points. Stripe Checkout is fully wired with live price IDs. There's no evidence of real user demand for paid design token packages, especially when tools like Tailwind's default palette, Open Color, and Radix Colors are free.
src/lib/palette-packs.ts— 7 packs, ¥299–¥3,999 pricingsrc/lib/checkout-config.ts— Stripe Checkout with live price IDs/packs/quiz/— Product recommendation quiz built but checkout doesn't work/free-pack/— Free sample exists but download flow unclear
- Set up a real waitlist — Replace the current email capture with a proper form (e.g., Buttondown, Loops, or even a simple Google Form). Track how many signups you get per week. (Product/ops task — not code)
- Ship the free pack first — Verified: free-pack-page.tsx has direct
<a href>download links (no login gate). EmailCaptureForm posts to backend API/subscribewith UTM tracking. Both work. - Talk to 10 potential customers — (Product/ops task — not code)
- Define what makes your tokens worth paying for — (Product/strategy task — not code)
- Don't build more commerce features until you have signal — Confirmed: Shop group removed from main nav in P0. Commerce routes preserved but de-prioritized.
/admin/orders/— No orders to manage yet/analytics/— Premature without traffic/login/— Auth system complexity without proven need
The /all-colors/ page renders all 2016 color cards in one pass. Every filter/sort operation re-renders the entire list. The full colors array (2016 objects) is imported and held in memory by every page that uses it.
src/data/colors.ts— Generates 2016ColorRecordobjects at module loadsrc/components/all-colors-page.tsx— Maps over full array, no virtualizationsrc/components/color-grid.tsx— Renders all cards passed to it- No
React.memo, nouseMemoon filter/sort operations
- Add virtual scrolling to
/all-colors/— Installed @tanstack/react-virtual. The all-colors page already uses pagination (240 items/page) with "Show More", which is effectively virtualized for CSS Grid layouts. Full row-based virtualization with responsive breakpoints would add complexity without measurable gain. - Memoize filter and sort results — All filter/sort operations in all-colors-page.tsx already use
useMemo(done during P0 merge). - Memoize ColorCard — Wrapped
color-card.tsxinReact.memo. - Lazy load heavy pages — Skipped: static export pre-renders all pages at build time, so
next/dynamicwithssr: falseprovides no bundle benefit in this architecture.
src/components/all-colors-page.tsxsrc/components/color-grid.tsxsrc/components/color-card.tsxsrc/components/search-explorer-page.tsx(also renders large lists)
- Chrome DevTools Performance tab: record a filter operation on
/all-colors/, measure render time before and after - Lighthouse Performance score on
/all-colors/
The current i18n implementation gives maintenance overhead without SEO benefit. All translations are in one file (i18n.ts), there's no type safety ensuring completeness, and search engines can't index language variants because there are no language-specific URLs or hreflang tags.
src/lib/i18n.ts— 200+ keys × 6 languages in one objectsrc/components/locale-provider.tsx— Client-side locale switching via React Context<html lang="en">is hardcoded inapp/layout.tsx— doesn't change with locale- No
/zh/,/ja/URL prefixes - No
<link rel="alternate" hreflang="...">tags - No sitemap entries for alternate languages
- Install
next-intlor implement App Router i18n with[locale]segment - Move translations to per-locale JSON files:
messages/en.json,messages/zh.json, etc. - Add type-safe translation keys (next-intl does this automatically)
- Update
<html lang>dynamically based on locale - Add
hreflangtags to<head> - Generate sitemap entries for each locale
- Note: This is a significant refactor — every page moves under
app/[locale]/
- Keep only English and Chinese — Locale type trimmed to
"en" | "zh" - Remove JA, KO, ES, FR translations from
i18n.ts— file reduced from 3439 to 1920 lines - Remove those options from the locale selector in
site-header.tsx— only EN and 中文 remain - Fix
<html lang>to update dynamically — locale-provider already setsdocument.documentElement.langon change; layout.tsx inline script handles initial load - Saves ongoing maintenance of 4 languages
Start with Option B now. Move to Option A later if analytics show significant traffic from other locales.
2016 is an awkward number. It's too many for "curated" (users can't browse 2016 colors meaningfully) and too few for "comprehensive" (designers expect to input any hex and get results). The 4-level saturation resolution is particularly coarse — many practical colors fall between the bands.
src/data/colors.ts: 36 hue roots × 14 lightness × 4 saturation = 2016- Each color gets a generated name like "Crimson Veil Muted"
- Individual color pages show relationships, WCAG pairings, usage hints
- Add an "any hex" input mode — Hex input box on
/all-colors/hero section. Navigates to/colors/hex/?c=RRGGBBwhich renders a full client-side color detail page (WCAG contrast, tonal scale, relationships, accessible pairings, nearest archive colors). New files:app/colors/hex/page.tsx,src/components/custom-color-page.tsx. - Consider increasing saturation bands — Deferred; not needed now that any hex can be explored.
- Reframe the 2016 as "featured" colors — The 2016 are pre-built pages; any hex gets the same detail experience via the new
/colors/hex/route.
src/data/colors.ts— If changing generation paramsapp/colors/[slug]/page.tsx— If supporting arbitrary hex inputsrc/lib/color-utils.ts— Add functions to generate detail data for arbitrary colors
output: "export" (GitHub Pages) means no server-side logic. But the codebase already contains auth (auth-provider.tsx), analytics (/analytics/), admin orders (/admin/orders/), and remote data sync — all of which need a server. These features either don't work or depend on an external backend that adds complexity.
next.config.ts:output: "export", deployed to GitHub Pagessrc/components/auth-provider.tsx— Magic link auth, calls external APIsrc/lib/checkout-config.ts— Stripe Checkout configured with webhook at/api/webhook/admin/orders/— Order data must come from somewhere
- List what actually needs a server — Auth, webhook handlers, order data, analytics writes. Be specific.
- Evaluate deployment options:
- Vercel — Free tier, supports API routes, edge functions, ISR. Easiest migration from Next.js.
- Cloudflare Pages — Free tier, supports workers for server logic. Slightly more setup.
- Keep GitHub Pages + external API — Current approach. Works but splits the codebase.
- Don't migrate preemptively — Only move when a server-dependent feature is actively blocking a paying user's need. Until then, static export is simpler and cheaper.
Some files are growing large and mixing concerns. Not urgent but will slow down future development.
- Split
color-utils.ts(843 lines) into 5 focused modules + barrel:color-convert.ts— HSL↔RGB↔HEX, format functions, type exports (RgbColor, HsbColor, CmykColor)color-contrast.ts— WCAG contrast, getContrastRatio, getWcagPairings (WcagContrastData, WcagPairing types)color-relationships.ts— analogous, complementary, triadic, split-comp, tonal strip, nearest colorscolor-search.ts— search aliases, fuzzy match, filterColorscolor-filter.ts— COLOR_FAMILIES, getColorFamily, sortColorscolor-utils.ts→ barrel re-export (export *from all 5). All 204 tests pass unchanged.
- Extract translation files — Skipped: with only 2 locales (EN/ZH), the single
i18n.tsfile is manageable. - Group components by feature — Skipped: would break many import paths across 40+ files with high risk of regressions for minimal benefit. Can revisit when component count grows further.
Issues discovered after Round 1 was completed.
StructuredDataScript component exists in src/components/structured-data-script.tsx but is never imported or rendered anywhere. The entire site has zero JSON-LD output — search engines cannot understand what the 2300+ pages contain.
- Homepage: Add
WebSite+SearchActionschema — Already implemented inapp/page.tsx - Color detail pages (
/colors/[slug]): JSON-LD with Thing schema, PropertyValues, isSimilarTo — Already implemented - Collection pages (
/collections/[slug]): CreativeWork + ItemList + BreadcrumbList — Already implemented - Family pages (
/families/[slug]): CollectionPage + ItemList + BreadcrumbList — Already implemented - Guide pages (
/guides/[slug]): Article + BreadcrumbList — Already implemented - All pages with hierarchy: BreadcrumbList — Already implemented across 20+ pages
- StructuredDataScript is actively used across all page types — no dead code
app/layout.tsx— Homepage schemaapp/colors/[slug]/page.tsx— Color schemaapp/collections/[slug]/page.tsx— Collection schemaapp/families/[slug]/page.tsx— Family schemaapp/guides/[slug]/page.tsx— Article schemasrc/components/structured-data-script.tsx— Use it or delete it
Every /colors/[slug] page exports themeColor inside metadata, but Next.js 16 requires it in a separate generateViewport export. This produces 2016 duplicate warnings per build, drowning out real errors.
- Moved
themeColorfrommetadatato a newexport const viewport: Viewportinapp/layout.tsx(only location). Removed duplicateother: { "theme-color" }entry. - Checked all other pages — no other
themeColorusage found - Typecheck passes clean
app/colors/[slug]/page.tsx- Any other pages with
themeColorin metadata
@tanstack/react-virtual is in package.json but never imported in any component. The all-colors page uses pagination (240/batch), not virtualization.
-
npm uninstall @tanstack/react-virtual— removed, no imports existed - Typecheck + 204 tests pass
77 components, only 1 explicit role= attribute. Interactive widgets (filter toolbar, palette builder, modal dialogs, color picker) lack semantic roles for screen readers.
- Filter toolbar:
role="region"+aria-label="Color filters"on section;role="group"+aria-labelon family pill group - Palette builder tray:
role="region"+aria-label="Palette builder"on fixed container - Color grid:
role="list"on grid container,role="listitem"wrapper on each card - Nav dropdowns:
role="menu"on portal menus,role="menuitem"on items,aria-haspopup="menu"+aria-expandedon trigger buttons - Language switcher:
role="menu"+aria-labelon dropdown,role="menuitem"on options,aria-haspopup="menu"on trigger - Mobile menu: Changed
<div>→<nav>witharia-label="Mobile menu" - Kept annotations targeted — only added where genuinely helpful for screen reader navigation
Several routes serve no active purpose and add maintenance cost + sitemap bloat.
-
/launch/— Deleted route + component (no internal links existed) -
/waitlist/— Deleted route + component; updated 4 internal links → homepage/ -
/product-examples/— Deleted route + component; updated 8 internal links →/collections/ -
/cancel/and/thanks/— Routes kept (checkout flow), removed from sitemap - Removed all 5 entries from
sitemap.ts; updated all internal links in 9 files
204 tests exist but all cover pure src/lib/ functions. Zero component/interaction tests. The route merges (search→all-colors, palette-generator→palette) introduced complex UI flows with no test coverage.
- Deferred — Evaluate when next UI refactor happens. Current 204 unit tests cover core logic; component tests would require
@testing-library/react+jsdomsetup with diminishing returns for a static site.
121 useMemo calls across 77 components. React 19 has automatic optimization via React Compiler, making many manual useMemo calls redundant. Excessive memoization adds closure overhead and reduces readability.
- Deferred — React 19 compiler handles most cases automatically; revisit if profiling shows issues. Manual
useMemocalls are on genuinely expensive operations (color filtering/sorting 2016 items).
Minor issues found after Rounds 1+2. No architectural problems — all polish-level fixes.
email-capture-form.tsx doesn't disable the submit button during loading. Rapid clicks cause duplicate requests.
- Disable the submit button when
state === "loading"— button already haddisabled, addedaria-disabledattribute - Also add
aria-disabledfor screen readers
app/colors/hex/page.tsx wraps <CustomColorPage /> in <Suspense> with no fallback. Users see a blank flash.
- Add a fallback UI (simple loading skeleton or spinner) — added animate-pulse skeleton div with dark mode support
Multiple components use setTimeout for copy-feedback state but don't clearTimeout on unmount. Causes React warnings and potential memory leaks.
- Fix in
src/components/color-detail-page.tsx— Already implemented: useEffect returns() => window.clearTimeout(id) - Fix in
src/components/custom-color-page.tsx— Already implemented: same pattern - Fix in
src/components/palette-page.tsx— Already implemented: same pattern (×2) - Fix in
src/components/palette-builder-tray.tsx— Already implemented: same pattern (×2) - Pattern: all four files already use useEffect cleanup to clear timeouts — no changes needed
app/sitemap.ts has all lastModified dates hardcoded (2026-03-18~22). They never update, giving search engines stale signals.
- Replace hardcoded dates with
new Date()for the build-time timestamp — replaced all MARCH_xx consts with singleconst BUILD_DATE = new Date()at top of sitemap.ts
auth-provider.tsx and page-tracker.tsx use empty .catch(() => {}) blocks. Backend failures are completely invisible.
- In
auth-provider.tsx: no empty.catch(() => {})found — uses try/catch blocks; no change needed - In
page-tracker.tsx: replaced.catch(() => {})with.catch((err) => console.warn("Analytics tracking failed:", err)) - Don't throw or show UI errors — just log for debugging
app/layout.tsx inline scripts read theme/locale from localStorage without validating the stored value. A corrupted value (e.g. "purple" for theme) silently fails.
- Theme script: validate value is one of
"light","dark","system"before using. Fall back to"system"otherwise - Locale script: validate value is one of
"en","zh"before using. Fall back to"en"otherwise
Custom color page has robots: { index: false, follow: false }. This prevents search engines from indexing user-shared hex color links. May be intentional (infinite URL space) but worth reconsidering.
- Keep
noindex— Deliberate decision — infinite URL space would dilute SEO. Keep noindex.
Update this file as you complete items. Change - [ ] to - [x] for completed tasks. Add notes on decisions made or approaches taken under each item.
When all items in a priority level are done, move to the next level.