From e6e3c93f9f303153de844089e2d9e4c47413cb65 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Wed, 2 Sep 2026 21:33:36 +0000
Subject: [PATCH 1/9] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20[=EC=A0=91?=
=?UTF-8?q?=EA=B7=BC=EC=84=B1=20=EA=B0=9C=EC=84=A0]=20=EB=82=B4=EB=B3=B4?=
=?UTF-8?q?=EB=82=B4=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20aria-describedby=20?=
=?UTF-8?q?=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
๐ก What: ExportModal์ ๋ด๋ณด๋ด๊ธฐ ๋ฒํผ๋ค์ `aria-describedby` ์์ฑ์ ์ถ๊ฐํ์ฌ ์์ ์๋ ์ค๋ช
ํ
์คํธ๋ฅผ ์ฐธ์กฐํ๋๋ก ๊ฐ์ ํ์ต๋๋ค.
๐ฏ Why: ๋ฒํผ์ด ๋นํ์ฑํ๋์์ ๋(์: "๋จผ์ ํ
์ด๋ธ์ ์ถ๊ฐํ์ธ์"), ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ฌ์ฉ์๊ฐ ์ ๋ฒํผ์ ๋๋ฅผ ์ ์๋์ง ์ด์ ๋ฅผ ๋ช
ํํ๊ฒ ์ ์ ์๋๋ก ๋งฅ๋ฝ์ ์ ๊ณตํ๊ธฐ ์ํจ์
๋๋ค.
๐ธ Before/After:
- Before: ``
- After: `๋จผ์ ํ
์ด๋ธ์ ์ถ๊ฐํ์ธ์ `
โฟ Accessibility: ๋นํ์ฑํ๋ ์ปจํธ๋กค์ ๋ํ ๋ช
ํํ ์ฌ์ ๋ฅผ ๋ณด์กฐ ๊ธฐ๊ธฐ์ ์ ๋ฌํ์ฌ WCAG์ ํผ ์ปจํธ๋กค ์ง์นจ๊ณผ ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ฌ์ฉ์ฑ์ ํฅ์์์ผฐ์ต๋๋ค.
---
.jules/palette.md | 4 +++
.../src/components/modals/ExportModal.tsx | 36 ++++++++++---------
2 files changed, 24 insertions(+), 16 deletions(-)
diff --git a/.jules/palette.md b/.jules/palette.md
index bd0f73248..fcd2da1c1 100644
--- a/.jules/palette.md
+++ b/.jules/palette.md
@@ -57,3 +57,7 @@
## 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.
+
+## 2026-10-25 - ๋ด๋ณด๋ด๊ธฐ ๋ชจ๋ฌ ๋ฒํผ ์ ๊ทผ์ฑ ๊ฐ์
+**Learning:** ExportModal์ ๋ด๋ณด๋ด๊ธฐ ๋ฒํผ์ด ๋นํ์ฑํ๋์์ ๋, ์๊ฐ์ ์ผ๋ก๋ "๋จผ์ ํ
์ด๋ธ์ ์ถ๊ฐํ์ธ์"๋ผ๋ ์ค๋ช
์ด ๋ณด์ด์ง๋ง ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ฌ์ฉ์์๊ฒ๋ ์ด ๋งฅ๋ฝ์ด ์ ๋ฌ๋์ง ์์ ์ ๋นํ์ฑํ๋์๋์ง ์ ์ ์๋ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
+**Action:** ๋นํ์ฑํ๋ ์ ์๋ ๋ฒํผ ๊ณ์ ์๋ ์ค๋ช
ํ
์คํธ ์์์ id๋ฅผ ๋ถ์ฌํ๊ณ , ๋ฒํผ์ `aria-describedby`๋ฅผ ์ถ๊ฐํ์ฌ ์คํฌ๋ฆฐ ๋ฆฌ๋๊ฐ ๋นํ์ฑํ ์ฌ์ ๋ฅผ ์ฝ์ ์ ์๋๋ก ์ฐ๊ฒฐํด์ผ ํฉ๋๋ค.
diff --git a/frontend/src/components/modals/ExportModal.tsx b/frontend/src/components/modals/ExportModal.tsx
index 995393ceb..aff876900 100644
--- a/frontend/src/components/modals/ExportModal.tsx
+++ b/frontend/src/components/modals/ExportModal.tsx
@@ -222,23 +222,27 @@ export function ExportModal({
- {artifacts.map((artifact) => (
-
-
-
{artifact.label}
-
{artifact.description}
+ {artifacts.map((artifact) => {
+ const descId = `export-artifact-desc-${artifact.label.replace(/\s+/g, '-')}`;
+ return (
+
+
+ {artifact.label}
+ {artifact.description}
+
+
-
-
- ))}
+ );
+ })}
From 71e01947d11ec9f6bc888e520a0c48e2aa0b6566 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Mon, 7 Sep 2026 10:58:54 +0900
Subject: [PATCH 2/9] fix(docs): remove future-dated unverified export doctrine
---
.jules/palette.md | 4 ----
1 file changed, 4 deletions(-)
diff --git a/.jules/palette.md b/.jules/palette.md
index fcd2da1c1..bd0f73248 100644
--- a/.jules/palette.md
+++ b/.jules/palette.md
@@ -57,7 +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.
-
-## 2026-10-25 - ๋ด๋ณด๋ด๊ธฐ ๋ชจ๋ฌ ๋ฒํผ ์ ๊ทผ์ฑ ๊ฐ์
-**Learning:** ExportModal์ ๋ด๋ณด๋ด๊ธฐ ๋ฒํผ์ด ๋นํ์ฑํ๋์์ ๋, ์๊ฐ์ ์ผ๋ก๋ "๋จผ์ ํ
์ด๋ธ์ ์ถ๊ฐํ์ธ์"๋ผ๋ ์ค๋ช
์ด ๋ณด์ด์ง๋ง ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ฌ์ฉ์์๊ฒ๋ ์ด ๋งฅ๋ฝ์ด ์ ๋ฌ๋์ง ์์ ์ ๋นํ์ฑํ๋์๋์ง ์ ์ ์๋ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
-**Action:** ๋นํ์ฑํ๋ ์ ์๋ ๋ฒํผ ๊ณ์ ์๋ ์ค๋ช
ํ
์คํธ ์์์ id๋ฅผ ๋ถ์ฌํ๊ณ , ๋ฒํผ์ `aria-describedby`๋ฅผ ์ถ๊ฐํ์ฌ ์คํฌ๋ฆฐ ๋ฆฌ๋๊ฐ ๋นํ์ฑํ ์ฌ์ ๋ฅผ ์ฝ์ ์ ์๋๋ก ์ฐ๊ฒฐํด์ผ ํฉ๋๋ค.
From 5f7bbac24801147e0b00aad3653a1edf903f338c Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Mon, 7 Sep 2026 11:08:07 +0900
Subject: [PATCH 3/9] test(a11y): verify export buttons expose disabled reasons
---
.../components/modals/ExportModal.test.tsx | 30 +++++++++++++------
1 file changed, 21 insertions(+), 9 deletions(-)
diff --git a/frontend/src/components/modals/ExportModal.test.tsx b/frontend/src/components/modals/ExportModal.test.tsx
index bd89c5384..71ea63f5b 100644
--- a/frontend/src/components/modals/ExportModal.test.tsx
+++ b/frontend/src/components/modals/ExportModal.test.tsx
@@ -161,14 +161,26 @@ describe('ExportModal', () => {
);
expect(screen.getAllByText('๋จผ์ ํ
์ด๋ธ์ ์ถ๊ฐํ์ธ์')).toHaveLength(8);
- expect(screen.getByRole('button', { name: 'SQL DDL ๋ณต์ฌ' })).toBeDisabled();
- expect(screen.getByRole('button', { name: 'SVG ์ด๋ฏธ์ง ๋ด๋ณด๋ด๊ธฐ' })).toBeDisabled();
- expect(screen.getByRole('button', { name: 'PlantUML ๋ด๋ณด๋ด๊ธฐ' })).toBeDisabled();
- expect(screen.getByRole('button', { name: 'Mermaid ๋ด๋ณด๋ด๊ธฐ' })).toBeDisabled();
- expect(screen.getByRole('button', { name: 'DBML ๋ด๋ณด๋ด๊ธฐ' })).toBeDisabled();
- expect(screen.getByRole('button', { name: 'Prisma Schema ๋ด๋ณด๋ด๊ธฐ' })).toBeDisabled();
- expect(screen.getByRole('button', { name: '๋ฐ์ดํฐ ์ฌ์ CSV ๋ด๋ณด๋ด๊ธฐ' })).toBeDisabled();
- expect(screen.getByRole('button', { name: '๋ฐ์ดํฐ ์ฌ์ Markdown ๋ด๋ณด๋ด๊ธฐ' })).toBeDisabled();
+ const exportButtonNames = [
+ 'SQL DDL ๋ณต์ฌ',
+ 'SVG ์ด๋ฏธ์ง ๋ด๋ณด๋ด๊ธฐ',
+ 'PlantUML ๋ด๋ณด๋ด๊ธฐ',
+ 'Mermaid ๋ด๋ณด๋ด๊ธฐ',
+ 'DBML ๋ด๋ณด๋ด๊ธฐ',
+ 'Prisma Schema ๋ด๋ณด๋ด๊ธฐ',
+ '๋ฐ์ดํฐ ์ฌ์ CSV ๋ด๋ณด๋ด๊ธฐ',
+ '๋ฐ์ดํฐ ์ฌ์ Markdown ๋ด๋ณด๋ด๊ธฐ',
+ ];
+
+ for (const name of exportButtonNames) {
+ const button = screen.getByRole('button', { name });
+ expect(button).toBeDisabled();
+ const descriptionId = button.getAttribute('aria-describedby');
+ expect(descriptionId).toBeTruthy();
+ expect(document.getElementById(descriptionId!)).toHaveTextContent(
+ '๋จผ์ ํ
์ด๋ธ์ ์ถ๊ฐํ์ธ์',
+ );
+ }
});
it('exposes access-control guidance for disabled button', () => {
@@ -180,4 +192,4 @@ describe('ExportModal', () => {
expect(accessManagementButton).toHaveAttribute('aria-describedby', 'share-export-access-hint');
expect(accessManagementButton).not.toHaveAttribute('title');
});
-});
+});
\ No newline at end of file
From aa934156fa5bbe66ae04e93fe69396ed6b219ae9 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Mon, 7 Sep 2026 11:09:01 +0900
Subject: [PATCH 4/9] test(a11y): cover enabled export descriptions
---
frontend/src/components/modals/ExportModal.test.tsx | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/frontend/src/components/modals/ExportModal.test.tsx b/frontend/src/components/modals/ExportModal.test.tsx
index 71ea63f5b..52754d3c7 100644
--- a/frontend/src/components/modals/ExportModal.test.tsx
+++ b/frontend/src/components/modals/ExportModal.test.tsx
@@ -183,6 +183,16 @@ describe('ExportModal', () => {
}
});
+ it('describes enabled exports with their artifact context', () => {
+ render();
+
+ const ddlButton = screen.getByRole('button', { name: 'SQL DDL ๋ณต์ฌ' });
+ expect(ddlButton).toBeEnabled();
+ const descriptionId = ddlButton.getAttribute('aria-describedby');
+ expect(descriptionId).toBeTruthy();
+ expect(document.getElementById(descriptionId!)).toHaveTextContent('์คํค๋ง ํ
์คํธ');
+ });
+
it('exposes access-control guidance for disabled button', () => {
render();
From 0118deeba43d61b66f226e90e436bd1ff8e6c4c9 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Mon, 7 Sep 2026 11:09:13 +0900
Subject: [PATCH 5/9] docs(frontend): record export accessibility descriptions
---
frontend/CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/frontend/CHANGELOG.md b/frontend/CHANGELOG.md
index c78944c31..41ba2e26e 100644
--- a/frontend/CHANGELOG.md
+++ b/frontend/CHANGELOG.md
@@ -2,6 +2,7 @@
## [Unreleased]
### Added
+- **๋ด๋ณด๋ด๊ธฐ ์ ๊ทผ์ฑ ์ค๋ช
์ฐ๊ฒฐ**: ExportModal์ ๊ฐ ์ฐ์ถ๋ฌผ ๋ฒํผ์ ๋์ ์ค๋ช
๊ณผ `aria-describedby`๋ก ์ฐ๊ฒฐํ์ฌ ํ์ฑ ์ํ์ ์ฐ์ถ๋ฌผ ๋งฅ๋ฝ๊ณผ ๋นํ์ฑ ์ํ์ ์์ฑ ๋ถ๊ฐ ์ฌ์ ๋ฅผ ๋ณด์กฐ ๊ธฐ์ ์ ํจ๊ป ์ ๊ณตํฉ๋๋ค.
- **ํ
์ด๋ธ ๋ฐ ์ปฌ๋ผ ํธ์ง ๊ธฐ๋ฅ**: UI ํจ๋์ ํตํด ๋
ธ๋๋ฅผ ์ ํํ๊ณ , ํ
์ด๋ธ์ ์ด๋ฆ/์ฝ๋ฉํธ๋ฅผ ์์ ํ๋ฉฐ, ์ปฌ๋ผ์ ์ถ๊ฐ/์์ /์ญ์ ํ๊ฑฐ๋ ํ
์ด๋ธ์ ์ญ์ ํ ์ ์๋ ๊ธฐ๋ฅ ์ถ๊ฐ.
- **ํ
์คํธ ์ถ๊ฐ**: ํ๋ก ํธ์๋ ํ
์คํธ ์ปค๋ฒ๋ฆฌ์ง 100% ๋ชฉํ ๋ฌ์ฑ์ ์ํด `cardinality.ts`, `types.ts`, `export.ts` ์ ๋ฏธ๋ฌ์ฑ ๋ถ๊ธฐ ๋ฐ ํจ์ ํ
์คํธ ์ถ๊ฐ (`cardinality_extra.test.ts` ๋ฑ).
- `.gitignore` ํ์ผ์ `coverage/` ํด๋๋ฅผ ์ถ๊ฐํ์ฌ ๋ถํ์ํ ํ
์คํธ ์ํฐํฉํธ๊ฐ ์ปค๋ฐ๋์ง ์๋๋ก ๋ณด์.
From c2fdfd8b06cab86f684d8ae4542b4eb6a9779103 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Mon, 7 Sep 2026 11:09:28 +0900
Subject: [PATCH 6/9] docs(changelog): record export a11y descriptions
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 35613431a..512bb689c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
# Changelog
## Unreleased
+- [FE] โฟ **๋ด๋ณด๋ด๊ธฐ ๋ฒํผ ์ค๋ช
์ฐ๊ฒฐ**: ExportModal์ ๊ฐ ์ฐ์ถ๋ฌผ ๋ฒํผ์ ๋์ ์ค๋ช
๊ณผ `aria-describedby`๋ก ์ฐ๊ฒฐํด ํ์ฑ ์ํ์์๋ ์ฐ์ถ๋ฌผ ๋งฅ๋ฝ์, ๋นํ์ฑ ์ํ์์๋ ์์ฑ ๋ถ๊ฐ ์ฌ์ ๋ฅผ ์คํฌ๋ฆฐ ๋ฆฌ๋๊ฐ ํจ๊ป ์ฝ์ ์ ์๋๋ก ํ์ต๋๋ค. ์ง์ค ํ
์คํธ๊ฐ ์ค์ ์ค๋ช
`id` ์ฐ๊ฒฐ์ ํ์ฑ/๋นํ์ฑ ์ํ์์ ๊ฒ์ฆํฉ๋๋ค.
- [BE] ๐ **Cryptography 50+ ๋ณด์ ๊ฒฝ๊ณ ๊ฐฑ์ **: `pyproject.toml`๊ณผ ๋ hash-locked ์๊ตฌ์ฌํญ ํ์ผ์ ๋์ผํ Cryptography 50+ ํด์์ผ๋ก ์ ํฉํํ์ฌ PKCS#7 ์ค๋ฅยทํ์ด๋ฐ ๊ตฌ๋ถ์ผ๋ก ์ธํ CVE-2026-69247 ์ํ๋ฅผ ์ค์ ์ค์นยท๊ฒ์ฆ ๊ฒฝ๋ก์ ๋ฐ์ํ์ต๋๋ค.
- [FE] โก **๊ฒ์ ๋
ธ๋ ์ฐธ์กฐ ์์ ํ ๋ฐ ์์ฐจ ์ค๋
์ท ํด๋ง**: ๊ฐ์ ์ ๊ทํ ๊ฒ์์ด์ ์๋ณธ ํ
์ด๋ธ ๋ฐ์ดํฐ์๋ ์ฅ์๋ `node.data` ์ฐธ์กฐ๋ฅผ ์ฌ์ฌ์ฉํ์ฌ ๋๋๊ทธ ์ค ๋ถํ์ํ ํ์ ๋ ๋๋ง๊ณผ ํ ๋น์ ์ค์
๋๋ค. ์ค๋
์ท ํด๋ง์ ์ด์ ์์ฒญ์ด ๋๋ ๋ค์๋ง ๋ค์ ์์ฒญ์ ์์ฝํ๋ฉฐ, ์ ํ ๋ณ๊ฒฝยท์ธ๋ง์ดํธ ํ ๋์ฐฉํ ์ค๋๋ ์ฑ๊ณต ๋๋ ์คํจ ์๋ต์ ๋ฌด์ํฉ๋๋ค.
- [BE] ๐ **๊ณต์ export ์ ๊ฒฝ๋ก redaction**: ๊ณต๊ฐ share์ SQL / index-design / reversing-spec export์์ ์ฝ๋ฉํธยท`example_value`๋ฅผ ์ ๊ฑฐํฉ๋๋ค. ๋จ์ ํ
์คํธ๋ก ๋์ถ์ ์ฐจ๋จํฉ๋๋ค.
From 39a7e5dd9822aa5359a2954a396850fb5c982f9e Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Mon, 7 Sep 2026 06:49:33 +0000
Subject: [PATCH 7/9] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20[=EC=A0=91?=
=?UTF-8?q?=EA=B7=BC=EC=84=B1=20=EA=B0=9C=EC=84=A0]=20=EB=82=B4=EB=B3=B4?=
=?UTF-8?q?=EB=82=B4=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20aria-describedby=20?=
=?UTF-8?q?=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
๐ก What: ExportModal์ ๋ด๋ณด๋ด๊ธฐ ๋ฒํผ๋ค์ `aria-describedby` ์์ฑ์ ์ถ๊ฐํ์ฌ ์์ ์๋ ์ค๋ช
ํ
์คํธ๋ฅผ ์ฐธ์กฐํ๋๋ก ๊ฐ์ ํ์ต๋๋ค.
๐ฏ Why: ๋ฒํผ์ด ๋นํ์ฑํ๋์์ ๋(์: "๋จผ์ ํ
์ด๋ธ์ ์ถ๊ฐํ์ธ์"), ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ฌ์ฉ์๊ฐ ์ ๋ฒํผ์ ๋๋ฅผ ์ ์๋์ง ์ด์ ๋ฅผ ๋ช
ํํ๊ฒ ์ ์ ์๋๋ก ๋งฅ๋ฝ์ ์ ๊ณตํ๊ธฐ ์ํจ์
๋๋ค.
๐ธ Before/After:
- Before: ``
- After: `๋จผ์ ํ
์ด๋ธ์ ์ถ๊ฐํ์ธ์ `
โฟ Accessibility: ๋นํ์ฑํ๋ ์ปจํธ๋กค์ ๋ํ ๋ช
ํํ ์ฌ์ ๋ฅผ ๋ณด์กฐ ๊ธฐ๊ธฐ์ ์ ๋ฌํ์ฌ WCAG์ ํผ ์ปจํธ๋กค ์ง์นจ๊ณผ ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ฌ์ฉ์ฑ์ ํฅ์์์ผฐ์ต๋๋ค.
From 26ff208c3da3f44f898a020ad432165414bd8c6a Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Mon, 7 Sep 2026 16:20:59 +0900
Subject: [PATCH 8/9] test(a11y): require stable export artifact description
ids
---
.../modals/ExportModal.ids.test.tsx | 56 +++++++++++++++++++
1 file changed, 56 insertions(+)
create mode 100644 frontend/src/components/modals/ExportModal.ids.test.tsx
diff --git a/frontend/src/components/modals/ExportModal.ids.test.tsx b/frontend/src/components/modals/ExportModal.ids.test.tsx
new file mode 100644
index 000000000..b769ecb7c
--- /dev/null
+++ b/frontend/src/components/modals/ExportModal.ids.test.tsx
@@ -0,0 +1,56 @@
+import '@testing-library/jest-dom/vitest';
+import { cleanup, render, screen } from '@testing-library/react';
+import { afterEach, describe, expect, it, vi } from 'vitest';
+
+import { ExportModal } from './ExportModal';
+
+const baseProps = {
+ isOpen: true,
+ isCopied: false,
+ hasDdlExport: true,
+ hasDictionaryExport: true,
+ hasDiagramExport: true,
+ shareLinkUrl: '',
+ isCreatingShareLink: false,
+ isShareLinkCopied: false,
+ shareLinkError: null,
+ canCreateShareLink: true,
+ onCloseExport: vi.fn(),
+ onCopyExportDdl: vi.fn(),
+ onDownloadSvg: vi.fn(),
+ onDownloadUml: vi.fn(),
+ onDownloadMermaid: vi.fn(),
+ onExportDictionaryCsv: vi.fn(),
+ onExportDictionaryMarkdown: vi.fn(),
+ onDownloadDbml: vi.fn(),
+ onDownloadPrisma: vi.fn(),
+ onCreateShareLink: vi.fn(),
+ onCopyShareLink: vi.fn(),
+};
+
+afterEach(cleanup);
+
+describe('ExportModal artifact description ids', () => {
+ it('uses stable artifact identities rather than user-visible labels for aria relationships', () => {
+ render();
+
+ const expectedIds = new Map([
+ ['SQL DDL ๋ณต์ฌ', 'export-artifact-desc-sql-ddl'],
+ ['SVG ์ด๋ฏธ์ง ๋ด๋ณด๋ด๊ธฐ', 'export-artifact-desc-svg'],
+ ['PlantUML ๋ด๋ณด๋ด๊ธฐ', 'export-artifact-desc-plantuml'],
+ ['Mermaid ๋ด๋ณด๋ด๊ธฐ', 'export-artifact-desc-mermaid'],
+ ['DBML ๋ด๋ณด๋ด๊ธฐ', 'export-artifact-desc-dbml'],
+ ['Prisma Schema ๋ด๋ณด๋ด๊ธฐ', 'export-artifact-desc-prisma'],
+ ['๋ฐ์ดํฐ ์ฌ์ CSV ๋ด๋ณด๋ด๊ธฐ', 'export-artifact-desc-dictionary-csv'],
+ ['๋ฐ์ดํฐ ์ฌ์ Markdown ๋ด๋ณด๋ด๊ธฐ', 'export-artifact-desc-dictionary-markdown'],
+ ]);
+
+ for (const [buttonName, descriptionId] of expectedIds) {
+ expect(screen.getByRole('button', { name: buttonName })).toHaveAttribute(
+ 'aria-describedby',
+ descriptionId,
+ );
+ expect(document.getElementById(descriptionId)).toBeInTheDocument();
+ }
+ });
+});
From 87516b519717ee7ab4307a06a31f978f74b6b7cf Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Mon, 7 Sep 2026 16:21:57 +0900
Subject: [PATCH 9/9] fix(a11y): decouple description ids from visible labels
---
frontend/src/components/modals/ExportModal.tsx | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/frontend/src/components/modals/ExportModal.tsx b/frontend/src/components/modals/ExportModal.tsx
index aff876900..5d2d1fb36 100644
--- a/frontend/src/components/modals/ExportModal.tsx
+++ b/frontend/src/components/modals/ExportModal.tsx
@@ -26,6 +26,7 @@ interface ExportModalProps {
}
type ExportArtifact = {
+ id: string;
label: string;
description: string;
buttonLabel: string;
@@ -72,6 +73,7 @@ export function ExportModal({
const artifacts: ExportArtifact[] = [
{
+ id: 'sql-ddl',
label: 'SQL DDL',
description: hasDdlExport ? '์คํค๋ง ํ
์คํธ' : '๋จผ์ ํ
์ด๋ธ์ ์ถ๊ฐํ์ธ์',
buttonLabel: isCopied ? '๋ณต์ฌ ์๋ฃ' : '๋ด๋ณด๋ด๊ธฐ',
@@ -80,6 +82,7 @@ export function ExportModal({
ariaLabel: 'SQL DDL ๋ณต์ฌ',
},
{
+ id: 'svg',
label: 'SVG ์ด๋ฏธ์ง',
description: hasDiagramExport ? '๋ค์ด์ด๊ทธ๋จ ํ์ผ' : '๋จผ์ ํ
์ด๋ธ์ ์ถ๊ฐํ์ธ์',
buttonLabel: '๋ด๋ณด๋ด๊ธฐ',
@@ -88,6 +91,7 @@ export function ExportModal({
ariaLabel: 'SVG ์ด๋ฏธ์ง ๋ด๋ณด๋ด๊ธฐ',
},
{
+ id: 'plantuml',
label: 'PlantUML',
description: hasDiagramExport ? 'ํ
์คํธ ํฌ๋งท' : '๋จผ์ ํ
์ด๋ธ์ ์ถ๊ฐํ์ธ์',
buttonLabel: '๋ด๋ณด๋ด๊ธฐ',
@@ -96,6 +100,7 @@ export function ExportModal({
ariaLabel: 'PlantUML ๋ด๋ณด๋ด๊ธฐ',
},
{
+ id: 'mermaid',
label: 'Mermaid',
description: hasDiagramExport ? 'ํ
์คํธ ํฌ๋งท' : '๋จผ์ ํ
์ด๋ธ์ ์ถ๊ฐํ์ธ์',
buttonLabel: '๋ด๋ณด๋ด๊ธฐ',
@@ -104,6 +109,7 @@ export function ExportModal({
ariaLabel: 'Mermaid ๋ด๋ณด๋ด๊ธฐ',
},
{
+ id: 'dbml',
label: 'DBML',
description: hasDiagramExport ? 'ํ
์คํธ ํฌ๋งท' : '๋จผ์ ํ
์ด๋ธ์ ์ถ๊ฐํ์ธ์',
buttonLabel: '๋ด๋ณด๋ด๊ธฐ',
@@ -112,6 +118,7 @@ export function ExportModal({
ariaLabel: 'DBML ๋ด๋ณด๋ด๊ธฐ',
},
{
+ id: 'prisma',
label: 'Prisma Schema',
description: hasDiagramExport ? 'ํ
์คํธ ํฌ๋งท' : '๋จผ์ ํ
์ด๋ธ์ ์ถ๊ฐํ์ธ์',
buttonLabel: '๋ด๋ณด๋ด๊ธฐ',
@@ -120,6 +127,7 @@ export function ExportModal({
ariaLabel: 'Prisma Schema ๋ด๋ณด๋ด๊ธฐ',
},
{
+ id: 'dictionary-csv',
label: 'Data Dictionary CSV',
description: hasDictionaryExport ? 'ํ
์ด๋ธ/์ปฌ๋ผ ๋ชฉ๋ก' : '๋จผ์ ํ
์ด๋ธ์ ์ถ๊ฐํ์ธ์',
buttonLabel: '๋ด๋ณด๋ด๊ธฐ',
@@ -128,6 +136,7 @@ export function ExportModal({
ariaLabel: '๋ฐ์ดํฐ ์ฌ์ CSV ๋ด๋ณด๋ด๊ธฐ',
},
{
+ id: 'dictionary-markdown',
label: 'Data Dictionary MD',
description: hasDictionaryExport ? '๋งํฌ๋ค์ด ๋ฌธ์' : '๋จผ์ ํ
์ด๋ธ์ ์ถ๊ฐํ์ธ์',
buttonLabel: '๋ด๋ณด๋ด๊ธฐ',
@@ -223,9 +232,9 @@ export function ExportModal({
{artifacts.map((artifact) => {
- const descId = `export-artifact-desc-${artifact.label.replace(/\s+/g, '-')}`;
+ const descId = `export-artifact-desc-${artifact.id}`;
return (
-
+
{artifact.label}
{artifact.description}
@@ -236,7 +245,7 @@ export function ExportModal({
disabled={artifact.disabled}
aria-label={artifact.ariaLabel}
aria-describedby={descId}
- aria-live={artifact.label === 'SQL DDL' ? 'polite' : undefined}
+ aria-live={artifact.id === 'sql-ddl' ? 'polite' : undefined}
>
{artifact.buttonLabel}