Skip to content

Commit ad30a1c

Browse files
authored
style(web): polish web UI typography and controls (#1502)
* style(web): polish sidebar and tool typography - Use UI font and medium weight for sidebar and composer controls - Add reusable shortcut and tool output blocks - Cap long tool output at 50 lines with a scrollbar - Update sidebar show-more copy and muted styling * style(web): refine workspace picker sizing * style(web): align composer mode menus * style(web): tune list and question typography * style(web): reuse shortcut keys in approvals * style(web): size workspace picker from content * feat(web): localize chat status labels * style(web): refine composer toolbar controls * style(web): use complete Inter variable font * style(web): tune sidebar workspace typography * style(web): polish composer and workspace picker * style(web): refine markdown and thinking typography * chore: add web UI polish changeset * fix(web): pin Inter package for Nix build * style(web): polish goal tool calls * style: polish goal mode display * fix: layer latest message pill below menus * style: align goal strip content
1 parent 735922c commit ad30a1c

39 files changed

Lines changed: 941 additions & 256 deletions

.changeset/web-ui-polish.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@moonshot-ai/kimi-code": patch
3+
---
4+
5+
web: Polish the chat UI with Inter typography, localized labels, and tighter sidebar, composer, and menu styling.

apps/kimi-web/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"check:style": "node scripts/check-style.mjs"
1414
},
1515
"dependencies": {
16-
"@fontsource-variable/inter": "^5.2.8",
16+
"@chenglou/pretext": "0.0.8",
17+
"@fontsource-variable/inter": "5.2.8",
1718
"@fontsource-variable/jetbrains-mono": "^5.2.8",
1819
"@xterm/addon-fit": "^0.11.0",
1920
"@xterm/xterm": "^6.0.0",

apps/kimi-web/src/components/SessionRow.vue

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,9 @@ defineExpose({ closeMenu });
341341
342342
.t {
343343
color: inherit;
344-
font-size: var(--ui-font-size);
345-
font-weight: var(--weight-regular);
344+
font-size: var(--text-base);
345+
font-weight: 450;
346+
line-height: var(--leading-tight);
346347
flex: 1;
347348
min-width: 0;
348349
overflow: hidden;
@@ -353,7 +354,10 @@ defineExpose({ closeMenu });
353354
.ts {
354355
color: var(--color-text-faint);
355356
font-size: var(--text-xs);
356-
font-family: var(--font-mono);
357+
font-family: var(--font-ui);
358+
font-weight: 475;
359+
font-variant-numeric: tabular-nums;
360+
text-align: right;
357361
}
358362
359363
/* Trailing action slot: time and kebab share one grid cell (grid-area:1/1).
@@ -366,7 +370,7 @@ defineExpose({ closeMenu });
366370
display: inline-grid;
367371
flex: none;
368372
align-items: center;
369-
justify-items: center;
373+
justify-items: end;
370374
}
371375
.act .ts,
372376
.act .kebab { grid-area: 1 / 1; }

apps/kimi-web/src/components/Sidebar.vue

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import IconButton from './ui/IconButton.vue';
2323
import Icon from './ui/Icon.vue';
2424
import Menu from './ui/Menu.vue';
2525
import MenuItem from './ui/MenuItem.vue';
26+
import ShortcutKey from './ui/ShortcutKey.vue';
2627
import { useConfirmDialog } from '../composables/useConfirmDialog';
2728
2829
const { t } = useI18n();
@@ -599,7 +600,10 @@ onBeforeUnmount(() => {
599600
<!-- Session search — opens the Spotlight-style search dialog -->
600601
<button class="search" type="button" @click="openSearch">
601602
<Icon class="search-icon" name="search" />
602-
<span class="search-input">{{ t('sidebar.searchShortcut', { shortcut: sessionSearchShortcut }) }}</span>
603+
<span class="search-input">
604+
<span class="search-label">{{ t('sidebar.searchPlaceholder') }}</span>
605+
<ShortcutKey>{{ sessionSearchShortcut }}</ShortcutKey>
606+
</span>
603607
</button>
604608

605609
<!-- New chat + new workspace buttons -->
@@ -911,6 +915,7 @@ onBeforeUnmount(() => {
911915
color: var(--color-text);
912916
font-family: var(--font-ui);
913917
font-size: var(--ui-font-size);
918+
font-weight: var(--weight-medium);
914919
cursor: pointer;
915920
text-align: left;
916921
}
@@ -950,15 +955,25 @@ onBeforeUnmount(() => {
950955
flex: none;
951956
}
952957
.search-input {
958+
display: inline-flex;
959+
align-items: center;
960+
gap: var(--space-1);
953961
flex: 1;
954962
min-width: 0;
955963
color: var(--color-text);
956-
font-family: var(--mono);
964+
font-family: var(--font-ui);
957965
font-size: var(--ui-font-size);
958966
overflow: hidden;
959967
text-overflow: ellipsis;
960968
white-space: nowrap;
961969
}
970+
.search-input > span {
971+
overflow: hidden;
972+
text-overflow: ellipsis;
973+
}
974+
.search-label {
975+
font-weight: var(--weight-medium);
976+
}
962977
963978
/* Sessions */
964979
.sessions {

apps/kimi-web/src/components/WorkspaceGroup.vue

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ function onHeaderDragStart(event: DragEvent): void {
265265
266266
.gh-name {
267267
font-size: var(--ui-font-size-lg);
268-
font-weight: var(--weight-medium);
268+
font-weight: 550;
269269
color: var(--color-text);
270270
flex: 1;
271271
min-width: 0;
@@ -276,6 +276,7 @@ function onHeaderDragStart(event: DragEvent): void {
276276
}
277277
.gh-path {
278278
color: var(--color-text-faint);
279+
font-weight: 425;
279280
overflow: hidden;
280281
text-overflow: ellipsis;
281282
white-space: nowrap;
@@ -317,10 +318,10 @@ function onHeaderDragStart(event: DragEvent): void {
317318
.gh-more.open { color: var(--color-text); background: var(--color-line); }
318319
319320
.group-empty {
320-
padding: var(--space-1) var(--space-2) var(--space-1) calc(var(--sb-pad-x) + var(--sb-gutter) + var(--sb-gap));
321+
padding: var(--space-1) var(--space-2) var(--space-1) calc(var(--sb-pad-x) - var(--space-2) + var(--sb-gutter) + var(--sb-gap));
321322
font-size: var(--text-xs);
322323
color: var(--color-text-faint);
323-
font-family: var(--font-mono);
324+
font-family: var(--font-ui);
324325
}
325326
/* Show-more / show-less — a session-row-shaped compact list control (§07). The
326327
empty lead slot mirrors a session row's status gutter, so the label text lands
@@ -338,7 +339,7 @@ function onHeaderDragStart(event: DragEvent): void {
338339
border: none;
339340
border-radius: var(--radius-md);
340341
background: transparent;
341-
color: var(--color-text);
342+
color: var(--color-text-muted);
342343
font-family: var(--font-ui);
343344
font-size: var(--text-xs);
344345
text-align: left;

apps/kimi-web/src/components/chat/ApprovalCard.vue

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Badge from '../ui/Badge.vue';
1010
import Button from '../ui/Button.vue';
1111
import IconButton from '../ui/IconButton.vue';
1212
import Icon from '../ui/Icon.vue';
13+
import ShortcutKey from '../ui/ShortcutKey.vue';
1314
import Tooltip from '../ui/Tooltip.vue';
1415
1516
const props = defineProps<{
@@ -314,20 +315,20 @@ onUnmounted(() => document.removeEventListener('keydown', handleKeydown));
314315
:loading="pendingAction === `option:${opt.label}`"
315316
:disabled="busy"
316317
@click="approveOption(opt.label)"
317-
>{{ opt.label }}<span class="k">[{{ i + 1 }}]</span></Button>
318+
>{{ opt.label }}<ShortcutKey class="k">[{{ i + 1 }}]</ShortcutKey></Button>
318319
</Tooltip>
319320
</template>
320-
<Button v-else class="kbtn" size="sm" variant="primary" :loading="pendingAction === 'approvePlan'" :disabled="busy" @click="approvePlan">{{ t('approval.approvePlan') }}<span class="k">[1]</span></Button>
321-
<Button class="kbtn" size="sm" variant="secondary" :disabled="busy" @click="revisePlan">{{ t('approval.revise') }}<span v-if="planReview.options.length === 0" class="k">[2]</span></Button>
322-
<Button class="kbtn" size="sm" variant="danger-soft" :loading="pendingAction === 'rejectAndExit'" :disabled="busy" @click="rejectAndExitPlan">{{ t('approval.rejectAndExit') }}<span v-if="planReview.options.length === 0" class="k">[3]</span></Button>
321+
<Button v-else class="kbtn" size="sm" variant="primary" :loading="pendingAction === 'approvePlan'" :disabled="busy" @click="approvePlan">{{ t('approval.approvePlan') }}<ShortcutKey class="k">[1]</ShortcutKey></Button>
322+
<Button class="kbtn" size="sm" variant="secondary" :disabled="busy" @click="revisePlan">{{ t('approval.revise') }}<ShortcutKey v-if="planReview.options.length === 0" class="k">[2]</ShortcutKey></Button>
323+
<Button class="kbtn" size="sm" variant="danger-soft" :loading="pendingAction === 'rejectAndExit'" :disabled="busy" @click="rejectAndExitPlan">{{ t('approval.rejectAndExit') }}<ShortcutKey v-if="planReview.options.length === 0" class="k">[3]</ShortcutKey></Button>
323324
</div>
324325

325326
<!-- default actions row -->
326327
<div v-else class="abtn">
327-
<Button class="kbtn" size="sm" variant="primary" :loading="pendingAction === 'approve'" :disabled="busy" @click="approve">{{ t('approval.approve') }}<span class="k">[1]</span></Button>
328-
<Button class="kbtn" size="sm" variant="secondary" :loading="pendingAction === 'approveSession'" :disabled="busy" @click="approveSession">{{ t('approval.approveSession') }}<span class="k">[2]</span></Button>
329-
<Button class="kbtn" size="sm" variant="secondary" :loading="pendingAction === 'reject'" :disabled="busy" @click="reject">{{ t('approval.reject') }}<span class="k">[3]</span></Button>
330-
<Button class="kbtn" size="sm" variant="secondary" :disabled="busy" @click="openFeedback">{{ t('approval.feedback') }}<span class="k">[4]</span></Button>
328+
<Button class="kbtn" size="sm" variant="primary" :loading="pendingAction === 'approve'" :disabled="busy" @click="approve">{{ t('approval.approve') }}<ShortcutKey class="k">[1]</ShortcutKey></Button>
329+
<Button class="kbtn" size="sm" variant="secondary" :loading="pendingAction === 'approveSession'" :disabled="busy" @click="approveSession">{{ t('approval.approveSession') }}<ShortcutKey class="k">[2]</ShortcutKey></Button>
330+
<Button class="kbtn" size="sm" variant="secondary" :loading="pendingAction === 'reject'" :disabled="busy" @click="reject">{{ t('approval.reject') }}<ShortcutKey class="k">[3]</ShortcutKey></Button>
331+
<Button class="kbtn" size="sm" variant="secondary" :disabled="busy" @click="openFeedback">{{ t('approval.feedback') }}<ShortcutKey class="k">[4]</ShortcutKey></Button>
331332
</div>
332333
</template>
333334
</Card>
@@ -554,7 +555,7 @@ onUnmounted(() => document.removeEventListener('keydown', handleKeydown));
554555
width: 100%;
555556
}
556557
.plan-actions { flex-wrap: wrap; }
557-
.k { margin-left: var(--space-2); font: var(--text-xs) var(--font-mono); opacity: .7; }
558+
.k { opacity: .75; }
558559
559560
/* =========================================================================
560561
MOBILE (≤640px): the card spans the full chat column, inner previews scroll

apps/kimi-web/src/components/chat/ChatDock.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ defineExpose({ loadForEdit, loadAttachmentsForEdit, focus });
251251
:plan-mode="planMode"
252252
:swarm-mode="swarmMode"
253253
:goal-mode="goalMode"
254+
:goal="goal"
254255
:activation-badges="activationBadges"
255256
:models="models"
256257
:starred-ids="starredIds"

apps/kimi-web/src/components/chat/ChatHeader.vue

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,25 @@ const behind = computed(() => props.behind ?? 0);
5151
const adds = computed(() => props.gitDiffStats?.totalAdditions ?? 0);
5252
const dels = computed(() => props.gitDiffStats?.totalDeletions ?? 0);
5353
const hasLineStats = computed(() => adds.value > 0 || dels.value > 0);
54+
const PR_STATE_LABEL_KEYS: Record<string, string> = {
55+
open: 'header.prStatusOpen',
56+
closed: 'header.prStatusClosed',
57+
merged: 'header.prStatusMerged',
58+
draft: 'header.prStatusDraft',
59+
};
60+
61+
function normalizedPrState(state: string): string {
62+
return state.trim().toLowerCase().replaceAll('_', '-');
63+
}
64+
65+
function prStateClass(state: string): string {
66+
const stateClass = normalizedPrState(state);
67+
return PR_STATE_LABEL_KEYS[stateClass] ? `pr-${stateClass}` : 'pr-unknown';
68+
}
69+
70+
function prStateLabel(state: string): string {
71+
return t(PR_STATE_LABEL_KEYS[normalizedPrState(state)] ?? 'header.prStatusUnknown');
72+
}
5473
5574
// ---------------------------------------------------------------------------
5675
// More-menu (kebab dropdown)
@@ -295,11 +314,11 @@ async function startArchive(): Promise<void> {
295314
v-if="pr"
296315
type="button"
297316
class="ch-pill ch-pr"
298-
:class="`pr-${pr.state}`"
317+
:class="prStateClass(pr.state)"
299318
@click="pr && emit('openPr', pr.url)"
300319
>
301320
<Icon name="git-pull-request" size="sm" />
302-
<span>PR #{{ pr.number }} · {{ pr.state }}</span>
321+
<span>PR #{{ pr.number }} · {{ prStateLabel(pr.state) }}</span>
303322
</button>
304323

305324
</header>
@@ -420,6 +439,7 @@ async function startArchive(): Promise<void> {
420439
.ch-pr.pr-merged { color: var(--color-done); border-color: var(--color-done-bd); background: var(--color-done-soft); }
421440
.ch-pr.pr-closed { color: var(--color-danger); border-color: var(--color-danger-bd); background: var(--color-danger-soft); }
422441
.ch-pr.pr-draft { color: var(--color-text-muted); border-color: var(--color-line-strong); background: var(--color-surface-sunken); }
442+
.ch-pr.pr-unknown { color: var(--color-text-muted); border-color: var(--color-line-strong); background: var(--color-surface-sunken); }
423443
.ch-pr:hover { border-color: var(--color-line-strong); }
424444
425445
/* Fixed more-menu, anchored to the kebab trigger. Surface / items come from

0 commit comments

Comments
 (0)