Skip to content

Commit 0e47efa

Browse files
committed
feat(webui): rebrand app to Ngent and agent terminology
Update the embedded web UI branding from "Agent Hub" to "Ngent" and replace user-facing "thread" wording with "agent" across key labels, empty states, dialogs, alerts, and accessibility text. Add a new SVG favicon at /favicon.svg and wire it into index.html. Update web UI handler tests to assert the new branding and verify favicon asset serving. Include ADR index entry for ADR-035 in decisions docs.
1 parent 7fa81a4 commit 0e47efa

File tree

7 files changed

+69
-31
lines changed

7 files changed

+69
-31
lines changed

docs/DECISIONS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
- ADR-032: Shared common agent config/state helper without protocol unification. (Accepted)
3737
- ADR-033: Surface ACP plan updates as first-class SSE and Web UI state. (Accepted)
3838
- ADR-034: Source Kimi config catalogs from local config to avoid empty sessions. (Accepted)
39+
- ADR-035: Add opt-in ACP debug tracing behind `--debug`. (Accepted)
3940

4041
## ADR-018: Embedded Web UI via Go embed
4142

internal/webui/web/index.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<title>Agent Hub</title>
6+
<title>Ngent</title>
7+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
78
<link rel="stylesheet" href="/src/style.css" />
89
</head>
910
<body>
Lines changed: 17 additions & 0 deletions
Loading

internal/webui/web/src/components/new-thread-modal.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ function renderModal(s: ModalState, agents: AgentInfo[]): string {
7272
const canSubmit = !!s.selectedAgent && isAbsolutePath(s.cwd) && !s.submitting
7373

7474
return `
75-
<div class="modal-overlay" id="new-thread-overlay" role="dialog" aria-modal="true" aria-label="New thread">
75+
<div class="modal-overlay" id="new-thread-overlay" role="dialog" aria-modal="true" aria-label="New agent">
7676
<div class="modal" id="new-thread-modal">
7777
7878
<div class="modal-header">
79-
<h2 class="modal-title">New Thread</h2>
79+
<h2 class="modal-title">New Agent</h2>
8080
<button class="btn btn-icon" id="new-thread-close" aria-label="Close">${iconClose}</button>
8181
</div>
8282
@@ -154,7 +154,7 @@ function renderModal(s: ModalState, agents: AgentInfo[]): string {
154154
type="button"
155155
${canSubmit ? '' : 'disabled'}
156156
>
157-
${s.submitting ? '<span class="btn-spinner"></span> Creating…' : 'Create Thread'}
157+
${s.submitting ? '<span class="btn-spinner"></span> Creating…' : 'Create Agent'}
158158
</button>
159159
</div>
160160

internal/webui/web/src/components/settings-panel.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ function renderPanel(): string {
4242
<h3 class="settings-section-title">Identity</h3>
4343
<label class="settings-label">Client ID</label>
4444
<p class="settings-description">
45-
Automatically assigned. All threads and turns are scoped to this ID.
45+
Automatically assigned. All agents and turns are scoped to this ID.
4646
</p>
4747
<div class="settings-id-row">
4848
<code class="settings-client-id" id="client-id-display">${escHtml(clientId)}</code>
@@ -86,7 +86,7 @@ function renderPanel(): string {
8686
<h3 class="settings-section-title">Connection</h3>
8787
<label class="settings-label" for="server-url-input">Server URL</label>
8888
<p class="settings-description">
89-
Base URL of the Agent Hub Server API. Change if using a reverse proxy.
89+
Base URL of the Ngent Server API. Change if using a reverse proxy.
9090
</p>
9191
<input
9292
id="server-url-input"
@@ -163,7 +163,7 @@ function bindEvents(): void {
163163

164164
// Reset client ID (with confirmation)
165165
container.querySelector('#reset-client-id-btn')?.addEventListener('click', () => {
166-
if (!confirm('Reset your Client ID? You will lose access to existing threads in this browser.')) return
166+
if (!confirm('Reset your Client ID? You will lose access to existing agents in this browser.')) return
167167
store.resetClientId()
168168
const display = container?.querySelector<HTMLElement>('#client-id-display')
169169
if (display) display.textContent = store.get().clientId

internal/webui/web/src/main.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -368,9 +368,9 @@ function renderThreadActionPopover(t: Thread): string {
368368
data-thread-id="${escHtml(t.threadId)}"
369369
type="text"
370370
value="${escHtml(renamingThreadDraft)}"
371-
placeholder="Thread name"
371+
placeholder="Agent name"
372372
maxlength="120"
373-
aria-label="Rename thread"
373+
aria-label="Rename agent"
374374
/>
375375
<div class="thread-rename-actions">
376376
<button class="btn btn-primary btn-sm" type="submit">Save</button>
@@ -383,7 +383,7 @@ function renderThreadActionPopover(t: Thread): string {
383383
}
384384

385385
return `
386-
<div class="thread-action-popover thread-action-menu" data-thread-id="${escHtml(t.threadId)}" role="menu" aria-label="Thread actions">
386+
<div class="thread-action-popover thread-action-menu" data-thread-id="${escHtml(t.threadId)}" role="menu" aria-label="Agent actions">
387387
<button class="thread-action-menu-item" type="button" data-thread-id="${escHtml(t.threadId)}" data-action="rename" role="menuitem">
388388
Rename
389389
</button>
@@ -940,8 +940,8 @@ function renderThreadStatusIndicator(status: ThreadActivityIndicator): string {
940940
<span
941941
class="thread-status-indicator thread-status-indicator--loading"
942942
role="status"
943-
aria-label="Thread is working"
944-
title="Thread is working"
943+
aria-label="Agent is working"
944+
title="Agent is working"
945945
>
946946
<span class="thread-status-spinner" aria-hidden="true"></span>
947947
</span>`
@@ -999,7 +999,7 @@ function renderThreadItem(
999999
<button class="btn btn-ghost btn-sm thread-item-menu-trigger" type="button"
10001000
data-thread-id="${escHtml(t.threadId)}"
10011001
aria-expanded="${isMenuOpen ? 'true' : 'false'}"
1002-
aria-label="Thread actions">
1002+
aria-label="Agent actions">
10031003
...
10041004
</button>
10051005
</div>
@@ -1021,7 +1021,7 @@ function updateThreadList(): void {
10211021
if (!filtered.length) {
10221022
el.innerHTML = `
10231023
<div class="thread-list-empty">
1024-
${q ? `No threads matching "<strong>${escHtml(q)}</strong>"` : 'No threads yet.<br>Click <strong>+</strong> to start one.'}
1024+
${q ? `No agents matching "<strong>${escHtml(q)}</strong>"` : 'No agents yet.<br>Click <strong>+</strong> to start one.'}
10251025
</div>`
10261026
renderThreadActionLayer()
10271027
return
@@ -1082,7 +1082,7 @@ async function handleRenameThread(threadId: string, nextTitle: string): Promise<
10821082
updatedThread = await api.updateThread(threadId, { title })
10831083
} catch (err) {
10841084
const message = err instanceof Error ? err.message : 'Unknown error'
1085-
window.alert(`Failed to rename thread: ${message}`)
1085+
window.alert(`Failed to rename agent: ${message}`)
10861086
return
10871087
}
10881088

@@ -1102,13 +1102,13 @@ async function handleDeleteThread(threadId: string): Promise<void> {
11021102
if (!thread) return
11031103

11041104
const label = threadTitle(thread)
1105-
if (!window.confirm(`Delete thread "${label}"? This will permanently remove its history.`)) return
1105+
if (!window.confirm(`Delete agent "${label}"? This will permanently remove its history.`)) return
11061106

11071107
try {
11081108
await api.deleteThread(threadId)
11091109
} catch (err) {
11101110
const message = err instanceof Error ? err.message : 'Unknown error'
1111-
window.alert(`Failed to delete thread: ${message}`)
1111+
window.alert(`Failed to delete agent: ${message}`)
11121112
return
11131113
}
11141114

@@ -1412,12 +1412,12 @@ function renderChatEmpty(): string {
14121412
return `
14131413
<div class="empty-state">
14141414
<div class="empty-state-icon">◈</div>
1415-
<h3 class="empty-state-title">No thread selected</h3>
1415+
<h3 class="empty-state-title">No agent selected</h3>
14161416
<p class="empty-state-desc">
1417-
Select a thread from the sidebar, or create a new one to start chatting with an agent.
1417+
Select an agent from the sidebar, or create a new one to start chatting.
14181418
</p>
14191419
<button class="btn btn-primary" id="new-thread-empty-btn">
1420-
${iconPlus} New Thread
1420+
${iconPlus} New Agent
14211421
</button>
14221422
</div>`
14231423
}
@@ -1685,7 +1685,7 @@ function bindThreadConfigSwitches(thread: Thread): void {
16851685
if (store.get().activeThreadId !== thread.threadId) return
16861686
renderConfigUI()
16871687
const message = err instanceof Error ? err.message : String(err)
1688-
window.alert(`Failed to load thread config options: ${message}`)
1688+
window.alert(`Failed to load agent config options: ${message}`)
16891689
})
16901690
}
16911691

@@ -1975,10 +1975,10 @@ function renderShell(): void {
19751975
<aside class="sidebar" id="sidebar">
19761976
<div class="sidebar-header">
19771977
<div class="sidebar-brand">
1978-
<div class="sidebar-brand-icon">A</div>
1979-
<span>Agent Hub</span>
1978+
<div class="sidebar-brand-icon">N</div>
1979+
<span>Ngent</span>
19801980
</div>
1981-
<button class="btn btn-icon" id="new-thread-btn" title="New thread" aria-label="New thread">
1981+
<button class="btn btn-icon" id="new-thread-btn" title="New agent" aria-label="New agent">
19821982
${iconPlus}
19831983
</button>
19841984
</div>
@@ -1988,8 +1988,8 @@ function renderShell(): void {
19881988
id="search-input"
19891989
class="search-input"
19901990
type="search"
1991-
placeholder="Search threads…"
1992-
aria-label="Search threads"
1991+
placeholder="Search agents…"
1992+
aria-label="Search agents"
19931993
/>
19941994
</div>
19951995
@@ -2125,7 +2125,7 @@ async function init(): Promise<void> {
21252125
const el = document.getElementById('thread-list')
21262126
if (el) {
21272127
el.innerHTML = `<div class="thread-list-empty" style="color:var(--error)">
2128-
Failed to load threads.<br>Check the server connection in Settings.
2128+
Failed to load agents.<br>Check the server connection in Settings.
21292129
</div>`
21302130
}
21312131
}

internal/webui/webui_test.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ func TestHandlerServesIndexHTML(t *testing.T) {
2323
if !strings.Contains(ct, "text/html") {
2424
t.Fatalf("GET / expected text/html content-type, got %q", ct)
2525
}
26-
if !strings.Contains(w.Body.String(), "Agent Hub") {
27-
t.Fatalf("GET / expected body to contain 'Agent Hub', got: %s", w.Body.String())
26+
if !strings.Contains(w.Body.String(), "Ngent") {
27+
t.Fatalf("GET / expected body to contain 'Ngent', got: %s", w.Body.String())
2828
}
2929
}
3030

@@ -43,6 +43,25 @@ func TestHandlerServesAssets(t *testing.T) {
4343
}
4444
}
4545

46+
func TestHandlerServesFavicon(t *testing.T) {
47+
h := webui.Handler()
48+
49+
req := httptest.NewRequest(http.MethodGet, "/favicon.svg", nil)
50+
w := httptest.NewRecorder()
51+
h.ServeHTTP(w, req)
52+
53+
if w.Code != http.StatusOK {
54+
t.Fatalf("GET /favicon.svg expected 200, got %d (body: %s)", w.Code, w.Body.String())
55+
}
56+
ct := w.Header().Get("Content-Type")
57+
if !strings.Contains(ct, "image/svg+xml") {
58+
t.Fatalf("GET /favicon.svg expected image/svg+xml content-type, got %q", ct)
59+
}
60+
if !strings.Contains(w.Body.String(), "<svg") {
61+
t.Fatalf("GET /favicon.svg expected SVG body, got: %s", w.Body.String())
62+
}
63+
}
64+
4665
func TestHandlerSPAFallback(t *testing.T) {
4766
h := webui.Handler()
4867

@@ -60,8 +79,8 @@ func TestHandlerSPAFallback(t *testing.T) {
6079
if !strings.Contains(ct, "text/html") {
6180
t.Errorf("SPA fallback %s expected text/html, got %q", p, ct)
6281
}
63-
if !strings.Contains(w.Body.String(), "Agent Hub") {
64-
t.Errorf("SPA fallback %s expected body to contain 'Agent Hub'", p)
82+
if !strings.Contains(w.Body.String(), "Ngent") {
83+
t.Errorf("SPA fallback %s expected body to contain 'Ngent'", p)
6584
}
6685
}
6786
}

0 commit comments

Comments
 (0)