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",
+45
View File
@@ -16,6 +16,7 @@ export type SectionId =
| "activity"
| "workspaces"
| "social"
| "skills"
| "automation"
| "communication"
| "documents"
@@ -453,6 +454,50 @@ export interface OnboardingBootstrapResult {
runId: string | null;
}
// ── Skill types ──
export type SkillSourceType = "github" | "local_path" | "url" | "skills_sh" | "managed";
export type TrustLevel = "markdown_only" | "assets" | "scripts_executables";
export interface FileInventoryEntry {
path: string;
kind: "skill" | "reference" | "script" | "asset" | "markdown" | "other";
}
export interface SkillRecord {
id: string;
companyId: string;
key: string;
slug: string;
name: string;
description: string | null;
markdown: string;
sourceType: SkillSourceType;
sourceLocator: string | null;
sourceRef: string | null;
trustLevel: TrustLevel;
compatibility: string;
fileInventory: FileInventoryEntry[];
metadata: Record<string, unknown> | null;
createdAt: string;
updatedAt: string;
}
export interface SkillImportResult {
skillId: string;
key: string;
name: string;
isUpdate: boolean;
}
export interface SkillUpdateStatus {
skillId: string;
key: string;
currentRef: string | null;
latestRef: string | null;
hasUpdate: boolean;
}
export type SocialPlatform = "twitter" | "linkedin" | "instagram" | "facebook" | "reddit" | "youtube" | "tiktok" | "bluesky" | "other";
export type SocialAccountStatus = "active" | "logged_in" | "login_required" | "suspended" | "paused";
export type BrowserActionType = "post" | "reply" | "like" | "follow" | "browse_feed" | "screenshot" | "navigate" | "search";