diff --git a/cmd/server/routes.go b/cmd/server/routes.go index 6570ccf..7fd8133 100644 --- a/cmd/server/routes.go +++ b/cmd/server/routes.go @@ -293,7 +293,12 @@ func (s *Server) handleConfigTheme(w http.ResponseWriter, r *http.Request) { }, s.cfg.NodeColors, theme.NodeColors) themeDark := mergeMap(map[string]interface{}{}, s.cfg.ThemeDark, theme.ThemeDark) - typeColors := mergeMap(map[string]interface{}{}, s.cfg.TypeColors, theme.TypeColors) + typeColors := mergeMap(map[string]interface{}{ + "ADVERT": "#22c55e", "GRP_TXT": "#3b82f6", "TXT_MSG": "#f59e0b", + "ACK": "#6b7280", "REQUEST": "#a855f7", "RESPONSE": "#06b6d4", + "TRACE": "#ec4899", "PATH": "#14b8a6", "ANON_REQ": "#f43f5e", + "UNKNOWN": "#6b7280", + }, s.cfg.TypeColors, theme.TypeColors) var home interface{} if theme.Home != nil { diff --git a/cmd/server/routes_test.go b/cmd/server/routes_test.go index a06201c..c6fa338 100644 --- a/cmd/server/routes_test.go +++ b/cmd/server/routes_test.go @@ -454,6 +454,35 @@ func TestConfigThemeEndpoint(t *testing.T) { } } +func TestConfigThemeTypeColorsDefaults(t *testing.T) { + _, router := setupTestServer(t) + req := httptest.NewRequest("GET", "/api/config/theme", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + if w.Code != 200 { + t.Fatalf("expected 200, got %d", w.Code) + } + var body map[string]interface{} + json.Unmarshal(w.Body.Bytes(), &body) + tc, ok := body["typeColors"].(map[string]interface{}) + if !ok { + t.Fatal("expected typeColors object in theme response") + } + expectedTypes := []string{"ADVERT", "GRP_TXT", "TXT_MSG", "ACK", "REQUEST", "RESPONSE", "TRACE", "PATH", "ANON_REQ", "UNKNOWN"} + for _, typ := range expectedTypes { + val, exists := tc[typ] + if !exists { + t.Errorf("typeColors missing default for %s", typ) + continue + } + color, ok := val.(string) + if !ok || color == "" || color == "#000000" { + t.Errorf("typeColors[%s] should be a non-black color, got %v", typ, val) + } + } +} + func TestConfigMapEndpoint(t *testing.T) { _, router := setupTestServer(t) req := httptest.NewRequest("GET", "/api/config/map", nil) diff --git a/public/customize-v2.js b/public/customize-v2.js index 577f6be..0c9a795 100644 --- a/public/customize-v2.js +++ b/public/customize-v2.js @@ -966,7 +966,7 @@ var stc = server.typeColors || {}; var typeRows = ''; for (var tkey in TYPE_LABELS) { - var tval = tc[tkey] || '#000000'; + var tval = tc[tkey] || (window.TYPE_COLORS && window.TYPE_COLORS[tkey]) || '#000000'; typeRows += '
' + '
' + '
' + (TYPE_HINTS[tkey] || '') + '
' + diff --git a/test-frontend-helpers.js b/test-frontend-helpers.js index 9508b4f..6c784cc 100644 --- a/test-frontend-helpers.js +++ b/test-frontend-helpers.js @@ -2020,6 +2020,24 @@ console.log('\n=== customize-v2.js: core behavior ==='); assert.ok(src.includes("surface3: '--surface-3'"), 'surface3 must map to --surface-3'); assert.ok(src.includes("sectionBg: '--section-bg'"), 'sectionBg must map to --section-bg'); }); + + test('_renderNodes falls back to window.TYPE_COLORS when typeColors is empty (#514)', () => { + const ctx = makeSandbox(); + ctx.CustomEvent = function (type) { this.type = type; }; + ctx.TYPE_COLORS = { ADVERT: '#22c55e', GRP_TXT: '#3b82f6' }; + ctx.window.TYPE_COLORS = ctx.TYPE_COLORS; + const v2 = loadCustomizeV2(ctx); + // computeEffective with empty typeColors should still allow fallback + const server = { typeColors: {} }; + const effective = v2.computeEffective(server, {}); + // When typeColors is empty, the render should fall back to TYPE_COLORS + // We test the logic directly: tc[key] || TYPE_COLORS[key] || '#000000' + const tc = effective.typeColors || {}; + const advertColor = tc['ADVERT'] || (ctx.window.TYPE_COLORS && ctx.window.TYPE_COLORS['ADVERT']) || '#000000'; + assert.strictEqual(advertColor, '#22c55e', 'ADVERT should fall back to TYPE_COLORS, not #000000'); + const grpColor = tc['GRP_TXT'] || (ctx.window.TYPE_COLORS && ctx.window.TYPE_COLORS['GRP_TXT']) || '#000000'; + assert.strictEqual(grpColor, '#3b82f6', 'GRP_TXT should fall back to TYPE_COLORS, not #000000'); + }); } // ===== APP.JS: home rehydration merge (mergeUserHomeConfig removed — dead code) =====