Refactor GoodsTable layout and add readGoodColor utility for dynamic good color retrieval#148
Conversation
|
@YourDeveloperFriend, any idea why the tests are failing? I tried a few things, but I don't want to dig in too much on an unrelated change. |
There was a problem hiding this comment.
Pull Request Overview
This PR refactors the GoodsTable layout to improve visual organization and add dynamic SVG hex headers that match map hex colors. The changes enhance the table's visual clarity by separating white and black column groups and ensuring color consistency with the game map.
- Restructured the CSS grid layout to group white and black columns separately with dedicated headers
- Added dynamic SVG hex headers that retrieve colors from CSS variables to match map hexes
- Implemented visual styling for black columns with dark backgrounds and white text
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/client/grid/readGoodColor.ts | New utility function that reads CSS --good-color variables dynamically with DOM manipulation and caching |
| src/client/game/goods_table.tsx | Major refactor of table layout with grouped columns, HeaderHex component, and dynamic color logic |
| src/client/game/goods_table.module.css | Updated CSS with new grid layout, group styling, and black column visual enhancements |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| // determine a primary good color for this onRoll column | ||
| let primaryGood: Good | undefined = undefined; | ||
| // first try to find the actual City on the map with this onRoll/group so color matches the map hex | ||
| const mapCity = grid.cities().find((c) => |
There was a problem hiding this comment.
Isn't this just the city&urbanizedCity above? Why do you need to find it again?
There was a problem hiding this comment.
Good call. Updated in commit 2c6d0c8: I simplified the logic and removed the extra fallback search path, keeping color selection focused on the intended city mapping for this column.
| } | ||
| // final fallback: use the goods currently in the city/urbanized city if present | ||
| if (primaryGood == null && city != null) { | ||
| for (const g of city) { |
There was a problem hiding this comment.
What is this? This is weird. If the city has no color, then you should have a colorless fallback. See Alabama for an example.
There was a problem hiding this comment.
Addressed in commit 2c6d0c8 by removing the weird fallback path and keeping a clear fallback behavior in HeaderHex (defaultColor) when no city color is available.
| if (primaryGood == null && urbanizedCity != null) { | ||
| for (const g of urbanizedCity) { | ||
| if (g != null) { | ||
| primaryGood = g as Good; |
There was a problem hiding this comment.
Isn't this urbanizedGood, the thing you're doing below?
There was a problem hiding this comment.
Yep, agreed. Updated in commit 2c6d0c8 so letter color handling is separated and explicit: letterGood comes from availableCities for A..H, and falls back to primaryGood when absent.
| const fillColor = primaryGood != null ? readGoodColor(primaryGood) : defaultColor; | ||
| const goodClass = primaryGood != null ? goodStyle(primaryGood) : ""; | ||
|
|
||
| function parseHexOrRgb(color: string): [number, number, number] { |
There was a problem hiding this comment.
Can you move these functions to the module level? That way we can guarantee you're not mixing variables from the function itself.
There was a problem hiding this comment.
Done in commit 2c6d0c8. I moved parseHexOrRgb and luminance to module scope so they are no longer defined inside HeaderHex.
| @@ -0,0 +1,26 @@ | |||
| import { Good } from "../../engine/state/good"; | |||
There was a problem hiding this comment.
Use the filename read_good_color to match the convention of the project.
There was a problem hiding this comment.
Done in commit 2c6d0c8. Renamed readGoodColor.ts to read_good_color.ts and updated imports accordingly.
|
@YourDeveloperFriend I will get back to this, btw -- I had a lot of work and other distractions the past couple of months and dropped off. But I also think this is a fairly low priority as well, and I may dabble in another issue before coming back to it, if you don't mind. |
…Color, improve type safety, and refactor logic - Rename readGoodColor.ts to read_good_color.ts to match project conventions - Optimize readGoodColor to use persistent hidden element instead of creating/removing DOM elements for each call - Extract parseHexOrRgb and luminance functions to module level to avoid closure issues - Fix type safety: replace 'any' types with MutableAvailableCity import and proper typing - Simplify letter good color logic: clarify separation between primaryGood (from map) and letterGood (from availableCities) - Remove unused goodClass from HeaderHex SVG - All tests passing
- Remove 'White' / 'Black' section headers (distinguished by background only) - Change black-dice background from dark grey (#333) to warm brown (#5d4037) - Apply brown background to entire black section (continuous, not per-column) - Replace SVG hex letter headers with tab-shaped badges matching physical board - Remove empty placeholder slots for columns without urbanized cities - Use Roboto sans-serif font for all table headers - Ensure all text meets WCAG AA contrast (4.5:1 minimum): - White-side numbers: YourDeveloperFriend#222 on white (~17.6:1) - Black-side numbers: #fff on #5d4037 (~9.4:1) - Letter badges: auto-selected via chooseBestTextColor() - Remove unused polygon import and orphaned CSS classes
|
Updated the Goods Growth Table based on the design discussion in #143. Changes
WCAG AA ComplianceAll text in the table meets WCAG AA contrast guidelines (minimum 4.5:1 ratio):
The
|
…ble and improve code quality Critical fixes: - File already correctly named read_good_color.ts (snake_case convention) - Add bounds checking for availableCities array access to prevent runtime errors - Remove redundant grid.cities().find() by caching City objects in useMemo - Replace magic numbers with named constants (TOTAL_COLUMNS, WHITE_COLUMNS, etc.) Code quality improvements: - Extract column generation to createGoodsColumn helper function for better separation - Add memoization for column generation with proper dependencies - Add development-mode error logging in readGoodColor with ESLint pragma - Improve type safety throughout All changes follow established codebase patterns and conventions. Addresses unresolved owner comments regarding redundant lookups and code organization.
…nit tests Quality improvements: - Add cleanupGoodColorReader() export for proper resource cleanup and testing - Replace hardcoded color #5d4037 with --black-group-bg CSS variable - Replace hardcoded #e69074 with COLORLESS_DEFAULT constant referencing CSS - Add comprehensive unit tests for readGoodColor utility Testing: - Create read_good_color_test.ts with Jasmine tests - Test caching behavior, multiple goods, and cleanup functionality - Follow established test patterns from codebase Theme consistency: - Define CSS variables in goods_table.module.css - Document constant-to-CSS-variable relationship for maintainability All enhancements follow established patterns and pass TypeScript/ESLint checks.
|
Dark-mode follow-up on this branch is complete and verified. What changed:
Validation:
|
|
Pulled latest main into this branch, which brought in the new unified Checks workflow (Build/Test/Format/Lint). I then updated the branch to satisfy those new gates:
Result: this branch has been updated specifically to pass the new CI checks introduced from main. |
JackOfMostTrades
left a comment
There was a problem hiding this comment.
Two broad comments on this one:
- While this is very much a matter of personal sense of aesthetics and taste, I feel like the amount of contrast between the white background and the dark brown background in light mode is a lot. Whereas in dark mode, there's not nearly so much contrast between the nearly-black left side and the dark brown right side. Could you see if there's a lighter color background for right side in light mode that's less of a stark contrast? I'd be inclined to go so far as to make it a color that's light enough that white text headers would still be appropriate.
- It feels like there's a lot of new code needed now to render the goods table... could these color choices just be calculated one time (maybe using a one-off script checked into the scripts dir) and encoded into the CSS and class names used when rendering the table? And have a pre-computed lookup table of Good => dark/light text?


Closes #143
This pull request significantly improves the layout, accessibility, and visual consistency of the
GoodsTablecomponent, especially for distinguishing between White and Black groups. The changes introduce new CSS for better grouping and styling, refactor the table rendering logic to use these groups, and add a newHeaderHexcomponent for consistent hex-shaped headers with dynamic coloring. Additionally, a utility is added to reliably read good colors from CSS at runtime.UI and Layout Improvements:
GoodsTableto render columns grouped into White and Black sections, each with improved headers and layout, using new CSS classes likegroupsGrid,groupHeader,blackColumn, andblackHeaderfor clearer visual separation and alignment. [1] [2] [3] [4] [5]Visual Consistency and Accessibility:
HeaderHexcomponent to render hex-shaped SVG headers for each column, matching the map's style and dynamically coloring the hex using the correct good color, with automatic text color for contrast.Color Handling:
readGoodColorutility to dynamically read the--good-colorCSS variable for a given good, ensuring that color assignments in the UI always match the CSS source of truth. [1] [2] [3] [4]Code Organization:
goods_table.tsxto include the new color utility and supporting functions for polygon/hex rendering.These changes collectively make the goods table more visually appealing, maintainable, and accessible, while ensuring color consistency with the rest of the application.