Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .agents/docs/components-package.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,15 @@ button for a file the machine will refuse, and the user would lose the edit at s

## Theming and fields

Empty settings catalogs use `SettingsEmptyList`: a centered decorative jellyfish,
a localized message, and any existing guidance directly on the page. A records
card appears once there are records. Existing catalog copy is preserved verbatim,
including punctuation and instructions; no new guidance is added.
The page owns its add action, while the optional empty-state action supports a
different entry point such as GitHub installation. Project Skills uses this
presentation for an empty inventory; its errors and search misses keep their
existing messages. See the [empty catalog decision](../notes/implemented/feature/2026-10-20-settings-empty-catalogs.md).

Reusable visual primitives and semantic design tokens are owned by
[`@lody/ui`](../../packages/ui/README.md). `@lody/components` composes those
primitives into product surfaces and may add layout or interaction constraints,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Illustrated empty settings catalogs

Status: implemented
Translation: current
PR: [#1745](https://github.com/LodyAI/Lody/pull/1745)

[中文](2026-10-20-settings-empty-catalogs.zh.md)

## Abstract

Empty settings catalogs looked like a record card containing only an absence
message. They now present a centered Lody jellyfish illustration above localized
copy on the page, preserving every existing message and instruction verbatim.
The shared component also covers existing project, GitHub, share, and provider
empty states, and the empty Project Skills inventory. Existing add actions remain
available, and the illustration is decorative. Data ownership, loading conditions,
permissions, error states, and populated lists retain their existing behavior.

## Decision

This replaces the empty-catalog presentation from the
[settings rhythm decision](2026-09-25-settings-rhythm-and-image-peek.md#design-review-round-2).
`SettingsEmptyList` now draws on the page rather than using `settingsRecordsCard`.
It shares StyleX geometry through the settings surface and accepts an optional
description. It has no internal scroller; copy wraps inside narrow panels. The
page keeps its existing add action, while the existing optional action slot still
supports GitHub installation without adding a second page action.

Existing English and Chinese copy is preserved verbatim, including punctuation
and instructions. No new guidance is added. The optional description carries the
existing Project Skills hint. The same presentation serves
both saved memory associations and an empty external memory inventory. Project
Skills uses it only for an empty inventory, keeping error/retry and search-result
messages separate. No catalog writes, provider requests, selection rules or
capability gates changed.

## Illustration

The new [transparent PNG](../../../../packages/components/src/assets/settings-empty-jellyfish.png)
was generated with the built-in `image_gen` tool using the existing `lody-icon.png`
as the brand reference. A blue/teal jellyfish searches an empty folder with a
magnifying glass; there is no embedded text or theme-colored backdrop. The
[generation prompt](../../../../packages/components/src/assets/settings-empty-jellyfish.prompt.txt)
is retained with the asset. The image is local to the public package and rendered
with empty alt text and `aria-hidden`; the adjacent copy carries the meaning.

## Verification

The components type check, typed lint, translation-key validation and 17 existing
memory, MCP and Prompt Shortcut form tests pass. Two desktop browser cases cover
six empty catalogs in English/light/1180px and Chinese/dark/500px, including image
loading, centering, wrapping, no card or internal scroller, add dialogs, and
loading/read-only conditions. Two mobile cases cover Roles and MCP in both
palettes at 393×852 with touch input. All four browser cases pass; the local run
uses Chromium build 1223 and allows 30 seconds for Storybook rendering.

`pnpm format` passes. The latest `pnpm check` completed type checks and lint but
failed on a five-second timeout in the unchanged iOS simulator panel suite
(5,742 component tests passed). That entire 25-test suite passes independently;
all 4,423 CLI tests passed, with four skipped. Translation and boundary checks
were also run separately and pass. The complete root check is therefore not
green. Documentation checking retains the six existing missing Kimi/Pi submodule
links, with no new errors or protected-topic changes.

## Screenshots

Before and after screenshots are attached to [PR #1745](https://github.com/LodyAI/Lody/pull/1745)
instead of being tracked in the repository. They use the actual catalog components
with synthetic fixtures. Desktop uses an 1180×800 viewport and shows the content
pane; mobile uses the production `MobileSettingsLayout` in a 393×852 touch viewport.
Both palettes are included, with matching fixtures and viewports for each pair.
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# 带插画的设置目录空状态

Status: implemented
Translation: current
PR: [#1745](https://github.com/LodyAI/Lody/pull/1745)

[English](2026-10-20-settings-empty-catalogs.md)

## 摘要

设置目录为空时,原先只显示一张写着“没有”的记录卡片。
现在直接在页面上居中展示 Lody 水母插画与本地化文字,原有提示和操作说明保持原文。
共用组件也覆盖已有项目、GitHub、分享、Provider
的空状态,以及空的项目 Skills 目录。已有添加入口继续可用,插画仅作装饰。
数据归属、加载条件、权限、错误状态和非空列表均保持原有行为。

## 决定

本次替换[设置节奏决定](2026-09-25-settings-rhythm-and-image-peek.md#design-review-round-2)
中的空目录展示方式。`SettingsEmptyList` 直接绘制在页面上,不再使用
`settingsRecordsCard`。共用 StyleX 布局由设置 surface 管理,并新增可选说明文字。
组件内部没有滚动区域,文字能在窄面板内换行。页面保留已有添加按钮;原有的可选
操作区域仍能承载 GitHub 安装入口,不会额外重复页面的添加动作。

原有中英文文案逐字保留,包括标点和操作说明,不新增引导文案。
可选描述承载项目 Skills 已有的提示。
已保存的记忆关联与外部记忆目录为空时共用同样的展示方式。项目 Skills 只在目录
确实为空时使用插画,错误重试与搜索未命中的提示继续单独显示。
没有改变目录写入、Provider 请求、选择规则或能力开关。

## 插画

新的[透明 PNG](../../../../packages/components/src/assets/settings-empty-jellyfish.png)
由内置 `image_gen` 工具生成,以现有 `lody-icon.png` 作为品牌参考。蓝绿色水母拿着
放大镜寻找空文件夹,没有内嵌文字或限定主题的背景。
[生成提示词](../../../../packages/components/src/assets/settings-empty-jellyfish.prompt.txt)
与素材一同保存。图片作为公共包的本地资源,使用空 alt 和 `aria-hidden` 标记为装饰,
相邻文字负责传达信息。

## 验证

组件类型检查、类型感知 lint、翻译键验证,以及已有记忆、MCP、Prompt Shortcut
表单测试共 17 项通过。两个桌面浏览器案例分别使用英文/亮色/1180px 与
中文/暗色/500px,覆盖六种空目录的图片加载、居中、换行、没有空卡片或内部滚动区、
已有添加弹窗,以及加载和只读状态。两个手机案例在 393×852 触屏视口下检查角色与
MCP 的亮暗主题。四个浏览器案例均通过;本地使用 Chromium build 1223,并允许
Storybook 在 30 秒内完成渲染。

`pnpm format` 通过。最新完整 `pnpm check` 的类型检查与 lint 通过,但未修改的
iOS 模拟器面板测试有一项在五秒后超时,组件测试其余 5,742 项通过。
所属 25 项测试单独运行全部通过;CLI 测试 4,423 项通过、四项跳过。
翻译和边界检查另行运行均通过,因此不能将此次全仓检查记为全绿。
文档检查保留原有六个 Kimi/Pi 子模块缺失链接,没有新增错误或保护主题变化。

## 截图

修改前后的截图以附件形式放在 [PR #1745](https://github.com/LodyAI/Lody/pull/1745),不纳入仓库。
使用真实目录组件与合成示例数据采集。桌面视口为 1180×800,截图聚焦内容区域;
手机使用正式的 `MobileSettingsLayout`,触屏视口为 393×852。两端均包含亮暗主题,
每组前后对照使用相同的示例数据和视口。
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Generator: built-in image_gen
Brand reference: lody-icon.png in this directory
Output: settings-empty-jellyfish.png, transparent background

Use case: illustration-story. Asset type: a small, polished empty-state illustration for Lody's software settings pages. The supplied image is a BRAND REFERENCE, not an image to reproduce unchanged. Generate a new original scene based on this existing jellyfish mascot: preserve its blue-to-teal dome, organic silhouette, and flowing curved tentacles. Show a single small jellyfish tilting curiously, using one tentacle to hold a simple magnifying glass as it searches an empty open folder. Gentle, quietly playful 'nothing here yet' feeling, not sad or alarmed. Refined minimal editorial illustration, softly shaded blue/teal gradients and subtle grain matching the reference. Keep the recognizable jellyfish form; do not turn it into a human or add arms/legs. Very sparse composition, centered, generous transparent margins, clear at 160 pixels, no surrounding scene, no card, no decorative frame, no floor, no background glow. True transparent background so it works on both white and dark gray UI. No words, letters, symbols, logos, watermark, UI, or text anywhere.
33 changes: 20 additions & 13 deletions packages/components/src/components/settings/compact-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { forwardRef, type ComponentProps, type ReactNode } from 'react';
import * as stylex from '@stylexjs/stylex';
import { ArrowUpRight, ChevronRight } from 'lucide-react';
import { withClassName } from '@/lib/stylex';
import emptyJellyfish from '@/assets/settings-empty-jellyfish.png';
import { settingsBoxed } from './material.stylex';
import { settingsSurface as surface } from './surface';

Expand Down Expand Up @@ -210,28 +211,34 @@ export const CompactLinkRow = forwardRef<HTMLButtonElement, CompactLinkRowProps>
);

/**
* An empty catalog, in its list's own geometry: the records card it will
* hold, with one quiet line where the records will be. No centred icon and no
* second copy of the page's add action; `action` is for the one list whose way
* in is not the page's add (installing the GitHub App).
* An empty catalog stands on the page rather than drawing an empty records
* card. The decorative mascot and centered copy explain what belongs here;
* the page keeps its add action. `action` supports a different way in, such as
* installing the GitHub App.
*/
export function SettingsEmptyList({
children,
description,
action,
}: {
children: ReactNode;
description?: ReactNode;
action?: ReactNode;
}) {
return (
<div {...stylex.props(settingsRecordsCard)}>
{action ? (
<div {...stylex.props(surface.cardNote, surface.cardNoteWithAction)}>
<span>{children}</span>
{action}
</div>
) : (
<p {...stylex.props(surface.cardNote)}>{children}</p>
)}
<div data-settings-empty-state="" {...stylex.props(surface.emptyState)}>
<img
src={emptyJellyfish}
alt=""
aria-hidden="true"
draggable={false}
width={176}
height={176}
{...stylex.props(surface.emptyIllustration)}
/>
<p {...stylex.props(surface.emptyTitle)}>{children}</p>
{description ? <p {...stylex.props(surface.emptyDescription)}>{description}</p> : null}
{action ? <div {...stylex.props(surface.emptyAction)}>{action}</div> : null}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { usePostHog } from '@posthog/react';
import { formatDistanceToNow, type Locale } from 'date-fns';
import { enUS } from 'date-fns/locale/en-US';
import { zhCN } from 'date-fns/locale/zh-CN';
import { AlertCircle, Boxes, Info, PackageOpen, RefreshCw, Search, User } from 'lucide-react';
import { AlertCircle, Boxes, Info, RefreshCw, Search, User } from 'lucide-react';
import * as stylex from '@stylexjs/stylex';
import { Spinner } from '@/ui/spinner';
import { DEFAULT_PROJECT_SKILL_DIR, type ProjectSkill, type ProjectSkillScope } from '@lody/shared';
Expand All @@ -22,6 +22,7 @@ import { Input } from '@lody/ui/input';
import { Spinner as LoadingSpinner } from '@lody/ui/spinner';
import { colors } from '@lody/ui/tokens/colors.stylex';
import { corner, radius, space } from '@lody/ui/tokens/scales.stylex';
import { SettingsEmptyList } from './compact-layout';

const MONO = 'var(--font-mono, ui-monospace, monospace)';
const TRUNCATE = {
Expand Down Expand Up @@ -281,15 +282,15 @@ export function ProjectSkillsView({
);
}
return (
<SkillsEmptyShell
icon={<PackageOpen {...stylex.props(styles.icon16, styles.hint)} />}
title={t('workspace.projects.skills.empty', 'No skills found')}
body={t('workspace.projects.skills.emptyHint', {
<SettingsEmptyList
description={t('workspace.projects.skills.emptyHint', {
defaultValue:
'Skills live in {{dir}} and other known skill directories. Add a skill there to see it here.',
dir: DEFAULT_PROJECT_SKILL_DIR,
})}
/>
>
{t('workspace.projects.skills.empty', 'No skills found')}
</SettingsEmptyList>
);
}

Expand Down
50 changes: 45 additions & 5 deletions packages/components/src/components/settings/surface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,13 +327,53 @@ export const settingsSurface = stylex.create({
lineHeight: 1.375,
color: colors.secondaryLabel,
},
/** An empty list's one line when it carries the way to fill it. */
cardNoteWithAction: {
/** Empty catalogs have no record card until there is something to list. */
emptyState: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'space-between',
gap: space[4],
paddingBlock: space[2],
justifyContent: 'center',
boxSizing: 'border-box',
minWidth: 0,
minHeight: '320px',
width: '100%',
gap: space[3],
paddingBlock: space[8],
paddingInline: space[4],
textAlign: 'center',
},
emptyIllustration: {
flexShrink: 0,
width: '176px',
height: '176px',
maxWidth: '100%',
objectFit: 'contain',
userSelect: 'none',
pointerEvents: 'none',
},
emptyTitle: {
margin: 0,
maxWidth: '44ch',
fontSize: text.bodySize,
fontWeight: 500,
lineHeight: 1.5,
color: colors.label,
overflowWrap: 'anywhere',
},
emptyDescription: {
margin: 0,
maxWidth: '48ch',
fontSize: text.footnoteSize,
lineHeight: 1.6,
color: colors.secondaryLabel,
overflowWrap: 'anywhere',
},
emptyAction: {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
gap: space[2],
paddingTop: space[1],
},
/** A row whose control cannot be used here: its name steps back with it. */
rowLabelDisabled: { color: colors.tertiaryLabel },
Expand Down
Loading
Loading