authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-17 21:50:40-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-18 00:59:06-08:00
log71d8f741436b69c1adc21245af6a8fd2969e467f
treed4fc54acdc6a56cc059c5a445591129e6252d559
parentdfeb673b6de6a16fefd8b34b6d56d1e71394d900

Refactor project script persistence flow in ChatView

Co-authored-by: codex <codex@users.noreply.github.com>

1 files changed, 62 insertions(+), 86 deletions(-)

apps/web/src/components/ChatView.tsx+62-86
......@@ -739,48 +739,35 @@ export default function ChatView() {
739739 },
740740 [activeProject, activeThread, activeThreadId, api, dispatch, gitCwd],
741741 );
742 const saveProjectScript = useCallback(
743 async (input: NewProjectScriptInput) => {
744 if (!activeProject) return;
745 const nextId = nextProjectScriptId(
746 input.name,
747 activeProject.scripts.map((script) => script.id),
748 );
749 const nextScript: ProjectScript = {
750 id: nextId,
751 name: input.name,
752 command: input.command,
753 icon: input.icon,
754 runOnWorktreeCreate: input.runOnWorktreeCreate,
755 };
756 const nextScripts = input.runOnWorktreeCreate
757 ? [
758 ...activeProject.scripts.map((script) =>
759 script.runOnWorktreeCreate ? { ...script, runOnWorktreeCreate: false } : script,
760 ),
761 nextScript,
762 ]
763 : [...activeProject.scripts, nextScript];
764
742 const persistProjectScripts = useCallback(
743 async (input: {
744 projectId: string;
745 projectCwd: string;
746 previousScripts: ProjectScript[];
747 nextScripts: ProjectScript[];
748 keybinding?: string | null;
749 keybindingCommand: string;
750 }) => {
765751 dispatch({
766752 type: "SET_PROJECT_SCRIPTS",
767 projectId: activeProject.id,
768 scripts: nextScripts,
753 projectId: input.projectId,
754 scripts: input.nextScripts,
769755 });
770756
771757 if (!isElectron || !api) return;
758
772759 let scriptsPersisted = false;
773760 try {
774761 const updated = await api.projects.updateScripts({
775 id: activeProject.id,
776 scripts: nextScripts,
762 id: input.projectId,
763 scripts: input.nextScripts,
777764 });
778765 scriptsPersisted = true;
779766
780767 if (input.keybinding) {
781768 const keybindingUpdate = await api.server.upsertKeybinding({
782769 key: input.keybinding,
783 command: commandForProjectScript(nextId),
770 command: input.keybindingCommand,
784771 });
785772 queryClient.setQueryData(
786773 serverQueryKeys.config(),
......@@ -788,7 +775,7 @@ export default function ChatView() {
788775 current
789776 ? { ...current, keybindings: keybindingUpdate.keybindings }
790777 : {
791 cwd: activeProject.cwd,
778 cwd: input.projectCwd,
792779 keybindings: keybindingUpdate.keybindings,
793780 },
794781 );
......@@ -803,20 +790,54 @@ export default function ChatView() {
803790 if (scriptsPersisted) {
804791 await api.projects
805792 .updateScripts({
806 id: activeProject.id,
807 scripts: activeProject.scripts,
793 id: input.projectId,
794 scripts: input.previousScripts,
808795 })
809796 .catch(() => undefined);
810797 }
811798 dispatch({
812799 type: "SET_PROJECT_SCRIPTS",
813 projectId: activeProject.id,
814 scripts: activeProject.scripts,
800 projectId: input.projectId,
801 scripts: input.previousScripts,
815802 });
816803 throw error;
817804 }
818805 },
819 [activeProject, api, dispatch, queryClient],
806 [api, dispatch, queryClient],
807 );
808 const saveProjectScript = useCallback(
809 async (input: NewProjectScriptInput) => {
810 if (!activeProject) return;
811 const nextId = nextProjectScriptId(
812 input.name,
813 activeProject.scripts.map((script) => script.id),
814 );
815 const nextScript: ProjectScript = {
816 id: nextId,
817 name: input.name,
818 command: input.command,
819 icon: input.icon,
820 runOnWorktreeCreate: input.runOnWorktreeCreate,
821 };
822 const nextScripts = input.runOnWorktreeCreate
823 ? [
824 ...activeProject.scripts.map((script) =>
825 script.runOnWorktreeCreate ? { ...script, runOnWorktreeCreate: false } : script,
826 ),
827 nextScript,
828 ]
829 : [...activeProject.scripts, nextScript];
830
831 await persistProjectScripts({
832 projectId: activeProject.id,
833 projectCwd: activeProject.cwd,
834 previousScripts: activeProject.scripts,
835 nextScripts,
836 keybinding: input.keybinding,
837 keybindingCommand: commandForProjectScript(nextId),
838 });
839 },
840 [activeProject, persistProjectScripts],
820841 );
821842 const updateProjectScript = useCallback(
822843 async (scriptId: string, input: NewProjectScriptInput) => {
......@@ -841,61 +862,16 @@ export default function ChatView() {
841862 : script,
842863 );
843864
844 dispatch({
845 type: "SET_PROJECT_SCRIPTS",
865 await persistProjectScripts({
846866 projectId: activeProject.id,
847 scripts: nextScripts,
867 projectCwd: activeProject.cwd,
868 previousScripts: activeProject.scripts,
869 nextScripts,
870 keybinding: input.keybinding,
871 keybindingCommand: commandForProjectScript(scriptId),
848872 });
849
850 if (!isElectron || !api) return;
851 let scriptsPersisted = false;
852 try {
853 const updated = await api.projects.updateScripts({
854 id: activeProject.id,
855 scripts: nextScripts,
856 });
857 scriptsPersisted = true;
858
859 if (input.keybinding) {
860 const keybindingUpdate = await api.server.upsertKeybinding({
861 key: input.keybinding,
862 command: commandForProjectScript(scriptId),
863 });
864 queryClient.setQueryData(
865 serverQueryKeys.config(),
866 (current: { cwd: string; keybindings: ResolvedKeybindingsConfig } | undefined) =>
867 current
868 ? { ...current, keybindings: keybindingUpdate.keybindings }
869 : {
870 cwd: activeProject.cwd,
871 keybindings: keybindingUpdate.keybindings,
872 },
873 );
874 }
875
876 dispatch({
877 type: "SET_PROJECT_SCRIPTS",
878 projectId: updated.project.id,
879 scripts: updated.project.scripts,
880 });
881 } catch (error) {
882 if (scriptsPersisted) {
883 await api.projects
884 .updateScripts({
885 id: activeProject.id,
886 scripts: activeProject.scripts,
887 })
888 .catch(() => undefined);
889 }
890 dispatch({
891 type: "SET_PROJECT_SCRIPTS",
892 projectId: activeProject.id,
893 scripts: activeProject.scripts,
894 });
895 throw error;
896 }
897873 },
898 [activeProject, api, dispatch, queryClient],
874 [activeProject, persistProjectScripts],
899875 );
900876
901877 const handleRuntimeModeChange = async (mode: "approval-required" | "full-access") => {