.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".
+
affordance stays reachable), the body below it must still render an empty
placeholder — persistent chrome is not an excuse to leave the content area
blank. ✅ The agent Documents tab keeps its new-folder / new-doc toolbar
and renders an Empty below it when there are no documents — ❌ not a toolbar
over dead space. (Meaningful)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)A surface with multiple tabs / views / panels has a landing selection. Don't hardcode it to "the first tab" — derive it from (a) how the user got here (the intent their navigation carried) and (b) which views actually have data. A static default that lands the user on an empty tab while a sibling holds exactly what they came for reads as broken. This pairs with §1.1: the empty state is the fallback within a view; this rule is about not landing on that empty view in the first place when a better one exists.
pickedTab
that overrides the derived default) so later data changes don't yank them off
their choice. (Natural)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.