Skip to content

Commit 48de10b

Browse files
feat(webui): Tailwind custom colors + shared nav, /work dark theme (#1627) (#1631)
* feat(webui): add Tailwind custom colors + shared nav partial, migrate /work to dark theme Extend tailwind.config.js with custom color tokens (surface, edge, txt, wave, state) that map to the production CSS custom properties from style.css. This lets new consolidated pages use Tailwind utilities while staying in the production dark theme. Add shared nav partial (templates/partials/nav_consolidated.html) using Tailwind utilities with production CSS variables. Wire partials into standalone page parsing via parsePartialsInto() so standalone pages can use {{template}} composition. Migrate /work board and /work-item detail from standalone light-theme Tailwind (bg-slate-50, text-slate-900) to the production dark theme (bg-surface, text-txt) with the shared nav. Both pages now load style.css for CSS variables + tailwind.css for utility classes. Closes #1627 * fix(webui): update Tailwind CSS presence test for custom color tokens bg-slate-50 was pruned from compiled CSS after /work pages migrated to custom dark-theme tokens (bg-surface). Update the content-scan guard to check for bg-surface instead.
1 parent 6b82c89 commit 48de10b

7 files changed

Lines changed: 184 additions & 93 deletions

File tree

internal/webui/embed.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,8 @@ func parseTemplates(extraFuncs ...template.FuncMap) (map[string]*template.Templa
283283

284284
// Standalone pages are NOT cloned from the layout-bearing base — they
285285
// render their own <html> shell so Tailwind utility classes don't
286-
// collide with the project stylesheet.
286+
// collide with the project stylesheet. They still get access to shared
287+
// partials (templates/partials/*) for template composition.
287288
for _, page := range standalonePageTemplates {
288289
data, readErr := templatesFS.ReadFile(page)
289290
if readErr != nil {
@@ -293,12 +294,36 @@ func parseTemplates(extraFuncs ...template.FuncMap) (map[string]*template.Templa
293294
if parseErr != nil {
294295
return nil, fmt.Errorf("parsing %s: %w", page, parseErr)
295296
}
297+
// Parse partials into each standalone template so {{template "partials/..."}} works.
298+
if err := parsePartialsInto(t); err != nil {
299+
return nil, fmt.Errorf("parsing partials for standalone %s: %w", page, err)
300+
}
296301
pages[page] = t
297302
}
298303

299304
return pages, nil
300305
}
301306

307+
// parsePartialsInto walks templates/partials/ and parses every file into the
308+
// given template. This lets standalone pages (which don't share the layout
309+
// base) still use {{template "partials/..."}} blocks.
310+
func parsePartialsInto(t *template.Template) error {
311+
return fs.WalkDir(templatesFS, "templates/partials", func(path string, d fs.DirEntry, walkErr error) error {
312+
if walkErr != nil {
313+
return walkErr
314+
}
315+
if d.IsDir() {
316+
return nil
317+
}
318+
data, err := templatesFS.ReadFile(path)
319+
if err != nil {
320+
return err
321+
}
322+
_, err = t.New(path).Parse(string(data))
323+
return err
324+
})
325+
}
326+
302327
// staticHandler returns an http.Handler that serves embedded static files.
303328
func staticHandler() http.Handler {
304329
sub, _ := fs.Sub(staticFS, "static")

internal/webui/static/tailwind.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/webui/tailwind.config.js

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,50 @@ module.exports = {
44
"./templates/**/*.html",
55
],
66
theme: {
7-
extend: {},
7+
extend: {
8+
colors: {
9+
wave: {
10+
primary: 'var(--wave-primary)',
11+
'primary-dark': 'var(--wave-primary-dark)',
12+
accent: 'var(--wave-accent)',
13+
secondary: 'var(--wave-secondary)',
14+
green: 'var(--wave-trust-green)',
15+
blue: 'var(--wave-trust-blue)',
16+
warning: 'var(--wave-warning)',
17+
danger: 'var(--wave-danger)',
18+
},
19+
surface: {
20+
DEFAULT: 'var(--color-bg)',
21+
secondary: 'var(--color-bg-secondary)',
22+
tertiary: 'var(--color-bg-tertiary)',
23+
},
24+
edge: {
25+
DEFAULT: 'var(--color-border)',
26+
light: 'var(--color-border-light)',
27+
},
28+
txt: {
29+
DEFAULT: 'var(--color-text)',
30+
secondary: 'var(--color-text-secondary)',
31+
muted: 'var(--color-text-muted)',
32+
},
33+
state: {
34+
completed: 'var(--color-completed)',
35+
'completed-bg': 'var(--color-completed-bg)',
36+
running: 'var(--color-running)',
37+
'running-bg': 'var(--color-running-bg)',
38+
failed: 'var(--color-failed)',
39+
'failed-bg': 'var(--color-failed-bg)',
40+
cancelled: 'var(--color-cancelled)',
41+
'cancelled-bg': 'var(--color-cancelled-bg)',
42+
pending: 'var(--color-pending)',
43+
'pending-bg': 'var(--color-pending-bg)',
44+
},
45+
},
46+
fontFamily: {
47+
sans: ['var(--font-sans)'],
48+
mono: ['var(--font-mono)'],
49+
},
50+
},
851
},
952
plugins: [],
1053
};

internal/webui/tailwind_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ func TestEmbeddedTailwindCSSPresent(t *testing.T) {
2626
t.Error("static/tailwind.css missing Tailwind `--tw-` custom properties — file may not be a real Tailwind build")
2727
}
2828

29-
// `bg-slate-50` is referenced by templates/work/board.html and
30-
// templates/work/detail.html, so a successful content scan must emit it.
31-
if !strings.Contains(css, "bg-slate-50") {
32-
t.Error("static/tailwind.css missing `bg-slate-50` utility — content scan likely broken")
29+
// `bg-surface` is a custom color token defined in tailwind.config.js and
30+
// referenced by consolidated templates. Its presence confirms the config
31+
// customizations are picked up by the content scan.
32+
if !strings.Contains(css, "bg-surface") {
33+
t.Error("static/tailwind.css missing `bg-surface` custom color utility — content scan likely broken")
3334
}
3435
}
3536

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{{define "partials/nav"}}
2+
<nav class="bg-surface-secondary border-b border-edge sticky top-0 z-10">
3+
<div class="max-w-7xl mx-auto px-6 py-3 flex items-center gap-6">
4+
<a href="/" class="flex items-center gap-2 font-semibold text-txt hover:opacity-80 no-underline">
5+
<svg class="w-5 h-5" viewBox="0 0 28 28" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" aria-hidden="true">
6+
<path d="M2 14 C6 6, 10 6, 14 14 C18 22, 22 22, 26 14"/>
7+
<path d="M2 14 C6 22, 10 22, 14 14 C18 6, 22 6, 26 14" opacity="0.35"/>
8+
</svg>
9+
Wave
10+
</a>
11+
<div class="flex items-center gap-1 text-sm">
12+
<a href="/work"
13+
class="px-3 py-1.5 rounded-md transition-colors no-underline
14+
{{if eq .ActivePage "work"}}bg-surface-tertiary text-txt font-medium{{else}}text-txt-secondary hover:text-txt hover:bg-surface-tertiary{{end}}">
15+
Work
16+
</a>
17+
<a href="/runs"
18+
class="px-3 py-1.5 rounded-md transition-colors no-underline
19+
{{if eq .ActivePage "runs"}}bg-surface-tertiary text-txt font-medium{{else}}text-txt-secondary hover:text-txt hover:bg-surface-tertiary{{end}}">
20+
Runs
21+
</a>
22+
<a href="/pipelines"
23+
class="px-3 py-1.5 rounded-md transition-colors no-underline
24+
{{if eq .ActivePage "pipelines"}}bg-surface-tertiary text-txt font-medium{{else}}text-txt-secondary hover:text-txt hover:bg-surface-tertiary{{end}}">
25+
Pipelines
26+
</a>
27+
<a href="/proposals"
28+
class="px-3 py-1.5 rounded-md transition-colors no-underline
29+
{{if eq .ActivePage "proposals"}}bg-surface-tertiary text-txt font-medium{{else}}text-txt-secondary hover:text-txt hover:bg-surface-tertiary{{end}}">
30+
Proposals
31+
</a>
32+
<a href="/issues"
33+
class="px-3 py-1.5 rounded-md transition-colors no-underline
34+
{{if eq .ActivePage "issues"}}bg-surface-tertiary text-txt font-medium{{else}}text-txt-secondary hover:text-txt hover:bg-surface-tertiary{{end}}">
35+
Issues
36+
</a>
37+
<a href="/prs"
38+
class="px-3 py-1.5 rounded-md transition-colors no-underline
39+
{{if eq .ActivePage "prs"}}bg-surface-tertiary text-txt font-medium{{else}}text-txt-secondary hover:text-txt hover:bg-surface-tertiary{{end}}">
40+
PRs
41+
</a>
42+
<a href="/onboard"
43+
class="px-3 py-1.5 rounded-md transition-colors no-underline
44+
{{if eq .ActivePage "onboard"}}bg-surface-tertiary text-txt font-medium{{else}}text-txt-secondary hover:text-txt hover:bg-surface-tertiary{{end}}">
45+
Onboard
46+
</a>
47+
<a href="/health"
48+
class="px-3 py-1.5 rounded-md transition-colors no-underline text-txt-muted hover:text-txt hover:bg-surface-tertiary">
49+
Health
50+
</a>
51+
</div>
52+
</div>
53+
</nav>
54+
{{end}}

internal/webui/templates/work/board.html

Lines changed: 26 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,46 +4,30 @@
44
<meta charset="utf-8" />
55
<meta name="viewport" content="width=device-width,initial-scale=1" />
66
<title>Work board &mdash; Wave</title>
7+
<link rel="stylesheet" href="/static/style.css">
78
<link rel="stylesheet" href="/static/tailwind.css" />
89
</head>
9-
<body class="bg-slate-50 text-slate-900 antialiased">
10-
<nav class="bg-white border-b border-slate-200">
11-
<div class="max-w-6xl mx-auto px-6 py-3 flex items-center gap-6">
12-
<a href="/work" class="flex items-center gap-2 font-semibold">
13-
<svg width="22" height="22" viewBox="0 0 28 28" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" aria-hidden="true">
14-
<path d="M2 14 C6 6, 10 6, 14 14 C18 22, 22 22, 26 14"/>
15-
<path d="M2 14 C6 22, 10 22, 14 14 C18 6, 22 6, 26 14" opacity="0.35"/>
16-
</svg>
17-
Wave
18-
</a>
19-
<div class="flex items-center gap-4 text-sm">
20-
<a href="/work" class="text-slate-900 font-medium border-b-2 border-slate-900 pb-3 -mb-3">Work</a>
21-
<a href="/runs" class="text-slate-600 hover:text-slate-900">Runs</a>
22-
<a href="/pipelines" class="text-slate-600 hover:text-slate-900">Pipelines</a>
23-
<a href="/issues" class="text-slate-600 hover:text-slate-900">Issues</a>
24-
<a href="/prs" class="text-slate-600 hover:text-slate-900">PRs</a>
25-
</div>
26-
</div>
27-
</nav>
10+
<body class="bg-surface text-txt antialiased">
11+
{{template "partials/nav" .}}
2812

2913
<main class="max-w-6xl mx-auto px-6 py-8">
3014
<header class="mb-8">
3115
<h1 class="text-2xl font-semibold tracking-tight">Work</h1>
32-
<p class="text-sm text-slate-600 mt-1">
16+
<p class="text-sm text-txt-secondary mt-1">
3317
Worksource bindings and recent matches across connected forges.
3418
</p>
3519
</header>
3620

3721
<section class="mb-10">
3822
<div class="flex items-baseline justify-between mb-3">
3923
<h2 class="text-lg font-semibold">Bindings</h2>
40-
<span class="text-xs text-slate-500">{{len .Bindings}} configured</span>
24+
<span class="text-xs text-txt-muted">{{len .Bindings}} configured</span>
4125
</div>
4226

4327
{{if .HasBindings}}
44-
<div class="bg-white border border-slate-200 rounded-lg overflow-hidden">
28+
<div class="bg-surface-secondary border border-edge rounded-lg overflow-hidden">
4529
<table class="w-full text-sm">
46-
<thead class="bg-slate-50 border-b border-slate-200 text-left text-xs uppercase tracking-wide text-slate-500">
30+
<thead class="bg-surface-tertiary border-b border-edge text-left text-xs uppercase tracking-wide text-txt-muted">
4731
<tr>
4832
<th class="px-4 py-2 font-medium">Status</th>
4933
<th class="px-4 py-2 font-medium">Forge</th>
@@ -55,26 +39,26 @@ <h2 class="text-lg font-semibold">Bindings</h2>
5539
</thead>
5640
<tbody>
5741
{{range .Bindings}}
58-
<tr class="border-b border-slate-100 last:border-0 hover:bg-slate-50">
42+
<tr class="border-b border-edge last:border-0 hover:bg-surface-tertiary">
5943
<td class="px-4 py-3">
6044
{{if .Active}}
61-
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-emerald-50 text-emerald-700 text-xs font-medium">
45+
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-state-completed-bg text-state-completed text-xs font-medium">
6246
<svg class="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg>
6347
active
6448
</span>
6549
{{else}}
66-
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-slate-100 text-slate-600 text-xs font-medium">inactive</span>
50+
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-surface-tertiary text-txt-secondary text-xs font-medium">inactive</span>
6751
{{end}}
6852
</td>
6953
<td class="px-4 py-3 font-mono text-xs">{{.Forge}}</td>
7054
<td class="px-4 py-3 font-mono text-xs">{{.RepoPattern}}</td>
7155
<td class="px-4 py-3 font-medium">{{.PipelineName}}</td>
72-
<td class="px-4 py-3 text-xs text-slate-600">{{.TriggerLabel}}</td>
73-
<td class="px-4 py-3 text-xs text-slate-600">
56+
<td class="px-4 py-3 text-xs text-txt-secondary">{{.TriggerLabel}}</td>
57+
<td class="px-4 py-3 text-xs text-txt-secondary">
7458
{{if .LabelFilter}}
75-
{{range .LabelFilter}}<span class="inline-block px-1.5 py-0.5 mr-1 rounded bg-slate-100 text-slate-700 font-mono">{{.}}</span>{{end}}
59+
{{range .LabelFilter}}<span class="inline-block px-1.5 py-0.5 mr-1 rounded bg-surface-tertiary text-txt font-mono">{{.}}</span>{{end}}
7660
{{else}}
77-
<span class="text-slate-400">&mdash;</span>
61+
<span class="text-txt-muted">&mdash;</span>
7862
{{end}}
7963
</td>
8064
</tr>
@@ -83,18 +67,18 @@ <h2 class="text-lg font-semibold">Bindings</h2>
8367
</table>
8468
</div>
8569
{{else}}
86-
<div class="bg-white border border-dashed border-slate-300 rounded-lg p-10 text-center">
87-
<svg class="w-10 h-10 text-slate-300 mx-auto mb-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
70+
<div class="bg-surface-secondary border border-dashed border-edge rounded-lg p-10 text-center">
71+
<svg class="w-10 h-10 text-txt-muted mx-auto mb-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
8872
<rect x="3" y="3" width="18" height="18" rx="2"/>
8973
<line x1="3" y1="9" x2="21" y2="9"/>
9074
<line x1="9" y1="21" x2="9" y2="9"/>
9175
</svg>
92-
<h3 class="text-base font-medium text-slate-900">No bindings yet</h3>
93-
<p class="text-sm text-slate-600 mt-1 max-w-md mx-auto">
76+
<h3 class="text-base font-medium">No bindings yet</h3>
77+
<p class="text-sm text-txt-secondary mt-1 max-w-md mx-auto">
9478
Bindings connect a forge repo to a Wave pipeline so incoming issues
9579
and PRs trigger the right work. Configure one to get started.
9680
</p>
97-
<p class="text-xs text-slate-500 mt-3">
81+
<p class="text-xs text-txt-muted mt-3">
9882
A bindings UI ships in a future iteration; create them via the API
9983
or <code class="font-mono">wave</code> CLI for now.
10084
</p>
@@ -105,28 +89,28 @@ <h3 class="text-base font-medium text-slate-900">No bindings yet</h3>
10589
<section>
10690
<div class="flex items-baseline justify-between mb-3">
10791
<h2 class="text-lg font-semibold">Recent matches</h2>
108-
<span class="text-xs text-slate-500">runs of pipelines that match a binding</span>
92+
<span class="text-xs text-txt-muted">runs of pipelines that match a binding</span>
10993
</div>
11094

11195
{{if .RecentRuns}}
112-
<ul class="bg-white border border-slate-200 rounded-lg divide-y divide-slate-100">
96+
<ul class="bg-surface-secondary border border-edge rounded-lg divide-y divide-edge">
11397
{{range .RecentRuns}}
114-
<li class="px-4 py-3 hover:bg-slate-50">
115-
<a href="/runs/{{.RunID}}" class="flex items-center justify-between gap-4">
98+
<li class="px-4 py-3 hover:bg-surface-tertiary">
99+
<a href="/runs/{{.RunID}}" class="flex items-center justify-between gap-4 text-txt no-underline hover:underline">
116100
<div class="min-w-0">
117101
<div class="font-medium truncate">{{.PipelineName}}</div>
118-
<div class="text-xs text-slate-500 mt-0.5">
102+
<div class="text-xs text-txt-muted mt-0.5">
119103
<span class="font-mono">{{.RunID}}</span>
120104
{{if .Input}}&middot; <span class="truncate">{{.Input}}</span>{{end}}
121105
</div>
122106
</div>
123-
<span class="text-xs text-slate-500 whitespace-nowrap">{{.Status}}</span>
107+
<span class="text-xs text-txt-muted whitespace-nowrap">{{.Status}}</span>
124108
</a>
125109
</li>
126110
{{end}}
127111
</ul>
128112
{{else}}
129-
<div class="bg-white border border-slate-200 rounded-lg p-6 text-center text-sm text-slate-500">
113+
<div class="bg-surface-secondary border border-edge rounded-lg p-6 text-center text-sm text-txt-muted">
130114
No recent runs match the configured bindings.
131115
</div>
132116
{{end}}

0 commit comments

Comments
 (0)