Back to Lobehub

UX — Design Values & Execution Checklists

.agents/skills/ux/SKILL.md

2.2.919.7 KB
Original Source

UX — Design Values & Execution Checklists

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.

Design values

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.

Interaction principles

Use these principles before the execution checklists when a flow has multiple plausible interaction patterns.

Preserve the surface contract・Meaningful・Natural

Every surface carries a task promise: chat keeps the user in a working conversation, a document page supports focused reading / editing, a settings page supports configuration, and so on. Default interactions should continue that promise instead of unexpectedly moving the user into another mode. Prefer in-context surfaces (portal / panel / drawer) for reference and auxiliary work; reserve full-page navigation for committed focus or explicit mode switches.

Consistency is semantic, not mechanical・Certainty・Meaningful

Consistency means the same user intent behaves the same way in the same surface. It does not mean the same component must do the same thing everywhere. When a component is reused across surfaces, let the parent surface provide the interaction strategy so behavior follows intent rather than implementation convenience.

Layout communicates role・Natural・Certainty

Element placement is part of the interface language. Identity and location (breadcrumbs, titles, object labels) should read separately from state and actions (save status, sharing, panel toggles, overflow menus). When these roles are mixed, users have to infer whether an element describes the current object or acts on it.

How this is organized

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.


1. Read — viewing data & lists

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.

1.1 Data states: empty / loading / error・Meaningful・Certainty

Every data surface has four states — design all of them, not just "has data".

  • Empty state is a purpose-built page, not a blank screen. It explains what this is, why it's empty, and gives a clear next action (CTA + value props). ✅ Devices: an empty "Connect your first device" page with primary/secondary connect paths and "what you can do once connected" cards — ❌ not a bare title over skeleton rows or a blank body. (Meaningful)
  • Distinguish the empty variants — "no data yet" (onboarding CTA) vs "no match for filters" (clear-filters affordance) are different screens. (Certainty)
  • Always-rendered chrome still needs a body empty state. When a surface keeps its toolbar / header mounted even with no data (so a create / + 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)
  • Loading state designed (skeleton / NeuralNetworkLoading), not a flash of blank or layout shift. (Natural)
  • Error state designed — surface the reason and a retry/back path. (Meaningful)

1.2 Lists at scale・Certainty・Natural

A list/data page must be designed for its whole range of sizes, not just the demo data.

  • Walk the scale: 1 / 2 / 5 / 20 / 100 / 1k–10k rows. Pick the right mechanism per range — plain render → load-more / pagination → virtual scroll; add batch-select / bulk actions once counts get large. (Certainty)
  • Co-design empty / loading / error with the data state (see §1.1). A list isn't done until all four render well. (Natural)

1.3 Selection visibility in scrolled lists・Certainty・Natural

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.

  • Scroll the active item into view on mount / restore. When the selection is restored from a URL query, deep link, or persisted state (not a fresh click), bring it into view — the container starts at the top otherwise. ✅ The nested thread list is capped to ~9 rows; a thread restored from ?thread= below the fold is scrolled into view on mount. (Certainty)
  • Hardest when the selection has no other anchor. If the parent/container row isn't highlighted while a child is active (no breadcrumb, no header echo), an off-screen active row means zero visible feedback — design for exactly this case. (Meaningful)
  • Use block: 'nearest' (or equivalent). Only scroll when the row is actually off-screen; an already-visible selection must not jump. (Natural)
  • Re-run once async rows mount. The active id is usually known before the list finishes loading; key the scroll off a list-ready signal (e.g. row count), not only off the id, so a restored selection still lands when the data arrives. (Certainty)
  • Mirror it across duplicated list variants so the behavior can't regress in just one (e.g. parallel agent / group lists). (Certainty)

1.4 Option visibility in pickers・Certainty・Meaningful

  • Pickers list every valid target. Watch for options dropped by backend list queries (pagination, 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)

1.5 Default view reflects entry intent & data state・Certainty・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.

  • Open on the tab the entry implies. When navigation carries intent — the user clicked a Skill, a file, a record of a specific type — land on the view that shows it, not the static first tab. ✅ Opening a document page by clicking a skill lands the right panel on the Skills tab; opening a plain document lands on Documents. (Meaningful)
  • Fall back to a populated view when the default would be empty. If the default tab has no data but a sibling does, default to the populated one so the surface opens on content. ✅ An agent with only skills (no documents) opens the panel on Skills instead of an empty Documents tab. (Certainty)
  • Decide from resolved state, not mid-load. Compute the default once the data has loaded — choosing off an empty in-flight list flips the tab as data arrives. Hold the static default while loading, switch on resolved-empty. (Certainty)
  • A manual choice wins and sticks. Once the user picks a tab, stop auto-selecting — track "user-picked" separately (e.g. a nullable pickedTab that overrides the derived default) so later data changes don't yank them off their choice. (Natural)

2. Edit — entering & changing content

Any surface where the user types or edits. Input is expensive effort; the overriding rule is never lose it.

2.1 Protect in-progress edits・Certainty・Meaningful

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.

  • Back up the draft locally as the user types. Persist to localStorage / IndexedDB / store so a refresh, crash, accidental close, or navigation doesn't vaporize the content. (Certainty)
  • Restore on return. Coming back to the same editing context auto-restores (or offers to restore) the unsaved draft, rather than showing a blank field. (Meaningful)
  • Guard destructive exits. Closing / navigating / switching items away from a dirty editor warns or auto-saves — never silently discards. (Certainty)
  • Survive a failed save. If the save errors, keep the user's content in the field / draft and let them retry; never clear the input on failure. (Meaningful)
  • Scope the draft to its target (per topic / message / item id) so drafts don't bleed across entities or resurrect on the wrong item. (Certainty)

3. Act — operations, flows & buttons

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.

3.1 Flow & momentum・Natural・Meaningful

Every action chain must push the user forward, never dead-end or block the flow.

  • Forward momentum — after any operation, lead the user to the next step, don't just stop. (Meaningful)
  • Success state = primary "go to result", secondary "dismiss" — the strong button is the forward action (take me to the result); "Done" is the weak/ secondary button. ✅ After moving topics: primary = "Go to «target»", secondary = "Done". (Meaningful・Natural)
  • Bulk ⇄ single-item parity — an action on a multi-select toolbar must also be reachable on a single item (its context menu), and vice versa. (Certainty)
  • Confirm → in-progress → done, in one surface — bulk/irreversible/async ops use a modal state machine: a confirm step stating exactly what happens → an in-progress view with dismissal locked → a done (or error) view in the same modal. Never fire-and-forget with only a toast; never leave a dead spinner. (Certainty・Meaningful)

3.2 One primary button per surface・Certainty

  • One primary button per surface. The single primary CTA tells the user the core action; everything else is secondary/tertiary. Never a pile of primary buttons competing for attention. (Certainty)

3.3 Entity lifecycle completeness・Meaningful・Certainty

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 classAddEditRemove
Official / built-in (skills, tools)✗ not removable
Community (installed MCP)installconfigureuninstall / remove
User-custom (custom connector)createeditdelete
  • No display-only features. For every listed entity, enumerate CRUD + lifecycle ops and build the ones that apply. (Meaningful)
  • Operation set per source/ownership class — built-in may be read-only; anything the user installed must be removable; anything the user created must be editable and deletable. (Certainty)
  • Each item exposes its allowed ops (hover action / context menu / detail page), and there's a clear entry point to add/create where applicable. (Natural)
  • An intentionally-absent op is a documented decision, not an oversight (e.g. official tools can't be deleted — by design). (Certainty)

4. Feedback — loading & system response

How the product answers back while and after the user acts — loading visuals and proactive guardrails.

4.1 Loading visuals・Natural

Never use antd Spin — it doesn't match the product's loading visual. Use a project loader:

NeedComponent
Default loading (in-flight)NeuralNetworkLoading from @/components/NeuralNetworkLoading (size prop)
Inline dotsDotsLoading / BubblesLoading from @/components
Branded full-pageLoading from @/components/Loading/BrandTextLoading
List / card placeholdera skeleton (e.g. SkeletonList)

When in doubt, reach for NeuralNetworkLoading — it's the default in-flight indicator (e.g. modal "in progress" states).

Minimise layout shift (CLS): swap text for skeleton in place, keep the chrome. The strongest loading state changes as little of the final layout as possible, so nothing jumps when the real content lands. When a surface already knows its shape (a card, a row, a list item), keep the layout elements — the container, border, radius, padding, icon — and replace only the text/data with a skeleton sized like the text it stands in for. A generic full-block / full-card skeleton (or a centred spinner that the real content later pushes aside) is heavier and shifts the layout; an in-place text→skeleton swap is the optimal design.

  • Reuse the loaded component's chrome for the skeleton. Render the same card/row wrapper and only skeletonise the text slots, so loading→loaded is a content swap, not a relayout. (Certainty・Natural)
  • Size skeleton lines like the text they replace (line height, count, rough width) so the placeholder height ≈ the real height. (Certainty)
  • Don't downgrade a known-shape surface to a bare block/spinner — that throws away the layout you already have and reintroduces the shift. (Natural)

4.2 Capability-gated features・Certainty・Meaningful

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.

  • Surface the mismatch, don't fail silently. When a feature needs a model capability (tool calling, vision, reasoning, long context) the current model lacks, show a soft inline warning at the point of action — never a hard block or a modal that stops the user. (Meaningful)
  • Stay reactive. The reminder clears the moment the user switches to a capable model — derive it from live state, not a one-shot check. (Natural)
  • Don't warn while config is loading. A capability that hasn't resolved yet looks "unsupported"; warning then is a false alarm — exactly the glitch users mistake for a product bug. Warn only on a resolved unsupported state. (Certainty)
  • Scope to the mode that needs it. Show only when the capability-dependent mode is on; one reminder per root cause, never a pile of overlapping notices. (Natural・Certainty)
  • State the problem and the remedy. The copy says what's wrong and what the user should do about it. (Meaningful)

5. Grow — discoverability & progressive disclosure

How the product deepens as the user's needs deepen.

5.1 Progressive disclosure・Growth

The product should grow with the user — deeper power shows up as needs deepen.

  • Progressive disclosure — keep the novice path clean; reveal advanced capabilities as the user gets there, don't dump everything at once. (Growth・Natural)
  • Surface related actions at the moment of need — make the next capability discoverable in context (e.g. after the first item exists, offer what to do with it), not buried in a far-off menu. (Growth・Meaningful)

Quick review checklist

Read — viewing data & lists

  • Empty / loading / error states are all designed; empty is a real page with a CTA. Always-rendered chrome (toolbar/header) still gets a body empty state.
  • List designed across 1 → 10k rows (virtual scroll / pagination / batch as needed).
  • Capped/scrollable/virtualized list scrolls the restored active item into view on mount (block: 'nearest', re-run after async rows mount).
  • Pickers show all valid targets (default/inbox included); empty = truly none.
  • Multi-tab/view surface lands on the tab the entry intent implies (and falls back to a populated view, decided from resolved state); a manual pick sticks.

Edit — entering & changing content

  • Editors back up in-progress input locally and recover it after refresh/crash/failed-save; destructive exits warn, never silently discard.

Act — operations, flows & buttons

  • Action leads the user forward; success offers a primary "go to result".
  • Bulk action has a single-item entry (and vice versa).
  • Async/bulk/irreversible action: confirm → in-progress (locked) → done/error.
  • Exactly one primary button per surface.
  • Listed entities have their full lifecycle (not display-only); ops match source (built-in / installed / custom).

Feedback — loading & system response

  • No antd Spin; use NeuralNetworkLoading / project loaders.
  • Capability-gated feature warns (soft, reactive, load-gated) when the model can't deliver it; copy gives the remedy.

Grow — discoverability & progressive disclosure

  • Advanced capability is progressively disclosed / discoverable at the moment of need.
  • modal — imperative createModal state-machine wiring for confirm/progress/done.
  • microcopy — wording for confirm / done / empty / error states.
  • react — component priority, Button usage, styling.