From 69d5dd8ee28c3e335e1a249443e5db64f753144f Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 7 Aug 2026 02:10:37 +0000
Subject: [PATCH 1/2] Add keyboard submission support for project and
connection forms
---
.jules/palette.md | 3 +++
frontend/src/App.tsx | 24 ++++++++++++------------
2 files changed, 15 insertions(+), 12 deletions(-)
diff --git a/.jules/palette.md b/.jules/palette.md
index bd0f7324..e47e36f7 100644
--- a/.jules/palette.md
+++ b/.jules/palette.md
@@ -57,3 +57,6 @@
## 2026-07-30 - Add window.confirm for destructive actions
**Learning:** Destructive actions like deleting groups and edge relationships previously occurred immediately without user confirmation.
**Action:** Always wrap delete operations with window.confirm() dialogs and ensure corresponding tests successfully mock window.confirm.
+## 2024-08-07 - Add Keyboard Submission to Inline Forms
+**Learning:** For components that contain input fields combined with submission buttons, they often omit a wrapper form element causing Enter-to-submit to fail. Wrapping these generic groupings (like 'New Project' and 'New Connection' creation elements) in standard `
@@ -1309,20 +1307,22 @@ export default function App() {
From 3ee4f5cdbadfefb43d1b0d8fc3fbf2c1777f1d7d Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 7 Aug 2026 03:06:49 +0000
Subject: [PATCH 2/2] Add keyboard submission support for project and
connection forms
---
.jules/palette.md | 3 ---
CHANGELOG.md | 1 +
frontend/src/App.coverage.test.tsx | 10 +++++-----
frontend/src/App.tsx | 12 +++++++-----
4 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/.jules/palette.md b/.jules/palette.md
index e47e36f7..bd0f7324 100644
--- a/.jules/palette.md
+++ b/.jules/palette.md
@@ -57,6 +57,3 @@
## 2026-07-30 - Add window.confirm for destructive actions
**Learning:** Destructive actions like deleting groups and edge relationships previously occurred immediately without user confirmation.
**Action:** Always wrap delete operations with window.confirm() dialogs and ensure corresponding tests successfully mock window.confirm.
-## 2024-08-07 - Add Keyboard Submission to Inline Forms
-**Learning:** For components that contain input fields combined with submission buttons, they often omit a wrapper form element causing Enter-to-submit to fail. Wrapping these generic groupings (like 'New Project' and 'New Connection' creation elements) in standard `
` elements handles native implicit submission smoothly without requiring manual key listeners.
-**Action:** When inspecting generic inline create elements containing an input and submit button, always ensure they are wrapped in a `
` and handle `onSubmit` (preventing default) instead of relying solely on the submit button's `onClick`.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 679a6202..3b7d2b4e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,3 +11,4 @@
- [FE] `autoInfer.ts`에 대한 단위 테스트 및 UI 컴포넌트 단위 테스트를 추가하여 100% 테스트 커버리지를 유지합니다.
- [FE] ⬇️ **DBML Export**: ERD 다이어그램을 DBML (Database Markup Language) 형식으로 내보낼 수 있는 기능을 추가했습니다. 상단의 DBML 버튼을 클릭하여 다운로드할 수 있습니다.
- [FE] 📚 **Data Dictionary Export**: ERD 테이블/컬럼 메타데이터를 CSV 및 Markdown으로 내보내며, CSV formula injection과 Markdown 렌더링 escape를 적용했습니다.
+- [FE] 🎨 **프로젝트 및 연결 생성 폼 엔터키 제출 지원**: 폼(`div` -> `form`) 요소를 통해 네이티브 HTML form 제출(Enter 키 동작)과 웹 접근성(WCAG 키보드 조작)을 개선했습니다.
diff --git a/frontend/src/App.coverage.test.tsx b/frontend/src/App.coverage.test.tsx
index 0b9a20aa..b7e554ec 100644
--- a/frontend/src/App.coverage.test.tsx
+++ b/frontend/src/App.coverage.test.tsx
@@ -347,20 +347,20 @@ describe('App orchestration coverage', () => {
fireEvent.click(screen.getByRole('button', { name: '편집기' }))
fireEvent.change(screen.getByLabelText('New project'), { target: { value: ' New ' } })
- fireEvent.click(screen.getByRole('button', { name: 'Create' }))
+ fireEvent.submit(screen.getByLabelText('New project').closest('form')!)
await waitFor(() => expect(api.createProject).toHaveBeenCalledWith('New'))
const dsn = screen.getByLabelText('Connection DSN')
fireEvent.change(dsn, { target: { value: 'postgresql://[' } })
- fireEvent.click(screen.getByRole('button', { name: 'Save connection' }))
+ fireEvent.submit(screen.getByLabelText('Connection DSN').closest('form')!)
expect(screen.getByRole('alert')).toHaveTextContent('Connection DSN must use')
fireEvent.change(dsn, { target: { value: 'http://bad.example/db' } })
- fireEvent.click(screen.getByRole('button', { name: 'Save connection' }))
+ fireEvent.submit(screen.getByLabelText('Connection DSN').closest('form')!)
expect(screen.getByRole('alert')).toHaveTextContent('Connection DSN must use')
expect(dsn).toHaveValue('')
fireEvent.change(dsn, { target: { value: 'postgresql://db.example/test' } })
- fireEvent.click(screen.getByRole('button', { name: 'Save connection' }))
+ fireEvent.submit(screen.getByLabelText('Connection DSN').closest('form')!)
await waitFor(() => expect(api.createConnection).toHaveBeenCalledWith('p3', 'target-db', 'postgresql://db.example/test'))
fireEvent.change(screen.getByLabelText('Schema filter (optional)'), { target: { value: ' public ' } })
@@ -682,7 +682,7 @@ describe('App orchestration coverage', () => {
await renderReadyApp()
fireEvent.click(screen.getByRole('button', { name: '편집기' }))
fireEvent.change(screen.getByLabelText('Connection DSN'), { target: { value: 'postgresql://db.example/test' } })
- fireEvent.click(screen.getByRole('button', { name: 'Save connection' }))
+ fireEvent.submit(screen.getByLabelText('Connection DSN').closest('form')!)
await waitFor(() => expect(api.createConnection).toHaveBeenCalled())
fireEvent.click(screen.getByRole('button', { name: 'Reverse engineer → snapshot' }))
await waitFor(() => expect(api.createSnapshot).toHaveBeenCalledWith('p1', 'c2', undefined))
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 42102eb9..cfaa34ee 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -939,7 +939,8 @@ export default function App() {
setLayoutMessage("되돌렸습니다");
}
- async function onCreateProject() {
+ async function handleProjectSubmit(e?: React.FormEvent) {
+ e?.preventDefault();
const nextProjectName = projectName.trim();
/* v8 ignore next -- the create control is disabled for both guard states */
if (!nextProjectName || isCreatingProject) return;
@@ -954,7 +955,8 @@ export default function App() {
}
}
- async function onCreateConnection() {
+ async function handleConnectionSubmit(e?: React.FormEvent) {
+ e?.preventDefault();
/* v8 ignore next -- the save control is disabled without a project or while saving */
if (!selectedProjectId || isCreatingConnection) return;
const nextConnectionName = connName.trim();
@@ -1078,7 +1080,7 @@ export default function App() {