refactor(config): make EditorState/ConfirmDeleteState generic over the artifact type

Review feedback: string widening dropped the compile-time guard at the
write/delete boundary right where the Codex panel (the next task) starts
writing into this state. Threading <TType extends string = CcArtifactType>
through EditorState/EditorModal and ConfirmDeleteState/ConfirmDeleteModal
keeps CcConfig.tsx's write/delete calls checked against CcArtifactType by
default while still letting a Codex-typed caller instantiate its own union
later, removing the two `as CcArtifactType` casts.
This commit is contained in:
2026-09-08 20:48:58 +07:00
parent 865c6c1d1e
commit f6973d24d6
3 changed files with 26 additions and 15 deletions
@@ -9,22 +9,27 @@
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ShieldAlert, Trash2 } from "lucide-react"; import { ShieldAlert, Trash2 } from "lucide-react";
import type { CcArtifactType } from "../../lib/api";
export type ConfirmDeleteState = { export type ConfirmDeleteState<TType extends string = CcArtifactType> = {
type: string; type: TType;
scope: "user" | "project" | "auto-memory"; scope: "user" | "project" | "auto-memory";
name?: string; name?: string;
path: string; path: string;
project?: string; // set for type === "auto-memory" project?: string; // set for type === "auto-memory"
} | null; } | null;
interface ConfirmDeleteModalProps { interface ConfirmDeleteModalProps<TType extends string = CcArtifactType> {
state: NonNullable<ConfirmDeleteState>; state: NonNullable<ConfirmDeleteState<TType>>;
onCancel: () => void; onCancel: () => void;
onConfirm: () => Promise<void>; onConfirm: () => Promise<void>;
} }
export function ConfirmDeleteModal({ state, onCancel, onConfirm }: ConfirmDeleteModalProps) { export function ConfirmDeleteModal<TType extends string = CcArtifactType>({
state,
onCancel,
onConfirm,
}: ConfirmDeleteModalProps<TType>) {
const { t } = useTranslation("ccConfig"); const { t } = useTranslation("ccConfig");
const [busy, setBusy] = useState(false); const [busy, setBusy] = useState(false);
+13 -7
View File
@@ -10,18 +10,19 @@
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Pencil, X, Save, AlertCircle } from "lucide-react"; import { Pencil, X, Save, AlertCircle } from "lucide-react";
import type { CcArtifactType } from "../../lib/api";
export type EditorState = export type EditorState<TType extends string = CcArtifactType> =
| { | {
mode: "create"; mode: "create";
type: string; type: TType;
defaultScope: "user" | "project"; defaultScope: "user" | "project";
template: string; template: string;
project?: string; // set for type === "auto-memory" project?: string; // set for type === "auto-memory"
} }
| { | {
mode: "edit"; mode: "edit";
type: string; type: TType;
scope: "user" | "project" | "auto-memory"; scope: "user" | "project" | "auto-memory";
name: string; name: string;
filePath: string; filePath: string;
@@ -29,12 +30,12 @@ export type EditorState =
} }
| null; | null;
interface EditorModalProps { interface EditorModalProps<TType extends string = CcArtifactType> {
state: NonNullable<EditorState>; state: NonNullable<EditorState<TType>>;
fetchContent: (filePath: string) => Promise<string>; fetchContent: (filePath: string) => Promise<string>;
onClose: () => void; onClose: () => void;
onSave: (args: { onSave: (args: {
type: string; type: TType;
targetScope: "user" | "project" | "auto-memory"; targetScope: "user" | "project" | "auto-memory";
name: string | undefined; name: string | undefined;
content: string; content: string;
@@ -42,7 +43,12 @@ interface EditorModalProps {
}) => Promise<void>; }) => Promise<void>;
} }
export function EditorModal({ state, fetchContent, onClose, onSave }: EditorModalProps) { export function EditorModal<TType extends string = CcArtifactType>({
state,
fetchContent,
onClose,
onSave,
}: EditorModalProps<TType>) {
const { t } = useTranslation("ccConfig"); const { t } = useTranslation("ccConfig");
const isCreate = state.mode === "create"; const isCreate = state.mode === "create";
const isAutoMemory = state.type === "auto-memory"; const isAutoMemory = state.type === "auto-memory";
+3 -3
View File
@@ -426,7 +426,7 @@ export function CcConfig() {
const handleSave = useCallback( const handleSave = useCallback(
async (args: { async (args: {
type: string; type: CcArtifactType;
targetScope: "user" | "project" | "auto-memory"; targetScope: "user" | "project" | "auto-memory";
name: string | undefined; name: string | undefined;
content: string; content: string;
@@ -434,7 +434,7 @@ export function CcConfig() {
}) => { }) => {
const result: CcMutationResult = await api.ccConfig.write({ const result: CcMutationResult = await api.ccConfig.write({
scope: args.targetScope, scope: args.targetScope,
type: args.type as CcArtifactType, type: args.type,
name: args.name, name: args.name,
content: args.content, content: args.content,
project: args.project, project: args.project,
@@ -456,7 +456,7 @@ export function CcConfig() {
try { try {
const result = await api.ccConfig.delete({ const result = await api.ccConfig.delete({
scope: confirmDelete.scope, scope: confirmDelete.scope,
type: confirmDelete.type as CcArtifactType, type: confirmDelete.type,
name: confirmDelete.name, name: confirmDelete.name,
project: confirmDelete.project, project: confirmDelete.project,
}); });