diff --git a/src/features/tech-log/presentation/studio/components/document-editor-screen.tsx b/src/features/tech-log/presentation/studio/components/document-editor-screen.tsx
index 84227e5..d383042 100644
--- a/src/features/tech-log/presentation/studio/components/document-editor-screen.tsx
+++ b/src/features/tech-log/presentation/studio/components/document-editor-screen.tsx
@@ -15,6 +15,7 @@ import { DocumentEditor } from "./document-editor.tsx";
import { GuardedStudioLink } from "./guarded-studio-link.tsx";
import { deriveValidationState } from "../../../domain/studio/document-state.ts";
import { slugFromName } from "./slug-from-name.ts";
+import { useSaveShortcut } from "./use-save-shortcut.ts";
import { useStudio, useStudioEditorSession } from "../use-studio.ts";
type CatalogEntry = components["schemas"]["CatalogEntry"];
@@ -176,6 +177,12 @@ export function DocumentEditorScreen({ documentId }: { documentId: string }) {
}
}, [begin, editor, setStatus, studio]);
+ /*
+ `save` 가 스스로 CLEAN·SAVING·CONFLICT 를 걸러 내므로 단축키는 늘 열어 둔다 — 저장할 것이
+ 없을 때 눌러도 아무 일도 일어나지 않는다.
+ */
+ useSaveShortcut(save);
+
/**
* 저장 → 검증 → 미리보기 → 게시를 한 번에 수행한다.
*
diff --git a/src/features/tech-log/presentation/studio/components/document-status-rail.tsx b/src/features/tech-log/presentation/studio/components/document-status-rail.tsx
index b08e2a1..69bd81b 100644
--- a/src/features/tech-log/presentation/studio/components/document-status-rail.tsx
+++ b/src/features/tech-log/presentation/studio/components/document-status-rail.tsx
@@ -33,7 +33,7 @@ export function DocumentStatusRail({
작업 상태
{labels[controller.status]}
저장 버전
{controller.saved.version}
종류
{kindLabels[controller.draft.kind]}
-
+
{/*
버튼은 저장과 게시 둘뿐이다. 예전에는 게시까지 검증 → 미리보기 → 게시 세 화면을 차례로
밟아야 했다 — 백엔드가 게시 요청에 신선한 검증 id 와 미리보기 id, 그리고 현재 경고를 모두
@@ -63,7 +63,7 @@ export function DocumentStatusRail({
) : controller.publishError ? (
{controller.publishError}
) : (
-
불완전한 초안도 저장할 수 있습니다. 게시를 누르면 채워야 할 칸을 그 자리에 표시합니다.
+
불완전한 초안도 저장할 수 있습니다. Ctrl+S 로도 저장합니다. 게시를 누르면 채워야 할 칸을 그 자리에 표시합니다.
)}
{unplacedIssues.length ? (
diff --git a/src/features/tech-log/presentation/studio/components/project-editor.tsx b/src/features/tech-log/presentation/studio/components/project-editor.tsx
index 59a8a4a..d4489aa 100644
--- a/src/features/tech-log/presentation/studio/components/project-editor.tsx
+++ b/src/features/tech-log/presentation/studio/components/project-editor.tsx
@@ -10,6 +10,7 @@ import { managementFailureMessage } from "../../../application/ports/management-
import { useRouteInput } from "../../../../../presentation/routes/route-input.tsx";
import { GuardedStudioLink } from "./guarded-studio-link.tsx";
import { slugFromName } from "./slug-from-name.ts";
+import { useSaveShortcut } from "./use-save-shortcut.ts";
import { useStudio } from "../use-studio.ts";
type CatalogEntry = components["schemas"]["CatalogEntry"];
@@ -211,6 +212,12 @@ export function ProjectEditor() {
}
};
+ // 폼 제출과 같은 일을 하므로 submit 이벤트를 만들어 보낸다 — 검증·기본값 처리가 한 곳에 남는다.
+ useSaveShortcut(
+ () => void save({ preventDefault: () => {} } as FormEvent),
+ Boolean(project && draft) && !pending,
+ );
+
const published = project ? project.targetVisibility !== "PRIVATE" : false;
return (
@@ -383,7 +390,7 @@ export function ProjectEditor() {