.agents/skills/ux/SKILL.md
How LobeHub products should feel, and concrete rules to get there. Use this when building or reviewing any user-facing flow. For component/styling choices see react, for wording see microcopy, for imperative modal wiring see modal.
LobeHub follows four product design values — Natural・Meaningful・Certainty・ Growth. Read them before designing: references/design-values.md (definitions + conflict priority).
The checklists below are the execution layer. Each item is tagged with the value(s) it serves; for what those values mean, see the file above.
The checklists are grouped by interaction type — the kind of thing the user is doing. Jump to the module that matches the surface you're building (reading a list, editing content, running an action, …); each module collects the rules specific to that interaction. The same surface often spans several modules (an editable list is Read + Edit + Act) — walk each that applies.
Any surface that displays records, lists, or detail. Covers the states a data view can be in, behavior at scale, and keeping the user's place visible.
Every data surface has four states — design all of them, not just "has data".
A list/data page must be designed for its whole range of sizes, not just the demo data.
A capped / scrollable / virtualized list mounts at scrollTop = 0. If the
active item sits below the fold, the user lands on a valid selection that is
off-screen — and reads it as "nothing is selected" or a broken page. Any
list that can open with a pre-selected item must scroll that item into view.
This is an easy case to miss: it only shows up once the list is long enough and
the selection is restored rather than freshly clicked.
?thread= below the fold is scrolled into view on mount. (Certainty)block: 'nearest' (or equivalent). Only scroll when the row is
actually off-screen; an already-visible selection must not jump. (Natural)virtual flags, scope filters) and add them back.
✅ The default "LobeAI" (inbox) agent is virtual and excluded from the
sidebar list, so the move picker re-adds it. An empty picker must mean
"genuinely none", never "we filtered out the only option". (Meaningful)Any surface where the user types or edits. Input is expensive effort; the overriding rule is never lose it.
Typed / edited content is real user effort; losing it is one of the most infuriating outcomes a product can produce. Whenever an editor holds unsaved input, assume the exit can be accidental — a misclick, a refresh, a crash, a navigation, a failed save — and build a safety net: back the draft up locally and recover it.
Any surface where the user performs an action — a single op, a bulk op, or a multi-step flow. Covers momentum, focus, and full entity lifecycle.
Every action chain must push the user forward, never dead-end or block the flow.
The recurring trap: a feature ships only the display of a list, but edit / delete / management are never built — so the user can add something and then be stuck with it. For every entity a user can see, design its full lifecycle: create / read / update / delete, plus state transitions (enable/disable, connect/disconnect, install/uninstall). A read-only list the user can't manage breaks the flow.
The allowed operation set depends on the entity's source / ownership — decide it explicitly before building. Worked example, the tools/connectors list:
| Entity class | Add | Edit | Remove |
|---|---|---|---|
| Official / built-in (skills, tools) | — | — | ✗ not removable |
| Community (installed MCP) | install | configure | uninstall / remove |
| User-custom (custom connector) | create | edit | delete |
How the product answers back while and after the user acts — loading visuals and proactive guardrails.
Never use antd Spin — it doesn't match the product's loading visual. Use a
project loader:
| Need | Component |
|---|---|
| Default loading (in-flight) | NeuralNetworkLoading from @/components/NeuralNetworkLoading (size prop) |
| Inline dots | DotsLoading / BubblesLoading from @/components |
| Branded full-page | Loading from @/components/Loading/BrandTextLoading |
| List / card placeholder | a skeleton (e.g. SkeletonList) |
When in doubt, reach for NeuralNetworkLoading — it's the default in-flight
indicator (e.g. modal "in progress" states).
A feature can be fully built and still produce a broken result when the selected model — or its still-loading config — can't deliver the capability the feature depends on (for example, an agentic run on a model without tool calling). This is usually the user's configuration choice, not a defect; but if the product stays silent the user reads it as the product being broken. When a feature's success depends on a capability the current config may lack, the product owes a proactive, non-blocking reminder — a guardrail, not a gate.
How the product deepens as the user's needs deepen.
The product should grow with the user — deeper power shows up as needs deepen.
Read — viewing data & lists
block: 'nearest', re-run after async rows mount).Edit — entering & changing content
Act — operations, flows & buttons
Feedback — loading & system response
Spin; use NeuralNetworkLoading / project loaders.Grow — discoverability & progressive disclosure
createModal state-machine wiring for confirm/progress/done.Button usage, styling.