一键导入
new-feature
Scaffold a new feature following project conventions — route, component, hook, util, and logging setup
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Scaffold a new feature following project conventions — route, component, hook, util, and logging setup
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | new-feature |
| description | Scaffold a new feature following project conventions — route, component, hook, util, and logging setup |
Use this skill when creating a new feature that needs routes, components, hooks, or utils. Follow the exact patterns below — do not deviate from the project conventions.
PascalCase.tsx (e.g. MuteConvoModal.tsx)camelCase.ts prefixed with use (e.g. useMute.ts)camelCase.ts (e.g. mute.ts)mute.tsx for /convo/$convoId/mute)Every file gets a domain-specific logger as the first thing after imports:
import { createLogger } from "@/utils/log";
const log = createLogger("domain-name");
Domain name should match the file/feature (e.g. "mute-convo" for MuteConvoModal). Messages must NOT repeat the domain name.
After creating the file, add the new domain to the "Active domains" table in CLAUDE.md.
Enforced by prettier plugin. Follow this order:
react, @mantine/core, lucide-react, @xmtp/browser-sdk)@/hooks/..., @/utils/..., @/components/...)./SiblingComponent)Use import type { Foo } or import { type Foo } for type-only imports.
Always use named exports. Never use default exports.
export const MyComponent: React.FC<MyComponentProps> = ({ ... }) => { ... };
export const useMyHook = () => { ... };
export const myFunction = () => { ... };
export type MyType = { ... };
Routes live in src/routes/ and use TanStack Router file-based routing.
File: src/routes/_app/feature.tsx
import { createFileRoute, redirect } from "@tanstack/react-router";
import { MyComponent } from "@/components/feature/MyComponent";
import { getData } from "@/utils/feature";
import { createLogger } from "@/utils/log";
const log = createLogger("feature");
const FeatureRoute = () => {
log.trace("render");
const data = Route.useLoaderData();
return <MyComponent data={data} />;
};
export const Route = createFileRoute("/_app/feature")({
component: FeatureRoute,
ssr: false,
loader: async () => {
const data = await getData();
if (!data) throw redirect({ to: "/" });
return data;
},
});
File: src/routes/_app/convo/$convoId/action.tsx
import { createFileRoute } from "@tanstack/react-router";
import { useRouter } from "@tanstack/react-router";
import { ActionModal } from "@/components/modals/ActionModal";
import { createLogger } from "@/utils/log";
const log = createLogger("action");
const ActionRoute = () => {
const router = useRouter();
log.trace("render");
const onClose = () => {
log.info("closed");
router.history.back();
};
return <ActionModal opened onClose={onClose} />;
};
export const Route = createFileRoute("/_app/convo/$convoId/action")({
component: ActionRoute,
ssr: false,
});
File: src/components/modals/ActionModal.tsx
import { Button, Group, Stack, Text } from "@mantine/core";
import { useState } from "react";
import { Modal } from "@/components/shared/Modal";
import { useConvo } from "@/hooks/useConvo";
import { createLogger } from "@/utils/log";
const log = createLogger("action");
type ActionModalProps = {
opened: boolean;
onClose: () => void;
};
export const ActionModal: React.FC<ActionModalProps> = ({ opened, onClose }) => {
const { convo, conversation } = useConvo();
const [loading, setLoading] = useState(false);
log.trace("render", { convoId: convo.id });
const handleAction = async () => {
log.info("started", { convoId: convo.id });
setLoading(true);
try {
// Business logic here
log.info("succeeded", { convoId: convo.id });
onClose();
} catch (err) {
log.error("failed", err);
} finally {
setLoading(false);
}
};
return (
<Modal
opened={opened}
onClose={() => {
log.info("dismissed");
onClose();
}}
title="Action Title">
<Stack gap="md">
<Text size="sm">Description of what this action does.</Text>
<Group justify="flex-end" gap="xxs">
<Button variant="default" onClick={onClose} disabled={loading}>
Cancel
</Button>
<Button onClick={() => void handleAction()} loading={loading}>
Confirm
</Button>
</Group>
</Stack>
</Modal>
);
};
For destructive actions, use color="red" on the confirm button.
File: src/components/feature/MyComponent.tsx
import { Box, Text } from "@mantine/core";
import { createLogger } from "@/utils/log";
const log = createLogger("my-component");
type MyComponentProps = {
data: SomeType;
};
export const MyComponent: React.FC<MyComponentProps> = ({ data }) => {
log.trace("render", { id: data.id });
return (
<Box>
<Text>{data.name}</Text>
</Box>
);
};
File: src/hooks/useFeature.ts
import { useCallback, useContext } from "react";
import { SomeContext } from "@/contexts/SomeContext";
import { createLogger } from "@/utils/log";
const log = createLogger("feature");
export const useFeature = () => {
const context = useContext(SomeContext);
if (!context) {
throw new Error("useFeature must be used within SomeProvider");
}
const doAction = useCallback(async () => {
log.info("action start");
// logic
log.info("action complete");
}, []);
return { doAction };
};
File: src/utils/feature.ts
import { db, type Convo } from "@/db";
import { createLogger } from "@/utils/log";
const log = createLogger("feature");
export const getFeatureData = (id: string) => {
log.trace("getFeatureData", { id });
return db.convos.get(id);
};
export const updateFeatureData = (id: string, changes: Partial<Convo>) => {
log.trace("updateFeatureData", { id, keys: Object.keys(changes) });
return db.convos.update(id, changes);
};
Use TanStack Router's useNavigate or <Link>:
const navigate = useNavigate();
void navigate({
to: "/convo/$convoId/action",
params: { convoId: convo.id },
});
After scaffolding, verify:
createLogger with a unique domain nameopened: boolean and onClose: () => voidssr: falseconst Route = createFileRoute(...)yarn fix passes cleanyarn typecheck passes clean