---
name: atomk-desktop-sidebar
description: Manage AtomK Desktop sidebar menu — add/remove/rename/group items, i18n syncing, QuickPromptBar readonly, Prompts CRUD page
version: 1.0.0
---
# AtomK Desktop Sidebar Menu Management
Guide for adding, removing, renaming, or reordering sidebar menu items in AtomK Desktop (Electron app).
## Session-specific references
- `references/atomlisting-files-media-api.md` — Files menu placement, AtomListing `/api/v1/media/*` endpoints, storage-space UI requirements, and IPC/preload wiring pattern.
## File Map (all changes touch these files)
| File | What to change |
|------|---------------|
| `src/renderer/src/screens/Layout/Layout.tsx` | View type union, NAV_ENTRIES array, NavCollapsible logic, rendering JSX, imports |
| `src/shared/i18n/locales/{zh-CN,en}/navigation.ts` | Add/remove/rename the `key: "label"` entry |
| `src/renderer/src/screens/{ScreenName}/{ScreenName}.tsx` | New screen component (if adding) |
| `src/renderer/src/assets/main.css` | New styles (if needed) |
## Current Menu Structure (3 flat groups with dividers)
```
Group 1 — 核心: Chat / Sessions / Prompts / Skills / Tools
Group 2 — 业务: Accounts / Products / Listings / Posts / Files / Mail ← Accounts page has tabs (Stores | Social); Products/Listings/Posts/Files/Mail use AtomListing API
Group 3 — 系统: Schedules / Cloud Bridge / Settings
```
**User-specified ordering** (do not rearrange without explicit request):
- Sessions comes right after Chat in Group 1
- Accounts is the FIRST item in Group 2
- Products → Listings → Posts → Files → Mail are grouped together in Group 2
- Files belongs in the business group between Posts and Mail, not in the core group, because it connects to atomlisting.com APIs
**Business group note**: Products, Listings, Posts, Files, and Mail call `window.hermesAPI.atomListing.*` IPC → `atomlisting.ts` → `https://www.atomlisting.com`. Files must use AtomListing's media library API, not a placeholder/local file browser: `getFiles()` → IPC `atomlisting-files-list` → `atomkAPI.getFiles()` → `GET /api/v1/media/files/`; `getFileFolders()` → `GET /api/v1/media/files/folders`; `getMediaOverview()` → `GET /api/v1/media/` for storage-space/quota. Normalize common response shapes (`items`, `data`, `files`, or array) and tolerate multiple storage field names. The user wants these AtomListing-backed items grouped together.
**Important UX decision**: Accounts does NOT use a collapsible sidebar group. Instead, it navigates to a dedicated Accounts page with internal tabs for "Stores" and "Social Accounts". The user explicitly prefers: "不是在菜单里折叠 而是在右侧主体框里分两个不同区域显示".
## Nav Data Model (Layout.tsx)
The sidebar uses `NAV_GROUPS: NavGroup[]` — an array of flat groups:
```ts
interface NavItem { view: View; icon: LucideIcon; labelKey: string; }
interface NavGroup { items: NavItem[]; }
```
**Historical note**: A `NavCollapsible` type with `parentLabelKey`/`children`/`childViews`/`items` was previously used for the Accounts group, but was removed in favor of a flat "Accounts" nav item that opens a tabbed page. The `NavCollapsible` interface and `isCollapsible()` type guard still exist in Layout.tsx but are unused.
## Step-by-step: Add a new menu item (flat)
1. **Create screen component** at `src/renderer/src/screens/{Name}/{Name}.tsx`:
```tsx
import { IconName } from "lucide-react";
import { useI18n } from "../../components/useI18n";
function Name(): React.JSX.Element {
const { t } = useI18n();
return (
);
}
export default Name;
```
2. **Add View type** — append `"name"` to the `View` union type in Layout.tsx
3. **Add to NAV_ENTRIES** — for a flat item, insert into the appropriate `NavGroup`'s `items` array:
`{ view: "name", icon: IconComponent, labelKey: "navigation.name" }`
4. **Add import** — import the icon from `lucide-react` AND the screen component.
- **Icon import pitfall**: Some icons (e.g. `ChatBubble`, `Settings`, `Trash2`) are re-exported with aliases from `../../assets/icons`. Check `src/renderer/src/assets/icons/index.tsx` first — if already re-exported there, import from there. Otherwise import directly from `lucide-react`. **Never import the same icon from both paths** — duplicate identifiers cause TS errors.
5. **Add rendering block** in Layout.tsx:
```tsx
{visitedViews.has("name") && (
)}
```
6. **Update 2 locale files** — add `name: "Label"` to each `src/shared/i18n/locales/{en,zh-CN}/navigation.ts`
7. **Add CSS** if needed (use existing `.screen-container/header/body/placeholder` classes for placeholder pages)
8. **Verify**: `npx tsc --noEmit` — must be zero errors
9. **Git**: `git add -A && git commit -m "..." && git push atomk HEAD:refs/heads/main --force`
## Step-by-step: Add a collapsible group
Collapsible groups (like "账号/Accounts") contain a parent row that expands/collapses to reveal child items.
1. **Add all child View types** to the `View` union (same as flat items above)
2. **Define a NavCollapsible entry** in `NAV_ENTRIES`:
```ts
{
parentLabelKey: "navigation.accounts",
parentIcon: Users,
children: [
{ view: "stores", icon: Store, labelKey: "navigation.stores" },
{ view: "social", icon: Share2, labelKey: "navigation.social" },
],
childViews: ["stores", "social"],
// Optional: flat sibling items that share the visual group (no divider)
items: [
{ view: "products", icon: Package, labelKey: "navigation.products" },
],
} satisfies NavCollapsible,
```
3. **Rendering** — Layout.tsx iterates `NAV_ENTRIES` and uses `isCollapsible()` to switch rendering:
- Collapsible: renders `.sidebar-nav-parent` (click toggles group) + `.sidebar-nav-children` (animated expand) + optional `entry.items?.map(...)` for flat siblings below
- Flat group: renders individual `.sidebar-nav-item` buttons
4. **State** — `expandedGroups: Set` tracks which groups are open (keyed by `parentLabelKey`). Auto-expanded when navigating to a child view.
5. **CSS classes**:
- `.sidebar-nav-collapsible` — wrapper div
- `.sidebar-nav-parent` — clickable parent row (style matches `.sidebar-nav-item`)
- `.sidebar-nav-parent.has-active-child` — highlight when a child is active
- `.sidebar-nav-chevron` + `.rotated` — chevron icon rotates 90deg when expanded
- `.sidebar-nav-children` + `.open` — uses `grid-template-rows: 0fr -> 1fr` animation
- `.sidebar-nav-child` — child items with `padding-left: 44px` indentation
6. **Icon import**: `ChevronRight` comes from `lucide-react` directly (not re-exported via assets/icons).
## Step-by-step: Remove a menu item
1. Remove from `NAV_ENTRIES` array (from the relevant `NavGroup.items` or `NavCollapsible.children`)
2. Remove from `View` type union
3. Remove the rendering block (`visitedViews.has(...)` div)
4. Remove the screen component import
5. Keep the component FILE (don't delete — avoids breaking other imports)
6. Remove `key: "..."` from both `src/shared/i18n/locales/{en,zh-CN}/navigation.ts`
7. Verify + commit
## Step-by-step: Rename a menu item
1. Change the label string in both `src/shared/i18n/locales/{en,zh-CN}/navigation.ts`
2. No code changes needed if only display text changes (labelKey stays the same)
## Step-by-step: Add a group divider
Groups are defined in `NAV_ENTRIES` array. Flat `NavGroup` entries each render as one visible group. Collapsible `NavCollapsible` entries render as a single expandable group with optional flat `items` siblings. Dividers render between entries automatically via `gi > 0 && `.
To merge two groups into one: move the flat items from one `NavGroup` into the `items` field of a `NavCollapsible` entry (or into a `NavGroup` that's adjacent). This removes the divider between them.
To regroup: just move `{ view, icon, labelKey }` objects between `NavGroup` `items` arrays, or restructure as a `NavCollapsible`.
## Key Patterns
- **EN navigation label for Mail**: Use `"Mail"` NOT `"Email"`. The user explicitly corrected this. Key = `navigation.mail`.
- **Files menu placement**: `navigation.crawler` currently displays as "Files" / "文件" and must live in Group 2 between Posts and Mail. Do not leave it in Group 1. When wiring Files to AtomListing, add all three layers: `atomlisting.ts` API method, `index.ts` IPC handler, and `preload/index.ts` + `preload/index.d.ts` exposure. See `references/atomlisting-media-library.md` for current `/api/v1/media/*` routes, storage-space fields, and normalization rules.
- **QuickPromptBar** reads from `DEFAULT_QUICK_PROMPTS` + `loadCustomPrompts()` (localStorage key `atomk-custom-prompts`). Add `readonly: true` to make a prompt undeletable/uneditable.
- **Prompts management page** at `src/renderer/src/screens/Prompts/Prompts.tsx` groups by `readonly → login → workflow → context`.
- **Gateway platform badge** in sidebar: `window.hermesAPI.getPlatformEnabled(profile)` polls every 15s, renders connected platform names after the "通讯" label.
- Icons: use `lucide-react` icons. For conflicting names (e.g. `Mail`), alias: `import { Mail as MailIcon }`.
- Alias `Settings` too: `import { Settings as SettingsIcon }`.
## Git Push
Remote is `atomk` → `https://gitea9webs.sh3.ikuai7.com/admin9webs/AtomK-Desktop.git`, branch `main`.
Force push often needed: `git push atomk main --force`
Note: The old `clean-main` branch was consolidated into `main` — only `main` branch exists now on Gitea. The `gitea` remote was removed due to timeout issues.
## Pitfalls
- Don't forget BOTH locales (zh-CN, en) or you'll get runtime i18n key-not-found
- After adding a new icon import, make sure no name collision with existing imports (use aliases)
- `screen-body` default is centered — add `.prompts-body` override class if you need flex-start layout with scroll
- **Icon dual-import pitfall**: `src/renderer/src/assets/icons/index.tsx` re-exports some `lucide-react` icons with aliases (e.g. `ChatBubble = MessageSquare`, `Settings`, `Trash2 = Trash`). If an icon is there, import from `../../assets/icons`; otherwise from `lucide-react` directly. **Never both** — duplicate identifiers fail `tsc`. When in doubt, `grep` the icons/index.tsx first.
- `ChevronRight` is NOT re-exported via assets/icons — always import directly from `lucide-react`.
- **⚠️ Navigation items silently dropped during Layout.tsx edits**: When modifying Layout.tsx (e.g. removing items, reordering groups, or merging branches), it's easy to accidentally drop unrelated nav items. **Always verify after Layout.tsx changes**: compare the `NAV_GROUPS` array against `src/renderer/src/screens/` directory — every screen folder with a default-exported component should have a corresponding nav entry (unless intentionally hidden). The i18n `navigation.ts` files are a good checklist: if a key exists there but has no `NAV_GROUPS` entry, something was lost. **This pitfall has recurred at least 4 times independently** (Gateway/Messaging removal, Products/Listings/Posts/Mail, Sessions, and during group reordering). **Mandatory step**: after ANY Layout.tsx edit, run `ls src/renderer/src/screens/` and compare against `NAV_GROUPS` — if a screen folder has no nav entry, add it back.