kimi-code/apps/kimi-web/AGENTS.md
qer 01b65bdddc
feat(web): open design-system easter egg at /design-system route (#1328)
* feat(web): open design-system easter egg at /design-system route

Replace the long-press-logo iframe overlay, which loaded a separately maintained static design-system.html, with a real /design-system route. The new view aliases to the product design tokens, so the design system is maintained in one place. Adds vue-router for the route, removes the duplicate static HTML copies, and exempts the showcase view from the style scanner.

* fix(web): lazy-load the design-system view

Address Codex review: load the 2.4k-line showcase via defineAsyncComponent so it is code-split and fetched only when /design-system is visited, instead of bloating the initial bundle for every load.

* chore(nix): update pnpmDeps hash for vue-router

Adding vue-router changed pnpm-lock.yaml, which invalidated the fetchPnpmDeps hash. Set it to the value reported by the nix build.

* fix(web): return to app root when closing the design system

In-page nav anchors push hash history entries, so router.back() only stepped through them and required multiple clicks to leave. Navigate to / directly; the client lives above the route so session state is preserved.

* feat(web): let /design-system bypass the auth gate

Render the design-system route ahead of the auth/server gates and skip the /login rewrite for it, so a direct deep link shows the showcase even before the app is OAuth-ready. The view is read-only and holds no user data, matching the old static page behavior.

* fix(web): make the design-system root scrollable

The route renders inside .app-shell (height:100dvh; overflow:hidden), so a position:fixed root could be clipped. Make .ds-page a flex item that fills the shell and scrolls internally, so later sections and hash navigation remain reachable.

* fix(web): preserve /design-system during initial session load

On load the app auto-selects the first session and rewrites the URL to /sessions/<id>, which clobbered a deep-linked /design-system. Skip the session URL write while on the design-system route so refreshing keeps the route.

* fix(web): restore the prior session URL when leaving the design system

Record the URL on entry to /design-system and navigate back to it on close, so a /sessions/<id> URL is preserved instead of falling back to /. This also keeps the earlier fix that sidesteps in-page hash anchors.

* fix(web): capture the real browser URL before opening the design system

Session URLs are rewritten via the native history API, so vue-router's from.fullPath can be stale ('/' after a session is selected). Read window.location on entry instead, falling back to / for a direct deep link.

* fix(web): sync the active session URL when leaving the design system

After a direct deep link to /design-system the app auto-selects a session but the address stays '/'. On close, fall back to the active session's canonical URL so the address bar matches the displayed session.

* fix(web): capture the design-system return path at logo entry

Capture window.location in the logo long-press handler instead of a navigation guard. The guard fired on browser Back/Forward too and overwrote the return path with the design-system URL itself; capturing only at the explicit entry action avoids that.

* fix(web): replace the design-system route when closing

Use router.replace instead of push for the captured return URL, so closing does not append a second app URL after /design-system and the browser Back button returns to the page before the easter egg.

* revert(web): drop the design-system auth-gate bypass

Keep /design-system behind the auth gate so it has a single in-app entry (logo long-press). This removes the deep-link machinery (auth exemption, active-session fallback) that drove most of the URL/session edge cases, while keeping the lazy-loaded route, the flex scroll fix, the logo-entry return-path capture, and replace-on-close.

* refactor(web): rebuild the design-system easter egg as an in-app overlay

The easter egg is a hidden, read-only spec viewer opened by long-pressing the logo; it does not need to be a URL route. Replace the vue-router approach with an overlay: Sidebar opens a lazy-loaded DesignSystemView in a body-teleported full-screen overlay, dismissed by the Back button or Escape. This removes vue-router, the route, the auth-gate exemption, the session-URL guards, and the return-path machinery — none of which the feature needs.

* chore(nix): restore pnpmDeps hash after removing vue-router
2026-07-03 13:42:27 +08:00

6.6 KiB

kimi-web Agent Guide

Package-local rules for apps/kimi-web (@moonshot-ai/kimi-web).

What it is

The browser web UI for Kimi Code — a peer to the TUI in apps/kimi-code. It talks to the local server over REST + WebSocket under /api/v1. Stack: Vue 3 + Vite 6 + TypeScript (strict) + vue-i18n v11. (Tailwind was removed; all styling is via design tokens in src/style.css + scoped component styles.) There is no client router and no Pinia; state lives in composables/refs and provide/inject.

Design system (normative — required when modifying the UI)

  • Before changing any component, style, layout, or theme, read the design system view at src/views/DesignSystemView.vue (open it as an overlay: long-press the sidebar logo). It is the canonical design system and visual spec for this app (tokens §02, primitives §03, chat §04, theme rules §05, style rules §06). It consumes the product tokens from src/style.css directly, so it stays in sync with the app. New and modified UI must match it.
  • Use the primitives in src/components/ui/. The library covers Button, IconButton, Badge, Pill, Card, Input/Select/Textarea/Field, Dialog, Spinner, MoonSpinner, Link, Menu/MenuItem, SegmentedControl, Tabs, Switch, Checkbox, Avatar, EmptyState, Divider, Tooltip, Banner, Sheet, Skeleton, CommandBar, TopBar. One semantic = one component — do not hand-roll a bespoke button/badge/dialog/input for a single screen. When a primitive replaces an element, delete the old scoped CSS (do not append override blocks).
  • Use the tokens, not ad-hoc values. Colors, fonts, radii, spacing, shadows, z-index, and motion come from the CSS custom properties in src/style.css (catalogued in the design-system view §02). Canonical names are --color-* / --radius-* / --space-* / --text-* / --font-* / --z-* / --shadow-* / --ease-* / --duration-* / --weight-* / --leading-*. A small set of layout/focus tokens keep the --p- prefix: --p-focus-ring, --p-selection, --p-ic-sm/md/lg, --p-sidebar-w, --p-content-max/-wide, --p-bp-sm/-md.
  • The moon spinner (🌑🌘) is reserved for the chat "waiting for the agent's first response" state only, and is rendered solely by ui/MoonSpinner.vue; every other loading state uses the plain Spinner.
  • Run pnpm --filter @moonshot-ai/kimi-web check:style (scripts/check-style.mjs) — it enforces the §06 anti-pattern rules (no-gradient, no-glassmorphism except TopBar frost, no-emoji-icon except moon, no-hardcoded-hex/font, radius/z/weight from scale). Do not add new violations.
  • Verify visually. For any UI change, render it in the browser (light + dark, plus hover/focus states) and confirm it matches the design-system view and introduces no regression before considering it done. Build/typecheck/check-style are necessary but not sufficient.

Layout (src/)

  • main.ts — bootstrap (creates the app, installs i18n, mounts #app). App.vue — root component, holds most app state.
  • api/ — server client. index.ts exposes the getKimiWebApi() singleton; config.ts builds REST/WS URLs; daemon/ holds the wire client (http.ts, ws.ts, wire.ts, mappers.ts, agentEventProjector.ts, eventReducer.ts).
  • components/ — SFCs grouped by area: chat/ (conversation/chat UI), settings/ (settings & configuration), dialogs/ (modal dialogs & sheets), mobile/ (mobile-specific shell), ui/ (design-system primitives — see "Design system" above), plus shared layout components at the top level.
  • composables/ — reusable state logic, useX naming (useKimiWebClient, useIsDark, usePaneLayout, …).
  • lib/ — pure helpers (parseDiff, slashCommands, sessionRoute, toolMeta, …).
  • i18n/ — vue-i18n setup plus locale namespaces.
  • debug/DebugPanel.vue and trace.ts for client error/trace capture.

Vue conventions (normative)

  • SFCs use <script setup lang="ts"> + the Composition API. Component files are PascalCase (ChatHeader.vue).
  • Type props with the generic form defineProps<{ ... }>(); type emits with defineEmits<{ evt: [arg: Type] }>().
  • Shared components go in src/components/; reusable logic goes in src/composables/ with a use prefix.
  • There is no auto-import plugin and no path alias#/ and @/ are intentionally unused. Write relative imports (../i18n, ./config).

i18n (normative — keeping locales in sync is manual)

  • Setup: src/i18n/index.ts, vue-i18n in Composition mode (legacy: false), fallback en. The active locale is persisted in localStorage under kimi-locale.
  • Locale files: src/i18n/locales/{en,zh}/<namespace>.ts, each export default { ... } as const. New namespaces are registered in src/i18n/locales/index.ts.
  • Reference with const { t } = useI18n() and t('namespace.key') (same form in templates).
  • Adding a key: add it to both en/<ns>.ts and zh/<ns>.ts. Adding a namespace: create the file in both locales and register it in locales/index.ts.
  • There is no automated missing-key or en/zh parity check. Keeping the two locales in sync is a manual responsibility — do not leave a key present in only one locale.

Commands

All via pnpm --filter @moonshot-ai/kimi-web …:

  • dev — Vite dev server (port WEB_PORT, default 5175; proxies /api/v1 to KIMI_SERVER_URL, default http://127.0.0.1:58627).
  • dev:stub — offline stub daemon (dev/stub-daemon.mjs).
  • build — production build into dist/.
  • typecheckvue-tsc --noEmit.
  • testvitest run (pure logic tests only; no jsdom / component tests).
  • check:style — design-system §06 anti-pattern guard (scripts/check-style.mjs).
  • There is no lint script in this package; linting runs at the repo root via oxlint.

Gotchas / hard rules

  • Do not depend on @moonshot-ai/agent-core (mirrors the CLI/SDK rule). The web app is decoupled from core/protocol; wire types are re-implemented locally in src/api/daemon/wire.ts. Keep it that way.
  • Same-origin by default: the browser only talks to its own origin; Vite proxies /api/v1 for both HTTP and WS. Set VITE_KIMI_SERVER_HTTP_URL only when you intentionally want direct (CORS) mode.
  • Vite-injected globals (__KIMI_DEV_PROXY_TARGET__, __KIMI_WEB_VERSION__, __KIMI_WEB_COMMIT__) are declared in src/env.d.ts and defined in vite.config.ts. Do not hand-edit dist/.
  • Theming: the root element carries data-color-scheme (light | dark | system); react to it through useIsDark(), not by reading the DOM directly.
  • Keep the Vite dev proxy and preview proxy in sync — both are defined in vite.config.ts.