Skip to content

epic: make the bundled WebUI usable, on the shared ui-common foundation #1910

Description

@inureyes

Problem / Background

Epic #1834 shipped a bundled WebUI whose contracts, security and accessibility gates pass, but whose screens are not usable: the Settings page is one 5,720 px scroll of 129 inputs, the Models page shows four rows per viewport, the Chat transcript has no role distinction, Korean mode is mostly English, and the Activity counters read zero for every UI chat because the server never records streaming completions. The four follow-ups filed on 2026-09-16 (#1900, #1901, #1902, #1903) and the ROCm catalog bug #1886 become children of this epic rather than standalone work.

Goal

Turn the bundled WebUI into a product surface: dense, task-first screens on the shared @lablup/ui-common foundation, every user-facing string in the catalog, honest metrics measured on the path the UI actually uses, and no internal state leaking into copy.

Evidence (review of 2026-09-17, release build of main 0ef0a1a4)

The review ran the real binary (mlxcel-server --webui --models-dir models/mlx --api-key-file ... --no-models-autoload --settings --props --metrics --slots) against 212 local checkpoints and drove login, load, streaming chat and unload with Playwright at 1440x900 and 390x844. Measured:

  • Settings page: 5,720 px tall, 129 form inputs, 763 words, 11 h2 headings, one uninterrupted scroll (webui/src/app.tsx SettingsScreen plus webui/src/features/settings/server-settings.tsx).
  • Models page: 3,132 px tall at 1440x900 with four table rows visible in the viewport; each row is about 100 px because the name cell stacks a ghost button, "Selected", and a source · quantization line (webui/src/features/models/screen.tsx:186-242). At 390 px the page is 5,936 px tall.
  • Chat page renders prompt, an "Edit and regenerate" button, the response, "Copy response" and a "Response details" disclosure as one flat article with no role distinction (webui/src/features/chat/transcript.tsx); conversations are a <select>; the composer is not pinned; the transcript is a separate 60vh scroll box (webui/src/features/chat/chat.css).
  • Server defect: streaming chat completions are never recorded in Metrics::record_request (src/server/state.rs:62-75); src/server/routes/chat.rs:776 records only the non-streaming path (the other call in that file, chat.rs:1349, is the audio transcription stream), while src/server/routes/anthropic.rs:667 and src/server/routes/responses.rs:794 record their streaming paths. Verified: one streaming plus one non-streaming /v1/chat/completions request yields completed_requests_total = 1 and completion_tokens_total = 19 from /ui-api/v1/runtime. The WebUI chat always streams, so Activity shows 0 completed requests and 0 completion tokens for every UI session.
  • Localization: webui/src/features/chat/chat.tsx contains 49 hard-coded English literals and window.confirm in 4 places (chat.tsx, privacy.tsx); the settings screens use a local words(en, ko) helper at 10 sites; webui/src/features/activity/strings.ts and webui/src/features/models/next-load-profile.tsx branch on locale === 'ko'. In Korean mode the Chat screen shows only its title in Korean. This violates docs/webui/ux-contract.md (every user-facing string has a key with English and Korean values in tests/fixtures/webui/strings.json).
  • The toolbar shows the browser's selected model, not the server's loaded models (webui/src/provider-surfaces.tsx selectedModelLabel); a fresh tab reads "No model selected" while a model is Ready on the server. The Chat model picker lists all 212 catalog entries with a lifecycle suffix.
  • Internal state exposed as copy: the inspector prints opaque mdl_... ids, raw reason strings such as "parameter count is not measured during metadata-only catalog scans", "Worker exit observed: No" for a Ready model, capability rows like chat · pre_load · Yes (webui/src/features/models/inspector.tsx); the Models page header prints "Backend router_pool; build 0.7.0; state streaming; catalog 212; operations 1; snapshot 3." and a "Library operations" list with op_model_load_000001 that duplicates Activity (webui/src/features/models/operations.tsx).
  • Catalog display names replace - and _ with spaces (display_name in src/server/webui/catalog_metadata.rs:465-470), so qwen3-0.6b-4bit shows as qwen3 0.6b 4bit and the inference id is hidden.
  • Activity: slot 0 shows a 21 / 40960 tokens bar while slots 1 to 3 print "context denominator is unknown" although request_context_tokens is the same 40960; the bar is drawn only when prompt_tokens is non-null (webui/src/features/activity/runtime.tsx). "Unavailable measurements: 12" is a headline.
  • Live settings render f32 values through f64 (0.800000011920929, 0.949999988079071), raw names (default_dry_multiplier) and the string null (webui/src/features/settings/live-settings.tsx).
  • Theme: data-theme="system" is written verbatim under the default preference so [data-theme="dark"] and [data-theme="light"] never match (already the subject of feat(webui): adopt the ui-common theme system and add a glass theme #1903). Two token systems coexist (--color-* in webui/src/design-system/tokens.css, --token-* in webui/src/design-system/common-tokens.css) and webui/src/design-system/components.css overrides ui-common classes.
  • Global shortcuts: the shell implements only Cmd/Ctrl+K and ? (webui/src/design-system/shell.tsx:34-39); the contract also lists Cmd/Ctrl+N and Cmd/Ctrl+Enter.

Contract for this epic

  • Keep every gate from epic: ship a bundled macOS-style model control interface #1834: keyboard-first navigation, visible focus, focus restoration, 200 percent zoom, off-canvas sidebar at 390 px with no horizontal page scroll, WCAG 2.2 AA, IME-safe inputs, CSP without inline scripts, and the /ui-api/v1 contract unchanged unless a child says otherwise.
  • Foundation before screens: theme (feat(webui): adopt the ui-common theme system and add a glass theme #1903) and components (refactor(webui): adopt every shared @lablup/ui-common component #1902) land first so every screen redesign composes the shared components once.
  • Every user-facing string goes through webui/src/i18n/catalog.ts with tests/fixtures/webui/strings.json in sync; no window.confirm, window.alert or window.prompt.
  • Internal identifiers, raw reason strings and enum names are not user copy. They may appear inside an explicit details disclosure or a diagnostics export.
  • Every metric shown must be measured for the path the UI actually uses (streaming chat).
  • Each child is one branch and one PR, closes only its child and references this epic. Verify against the running bundled binary, not only mocks; screenshots of mocks do not prove acceptance.

Sub-issues

Filed 2026-09-18. #1886, #1911 and #1912 have no dependency on any other child and sit in Phase 1 so they are dispatched at once; #1911 and #1912 are server-side changes the Phase 2 screens consume.

Phase 1

Phase 2

Phase 3

Definition of done

  • All children closed by merged PRs, each verified against the installed bundled binary with a real checkpoint.
  • pnpm --dir webui run lint, typecheck, unit, browser, verify-generated green; cargo test --workspace --profile test-fast --features metal,accelerate, clippy and fmt green.
  • A final walkthrough at 1440x900 and 390x844 records: find a model, load it, chat, stop, unload, all reachable from the Models list without opening the inspector by name; Activity shows non-zero completed requests after one streaming chat; Korean mode has no English copy outside proper nouns and identifiers.
  • Page heights and word counts for Models, Chat, Activity and Settings are recorded before and after in the closing comment.

Technical Considerations

/epic-impl reads the ## Sub-issues section as the execution DAG: items inside one ### Phase N heading run in parallel unless an explicit (depends on ...) annotation narrows the wait. The two server-only children (#1911, #1912) sit in Phase 1 with no annotation so they start immediately; #1918 and #1916 name them as dependencies.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:architectureArchitecture and code structure changespriority:highHigh prioritystatus:readyReady to be worked ontype:enhancementNew features, capabilities, or significant additions

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions