Skip to content

Conversation

@lordscarlet
Copy link
Contributor

@lordscarlet lordscarlet commented Aug 23, 2025

Closes #143

This pull request significantly improves the layout, accessibility, and visual consistency of the GoodsTable component, 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 new HeaderHex component 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:

  • Refactored the GoodsTable to render columns grouped into White and Black sections, each with improved headers and layout, using new CSS classes like groupsGrid, groupHeader, blackColumn, and blackHeader for clearer visual separation and alignment. [1] [2] [3] [4] [5]

Visual Consistency and Accessibility:

  • Introduced the HeaderHex component 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.
  • Improved column and cell sizing, padding, and alignment to ensure consistent appearance and spacing across both White and Black groups. [1] [2]

Color Handling:

  • Added the readGoodColor utility to dynamically read the --good-color CSS 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:

  • Updated imports in goods_table.tsx to include the new color utility and supporting functions for polygon/hex rendering.
image

These changes collectively make the goods table more visually appealing, maintainable, and accessible, while ensuring color consistency with the rest of the application.

@lordscarlet
Copy link
Contributor Author

@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.

@lordscarlet lordscarlet marked this pull request as ready for review August 23, 2025 04:03
Copilot AI review requested due to automatic review settings August 23, 2025 04:03
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment on lines +7 to +18
export function readGoodColor(g: Good): string {
if (cache.has(g)) return cache.get(g)!;
if (typeof document === "undefined") return fallback;
try {
const el = document.createElement("div");
el.className = goodStyle(g);
el.style.position = "absolute";
el.style.visibility = "hidden";
el.style.pointerEvents = "none";
document.body.appendChild(el);
const value = getComputedStyle(el).getPropertyValue("--good-color");
document.body.removeChild(el);
Copy link

Copilot AI Aug 23, 2025

Choose a reason for hiding this comment

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

Creating and removing DOM elements for each color read is expensive. Consider using a single persistent hidden element that gets its className updated, or batch multiple color reads to minimize DOM manipulation.

Suggested change
export function readGoodColor(g: Good): string {
if (cache.has(g)) return cache.get(g)!;
if (typeof document === "undefined") return fallback;
try {
const el = document.createElement("div");
el.className = goodStyle(g);
el.style.position = "absolute";
el.style.visibility = "hidden";
el.style.pointerEvents = "none";
document.body.appendChild(el);
const value = getComputedStyle(el).getPropertyValue("--good-color");
document.body.removeChild(el);
// Persistent hidden element for reading good color
let persistentEl: HTMLDivElement | null = null;
function getPersistentEl(): HTMLDivElement {
if (!persistentEl) {
persistentEl = document.createElement("div");
persistentEl.style.position = "absolute";
persistentEl.style.visibility = "hidden";
persistentEl.style.pointerEvents = "none";
persistentEl.style.height = "0";
persistentEl.style.width = "0";
persistentEl.style.overflow = "hidden";
document.body.appendChild(persistentEl);
}
return persistentEl;
}
export function readGoodColor(g: Good): string {
if (cache.has(g)) return cache.get(g)!;
if (typeof document === "undefined") return fallback;
try {
const el = getPersistentEl();
el.className = goodStyle(g);
const value = getComputedStyle(el).getPropertyValue("--good-color");

Copilot uses AI. Check for mistakes.
Comment on lines +142 to +143
const avail = (availableCities as any[]).find((a) =>
a.onRoll.some((r: any) => r.group === cityGroup && r.onRoll === onRoll),
Copy link

Copilot AI Aug 23, 2025

Choose a reason for hiding this comment

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

Using any type defeats TypeScript's type safety. Define proper interfaces for availableCities and its elements to ensure type safety and better code maintainability.

Suggested change
const avail = (availableCities as any[]).find((a) =>
a.onRoll.some((r: any) => r.group === cityGroup && r.onRoll === onRoll),
const avail = (availableCities as AvailableCity[]).find((a: AvailableCity) =>
a.onRoll.some((r) => r.group === cityGroup && r.onRoll === onRoll),

Copilot uses AI. Check for mistakes.
Comment on lines +170 to +171
const avail = (availableCities as any[])[letterIndex];
const colorVal = avail.color;
Copy link

Copilot AI Aug 23, 2025

Choose a reason for hiding this comment

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

Using any type and accessing properties without type checking. This should use proper typing and null checks to prevent runtime errors if avail is undefined or doesn't have a color property.

Suggested change
const avail = (availableCities as any[])[letterIndex];
const colorVal = avail.color;
const avail = (availableCities as { color?: Good | Good[] }[])[letterIndex];
const colorVal = avail && "color" in avail ? avail.color : undefined;

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@lordscarlet lordscarlet marked this pull request as draft August 23, 2025 12:49
// 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) =>

Choose a reason for hiding this comment

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

Isn't this just the city&urbanizedCity above? Why do you need to find it again?

}
// final fallback: use the goods currently in the city/urbanized city if present
if (primaryGood == null && city != null) {
for (const g of city) {

Choose a reason for hiding this comment

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

What is this? This is weird. If the city has no color, then you should have a colorless fallback. See Alabama for an example.

if (primaryGood == null && urbanizedCity != null) {
for (const g of urbanizedCity) {
if (g != null) {
primaryGood = g as Good;

Choose a reason for hiding this comment

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

Isn't this urbanizedGood, the thing you're doing below?

const fillColor = primaryGood != null ? readGoodColor(primaryGood) : defaultColor;
const goodClass = primaryGood != null ? goodStyle(primaryGood) : "";

function parseHexOrRgb(color: string): [number, number, number] {

Choose a reason for hiding this comment

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

Can you move these functions to the module level? That way we can guarantee you're not mixing variables from the function itself.

@@ -0,0 +1,26 @@
import { Good } from "../../engine/state/good";

Choose a reason for hiding this comment

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

Use the filename read_good_color to match the convention of the project.

@lordscarlet
Copy link
Contributor Author

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add colors to NewCity characters

2 participants