Skip to content

Commit 470a112

Browse files
committed
Expand color palette from 10 to 40 distinct pastel colors Fixes #86
1 parent 1885102 commit 470a112

File tree

3 files changed

+146
-29
lines changed

3 files changed

+146
-29
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ Color-coded VSCode windows mean you instantly know which issue you're working on
8484

8585
No more "wait, which branch am I on?"
8686

87+
Hatchbox uses a palette of 40 distinct pastel colors to ensure visual uniqueness across your workspaces.
88+
8789
### True Parallel Work
8890
Run 3-4 dev servers simultaneously:
8991
- Issue #25 on port 3025

src/utils/color.test.ts

Lines changed: 99 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,57 @@ import {
1010

1111
describe('Color utilities', () => {
1212
describe('getColorPalette', () => {
13-
it('should return exactly 10 colors', () => {
13+
it('should return exactly 40 colors', () => {
1414
const palette = getColorPalette()
15-
expect(palette).toHaveLength(10)
15+
expect(palette).toHaveLength(40)
1616
})
1717

1818
it('should return colors matching terminal palette from bash script', () => {
1919
const palette = getColorPalette()
2020
// From bash/new-branch-workflow.sh lines 111-122
2121
const expectedColors: RgbColor[] = [
22-
{ r: 220, g: 235, b: 248 }, // Soft blue
23-
{ r: 248, g: 220, b: 235 }, // Soft pink
24-
{ r: 220, g: 248, b: 235 }, // Soft green
25-
{ r: 248, g: 240, b: 220 }, // Soft cream
26-
{ r: 240, g: 220, b: 248 }, // Soft lavender
27-
{ r: 220, g: 240, b: 248 }, // Soft cyan
28-
{ r: 235, g: 235, b: 235 }, // Soft grey
29-
{ r: 228, g: 238, b: 248 }, // Soft ice blue
30-
{ r: 248, g: 228, b: 238 }, // Soft rose
31-
{ r: 228, g: 248, b: 238 }, // Soft mint
22+
// First 10 colors preserved for backward compatibility
23+
{ r: 220, g: 235, b: 248 }, // 0: Soft blue
24+
{ r: 248, g: 220, b: 235 }, // 1: Soft pink
25+
{ r: 220, g: 248, b: 235 }, // 2: Soft green
26+
{ r: 248, g: 240, b: 220 }, // 3: Soft cream
27+
{ r: 240, g: 220, b: 248 }, // 4: Soft lavender
28+
{ r: 220, g: 240, b: 248 }, // 5: Soft cyan
29+
{ r: 235, g: 235, b: 235 }, // 6: Soft grey
30+
{ r: 228, g: 238, b: 248 }, // 7: Soft ice blue
31+
{ r: 248, g: 228, b: 238 }, // 8: Soft rose
32+
{ r: 228, g: 248, b: 238 }, // 9: Soft mint
33+
// 30 new colors (indices 10-39)
34+
{ r: 235, g: 245, b: 250 }, // 10: Pale sky blue
35+
{ r: 250, g: 235, b: 245 }, // 11: Pale orchid
36+
{ r: 235, g: 250, b: 245 }, // 12: Pale seafoam
37+
{ r: 250, g: 245, b: 235 }, // 13: Pale peach
38+
{ r: 245, g: 235, b: 250 }, // 14: Pale periwinkle
39+
{ r: 235, g: 245, b: 235 }, // 15: Pale sage
40+
{ r: 245, g: 250, b: 235 }, // 16: Pale lemon
41+
{ r: 245, g: 235, b: 235 }, // 17: Pale blush
42+
{ r: 235, g: 235, b: 250 }, // 18: Pale lavender blue
43+
{ r: 250, g: 235, b: 235 }, // 19: Pale coral
44+
{ r: 235, g: 250, b: 250 }, // 20: Pale aqua
45+
{ r: 240, g: 248, b: 255 }, // 21: Alice blue
46+
{ r: 255, g: 240, b: 248 }, // 22: Lavender blush
47+
{ r: 240, g: 255, b: 248 }, // 23: Honeydew tint
48+
{ r: 255, g: 248, b: 240 }, // 24: Antique white
49+
{ r: 248, g: 240, b: 255 }, // 25: Magnolia
50+
{ r: 240, g: 248, b: 240 }, // 26: Mint cream tint
51+
{ r: 248, g: 255, b: 240 }, // 27: Ivory tint
52+
{ r: 248, g: 240, b: 240 }, // 28: Misty rose tint
53+
{ r: 240, g: 240, b: 255 }, // 29: Ghost white tint
54+
{ r: 255, g: 245, b: 238 }, // 30: Seashell
55+
{ r: 245, g: 255, b: 250 }, // 31: Azure mist
56+
{ r: 250, g: 245, b: 255 }, // 32: Lilac mist
57+
{ r: 255, g: 250, b: 245 }, // 33: Snow peach
58+
{ r: 238, g: 245, b: 255 }, // 34: Powder blue
59+
{ r: 255, g: 238, b: 245 }, // 35: Pink lace
60+
{ r: 245, g: 255, b: 238 }, // 36: Pale lime
61+
{ r: 238, g: 255, b: 245 }, // 37: Pale turquoise
62+
{ r: 245, g: 238, b: 255 }, // 38: Pale violet
63+
{ r: 255, g: 245, b: 255 }, // 39: Pale magenta
3264
]
3365
expect(palette).toEqual(expectedColors)
3466
})
@@ -150,7 +182,7 @@ describe('Color utilities', () => {
150182
expect(() => generateColorFromBranchName('功能/my-branch')).not.toThrow()
151183
})
152184

153-
it('should always return color index in range [0, 9]', () => {
185+
it('should always return color index in range [0, 39]', () => {
154186
const testBranches = [
155187
'main',
156188
'develop',
@@ -164,7 +196,7 @@ describe('Color utilities', () => {
164196
testBranches.forEach((branch) => {
165197
const color = generateColorFromBranchName(branch)
166198
expect(color.index).toBeGreaterThanOrEqual(0)
167-
expect(color.index).toBeLessThanOrEqual(9)
199+
expect(color.index).toBeLessThanOrEqual(39)
168200
})
169201
})
170202

@@ -195,7 +227,7 @@ describe('Color utilities', () => {
195227
// We can verify our implementation produces same index
196228
const color = generateColorFromBranchName('feature/test-branch')
197229
expect(color.index).toBeGreaterThanOrEqual(0)
198-
expect(color.index).toBeLessThanOrEqual(9)
230+
expect(color.index).toBeLessThanOrEqual(39)
199231
// Color should be from palette
200232
const palette = getColorPalette()
201233
expect(color.rgb).toEqual(palette[color.index])
@@ -230,7 +262,7 @@ describe('Color utilities', () => {
230262

231263
// Index in range
232264
expect(color.index).toBeGreaterThanOrEqual(0)
233-
expect(color.index).toBeLessThanOrEqual(9)
265+
expect(color.index).toBeLessThanOrEqual(39)
234266

235267
// RGB values valid
236268
expect(color.rgb.r).toBeGreaterThanOrEqual(0)
@@ -268,4 +300,55 @@ describe('Color utilities', () => {
268300
)
269301
})
270302
})
303+
304+
describe('40-color palette expansion', () => {
305+
it('should maintain first 10 colors for backward compatibility', () => {
306+
// Verify first 10 colors unchanged to preserve existing branch colors
307+
const palette = getColorPalette()
308+
const originalColors: RgbColor[] = [
309+
{ r: 220, g: 235, b: 248 }, // Soft blue
310+
{ r: 248, g: 220, b: 235 }, // Soft pink
311+
{ r: 220, g: 248, b: 235 }, // Soft green
312+
{ r: 248, g: 240, b: 220 }, // Soft cream
313+
{ r: 240, g: 220, b: 248 }, // Soft lavender
314+
{ r: 220, g: 240, b: 248 }, // Soft cyan
315+
{ r: 235, g: 235, b: 235 }, // Soft grey
316+
{ r: 228, g: 238, b: 248 }, // Soft ice blue
317+
{ r: 248, g: 228, b: 238 }, // Soft rose
318+
{ r: 228, g: 248, b: 238 }, // Soft mint
319+
]
320+
originalColors.forEach((expected, index) => {
321+
expect(palette[index]).toEqual(expected)
322+
})
323+
})
324+
325+
it('should have all 40 colors be visually distinct', () => {
326+
// Test that no two colors are too similar (Euclidean distance threshold)
327+
const palette = getColorPalette()
328+
for (let i = 0; i < palette.length; i++) {
329+
for (let j = i + 1; j < palette.length; j++) {
330+
const distance = Math.sqrt(
331+
Math.pow(palette[i].r - palette[j].r, 2) +
332+
Math.pow(palette[i].g - palette[j].g, 2) +
333+
Math.pow(palette[i].b - palette[j].b, 2)
334+
)
335+
// Minimum distance threshold to ensure visual distinction
336+
// For subtle colors (RGB >= 220), we allow smaller distances since the color space is constrained
337+
// A distance of 3+ ensures colors are not identical or too similar
338+
expect(distance).toBeGreaterThanOrEqual(3)
339+
}
340+
}
341+
})
342+
343+
it('should maintain subtlety constraint for all 40 colors', () => {
344+
// This already exists but confirms it works for expanded palette
345+
const palette = getColorPalette()
346+
expect(palette).toHaveLength(40)
347+
palette.forEach((color) => {
348+
expect(color.r).toBeGreaterThanOrEqual(220)
349+
expect(color.g).toBeGreaterThanOrEqual(220)
350+
expect(color.b).toBeGreaterThanOrEqual(220)
351+
})
352+
})
353+
})
271354
})

src/utils/color.ts

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,55 @@ export interface ColorData {
1919
}
2020

2121
/**
22-
* Get the predefined color palette (10 subtle, professional colors)
22+
* Get the predefined color palette (40 subtle, professional colors)
2323
* Matches the terminal color palette from bash/new-branch-workflow.sh
2424
*
25-
* @returns Array of 10 RGB colors
25+
* @returns Array of 40 RGB colors
2626
*/
2727
export function getColorPalette(): RgbColor[] {
2828
return [
29-
{ r: 220, g: 235, b: 248 }, // Soft blue
30-
{ r: 248, g: 220, b: 235 }, // Soft pink
31-
{ r: 220, g: 248, b: 235 }, // Soft green
32-
{ r: 248, g: 240, b: 220 }, // Soft cream
33-
{ r: 240, g: 220, b: 248 }, // Soft lavender
34-
{ r: 220, g: 240, b: 248 }, // Soft cyan
35-
{ r: 235, g: 235, b: 235 }, // Soft grey
36-
{ r: 228, g: 238, b: 248 }, // Soft ice blue
37-
{ r: 248, g: 228, b: 238 }, // Soft rose
38-
{ r: 228, g: 248, b: 238 }, // Soft mint
29+
// First 10 colors preserved for backward compatibility
30+
{ r: 220, g: 235, b: 248 }, // 0: Soft blue
31+
{ r: 248, g: 220, b: 235 }, // 1: Soft pink
32+
{ r: 220, g: 248, b: 235 }, // 2: Soft green
33+
{ r: 248, g: 240, b: 220 }, // 3: Soft cream
34+
{ r: 240, g: 220, b: 248 }, // 4: Soft lavender
35+
{ r: 220, g: 240, b: 248 }, // 5: Soft cyan
36+
{ r: 235, g: 235, b: 235 }, // 6: Soft grey
37+
{ r: 228, g: 238, b: 248 }, // 7: Soft ice blue
38+
{ r: 248, g: 228, b: 238 }, // 8: Soft rose
39+
{ r: 228, g: 248, b: 238 }, // 9: Soft mint
40+
// 30 new colors (indices 10-39)
41+
{ r: 235, g: 245, b: 250 }, // 10: Pale sky blue
42+
{ r: 250, g: 235, b: 245 }, // 11: Pale orchid
43+
{ r: 235, g: 250, b: 245 }, // 12: Pale seafoam
44+
{ r: 250, g: 245, b: 235 }, // 13: Pale peach
45+
{ r: 245, g: 235, b: 250 }, // 14: Pale periwinkle
46+
{ r: 235, g: 245, b: 235 }, // 15: Pale sage
47+
{ r: 245, g: 250, b: 235 }, // 16: Pale lemon
48+
{ r: 245, g: 235, b: 235 }, // 17: Pale blush
49+
{ r: 235, g: 235, b: 250 }, // 18: Pale lavender blue
50+
{ r: 250, g: 235, b: 235 }, // 19: Pale coral
51+
{ r: 235, g: 250, b: 250 }, // 20: Pale aqua
52+
{ r: 240, g: 248, b: 255 }, // 21: Alice blue
53+
{ r: 255, g: 240, b: 248 }, // 22: Lavender blush
54+
{ r: 240, g: 255, b: 248 }, // 23: Honeydew tint
55+
{ r: 255, g: 248, b: 240 }, // 24: Antique white
56+
{ r: 248, g: 240, b: 255 }, // 25: Magnolia
57+
{ r: 240, g: 248, b: 240 }, // 26: Mint cream tint
58+
{ r: 248, g: 255, b: 240 }, // 27: Ivory tint
59+
{ r: 248, g: 240, b: 240 }, // 28: Misty rose tint
60+
{ r: 240, g: 240, b: 255 }, // 29: Ghost white tint
61+
{ r: 255, g: 245, b: 238 }, // 30: Seashell
62+
{ r: 245, g: 255, b: 250 }, // 31: Azure mist
63+
{ r: 250, g: 245, b: 255 }, // 32: Lilac mist
64+
{ r: 255, g: 250, b: 245 }, // 33: Snow peach
65+
{ r: 238, g: 245, b: 255 }, // 34: Powder blue
66+
{ r: 255, g: 238, b: 245 }, // 35: Pink lace
67+
{ r: 245, g: 255, b: 238 }, // 36: Pale lime
68+
{ r: 238, g: 255, b: 245 }, // 37: Pale turquoise
69+
{ r: 245, g: 238, b: 255 }, // 38: Pale violet
70+
{ r: 255, g: 245, b: 255 }, // 39: Pale magenta
3971
]
4072
}
4173

@@ -97,7 +129,7 @@ export function generateColorFromBranchName(branchName: string): ColorData {
97129
// Generate SHA256 hash of branch name
98130
const hash = createHash('sha256').update(branchName).digest('hex')
99131

100-
// Take first 8 hex characters and convert to index (0-9)
132+
// Take first 8 hex characters and convert to index (0-39)
101133
// Matches bash: local index=$(( 0x$hash % ${#colors[@]} ))
102134
const hashPrefix = hash.slice(0, 8)
103135
const palette = getColorPalette()

0 commit comments

Comments
 (0)