feat(03-01): add skill section type wiring, Zustand slice, i18n, and sidebar nav entry

- Add "skills" to SectionId union in shared types
- Add SkillRecord, SkillImportResult, SkillUpdateStatus types to shared/types.ts
- Create SkillSlice with skills list, selection, file path, and import modal state
- Wire SkillSlice into Zustand store composition
- Add "nav.skills" i18n keys for English and Chinese
- Add Skills sidebar nav entry with PuzzlePiece icon between Communication and Documents
- Add section === "skills" rendering route in App.tsx
- Clear skill state on company switch
- Add skills case to navigateTo in NavigationSlice

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 4.6 committed 2026-03-26 01:45:32 -04:00
1 parent c1de05a7f1
commit e8d642941f
9 files changed
+121 -1

No files matched your search

+11
View File
@@ -14,6 +14,7 @@ import {
MagnifyingGlass,
Megaphone,
Plus,
PuzzlePiece,
UsersThree,
ShieldCheck,
Trash,
@@ -75,6 +76,7 @@ import { SocialAccountsPanel } from "./components/social";
import { AutomationPanel } from "./components/automation";
import { CommunicationHub } from "./components/communication";
import { HiringPipeline } from "./components/hiring";
import { SkillsSection } from "./components/skills";
import { DocumentCenter, KnowledgeBaseManager, MeetingsManager, SprintManager } from "./components/company-operations";
import { ActionButton, ErrorBoundary, ListFrame, ListRow, Modal, Panel, SectionButton, Select, StatusPill, useFocusTrap, useReducedMotion } from "./components/ui";
import ToastContainer from "./components/Toast";
@@ -94,6 +96,7 @@ const sidebarNavDefs: NavItemDef[] = [
{ id: "agents", labelKey: "nav.agents", icon: UsersThree },
{ id: "tasks", labelKey: "nav.work", icon: ListChecks },
{ id: "communication", labelKey: "nav.communication", icon: ChatCircle },
{ id: "skills", labelKey: "nav.skills", icon: PuzzlePiece },
{ id: "documents", labelKey: "nav.documents", icon: FileText },
{ id: "approvals", labelKey: "nav.approvals", icon: ShieldCheck },
{ id: "social", labelKey: "nav.social", icon: Megaphone },
@@ -181,6 +184,10 @@ export function App() {
store.setSocialAccounts([]);
store.setBrowserActions([]);
store.setBadges(null);
store.setSkills([]);
store.setSelectedSkillId(null);
store.setSelectedSkillFilePath("SKILL.md");
store.setSkillImportModalOpen(false);
}, []);
const refresh = useCallback(async () => {
@@ -1193,6 +1200,10 @@ export function App() {
/>
)}
{section === "skills" && currentCompany ? (
<SkillsSection companyId={currentCompany.id} agents={companyAgents} onRefresh={refresh} />
) : null}
{section === "automation" && currentCompany ? (
<AutomationPanel
key={currentCompany.id}
@@ -0,0 +1,15 @@
import type { AgentRecord } from "@shared/types";
interface SkillsSectionProps {
companyId: string;
agents: AgentRecord[];
onRefresh: () => void;
}
export function SkillsSection({ companyId: _companyId, agents: _agents, onRefresh: _onRefresh }: SkillsSectionProps) {
return (
<div className="h-full">
<p className="text-[color:var(--muted)]">Skills section placeholder -- Task 2</p>
</div>
);
}
+1
View File
@@ -0,0 +1 @@
export { SkillsSection } from "./SkillsSection";
+1
View File
@@ -22,6 +22,7 @@ export const en: Record<string, string> = {
"nav.costs": "Costs",
"nav.activity": "Activity",
"nav.social": "Social Media",
"nav.skills": "Skills",
"nav.connectors": "Connectors",
"nav.workspaces": "Workspaces",
"nav.settings": "Settings",
+1
View File
@@ -22,6 +22,7 @@ export const zh: Record<string, string> = {
"nav.costs": "成本",
"nav.activity": "活动",
"nav.social": "社交媒体",
"nav.skills": "技能",
"nav.connectors": "连接器",
"nav.workspaces": "工作区",
"nav.settings": "设置",
+1
View File
@@ -105,6 +105,7 @@ export const createNavigationSlice: StateCreator<
else if (section === "connectors") state.selectedConnectorId = entityId;
else if (section === "communication")
state.selectedCommunicationEntityId = entityId;
else if (section === "skills") state.selectedSkillId = entityId;
}
}),
});
+42
View File
@@ -0,0 +1,42 @@
import type { StateCreator } from "zustand";
import type { AppState } from "../store";
import type { SkillRecord } from "@shared/types";
export interface SkillSlice {
skills: SkillRecord[];
setSkills: (skills: SkillRecord[]) => void;
selectedSkillId: string | null;
setSelectedSkillId: (id: string | null) => void;
selectedSkillFilePath: string;
setSelectedSkillFilePath: (path: string) => void;
skillImportModalOpen: boolean;
setSkillImportModalOpen: (open: boolean) => void;
}
export const createSkillSlice: StateCreator<
AppState,
[["zustand/immer", never]],
[],
SkillSlice
> = (set) => ({
skills: [],
setSkills: (skills) =>
set((state) => {
state.skills = skills;
}),
selectedSkillId: null,
setSelectedSkillId: (id) =>
set((state) => {
state.selectedSkillId = id;
}),
selectedSkillFilePath: "SKILL.md",
setSelectedSkillFilePath: (path) =>
set((state) => {
state.selectedSkillFilePath = path;
}),
skillImportModalOpen: false,
setSkillImportModalOpen: (open) =>
set((state) => {
state.skillImportModalOpen = open;
}),
});
+4 -1
View File
@@ -16,13 +16,15 @@ import {
type CommunicationSlice,
} from "./slices/communication";
import { createSocialSlice, type SocialSlice } from "./slices/social";
import { createSkillSlice, type SkillSlice } from "./slices/skill";
export type AppState = NavigationSlice &
CompanySlice &
SelectionSlice &
UiSlice &
CommunicationSlice &
SocialSlice;
SocialSlice &
SkillSlice;
export const useAppStore = create<AppState>()(
devtools(
@@ -33,6 +35,7 @@ export const useAppStore = create<AppState>()(
...createUiSlice(...a),
...createCommunicationSlice(...a),
...createSocialSlice(...a),
...createSkillSlice(...a),
})),
{
name: "AgentCompany",