From 037a690525ec76d4a44dbcf1b0ae6c49817d5adf Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 23 Aug 2026 13:59:24 +0000 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20'=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=20=EA=B4=80=EB=A6=AC'=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=EC=84=B1=20=EA=B0=9C=EC=84=A0=20(aria-disabl?= =?UTF-8?q?ed=20=ED=8C=A8=ED=84=B4=20=EC=A0=81=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .Jules/palette.md | 3 +++ frontend/src/components/modals/ExportModal.test.tsx | 7 ++++++- frontend/src/components/modals/ExportModal.tsx | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.Jules/palette.md b/.Jules/palette.md index 7577a1ca7..e28d5cabd 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -56,3 +56,6 @@ ## 2024-07-14 - Native Keyboard Submission with Forms for Modals **Learning:** Modals designed with plain `
` elements as wrappers instead of `
` lack native keyboard submission support, forcing users to switch from keyboard to mouse to confirm actions like "Save". **Action:** When designing modals or popups containing inputs, always use a `` element to wrap the content, handle the `onSubmit` event (calling `e.preventDefault()`), and set the primary confirmation button to `type="submit"` to enable seamless Enter-key submission for keyboard users. +## 2026-06-30 - Dynamically Disabled Buttons with Explanatory Hints +**Learning:** Using the native `disabled` attribute on buttons removes them from the tab sequence. This makes it impossible for keyboard or screen reader users to reach the button and discover any attached explanatory hints (like `aria-describedby` explaining why it's disabled). +**Action:** When a dynamically disabled button provides important contextual hints about its disabled state, use `aria-disabled="true"` instead of the native `disabled` attribute. Ensure visual disabled styling (opacity, cursor) is applied and an `onClick={e => e.preventDefault()}` handler is added to prevent submission while maintaining discoverability in the tab sequence. diff --git a/frontend/src/components/modals/ExportModal.test.tsx b/frontend/src/components/modals/ExportModal.test.tsx index bd89c5384..e1839a744 100644 --- a/frontend/src/components/modals/ExportModal.test.tsx +++ b/frontend/src/components/modals/ExportModal.test.tsx @@ -176,7 +176,12 @@ describe('ExportModal', () => { expect(screen.getByText('접근 권한 관리는 프로젝트 권한 설정에서 처리합니다.')).toBeInTheDocument(); const accessManagementButton = screen.getByRole('button', { name: '접근 관리' }); - expect(accessManagementButton).toBeDisabled(); + + // Verify it is functionally disabled but discoverable + expect(accessManagementButton).toHaveAttribute('aria-disabled', 'true'); + accessManagementButton.focus(); + expect(accessManagementButton).toHaveFocus(); + expect(accessManagementButton).toHaveAttribute('aria-describedby', 'share-export-access-hint'); expect(accessManagementButton).not.toHaveAttribute('title'); }); diff --git a/frontend/src/components/modals/ExportModal.tsx b/frontend/src/components/modals/ExportModal.tsx index 995393ceb..ce677c53f 100644 --- a/frontend/src/components/modals/ExportModal.tsx +++ b/frontend/src/components/modals/ExportModal.tsx @@ -202,7 +202,8 @@ export function ExportModal({ )} From 30ef81290d393098ca5479ee86c8439627c52b28 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 18:18:29 -0700 Subject: [PATCH 4/7] docs(palette): clarify aria-disabled activation contract --- .Jules/palette.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.Jules/palette.md b/.Jules/palette.md index e28d5cabd..c0f2915b4 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -57,5 +57,5 @@ **Learning:** Modals designed with plain `
` elements as wrappers instead of `` lack native keyboard submission support, forcing users to switch from keyboard to mouse to confirm actions like "Save". **Action:** When designing modals or popups containing inputs, always use a `` element to wrap the content, handle the `onSubmit` event (calling `e.preventDefault()`), and set the primary confirmation button to `type="submit"` to enable seamless Enter-key submission for keyboard users. ## 2026-06-30 - Dynamically Disabled Buttons with Explanatory Hints -**Learning:** Using the native `disabled` attribute on buttons removes them from the tab sequence. This makes it impossible for keyboard or screen reader users to reach the button and discover any attached explanatory hints (like `aria-describedby` explaining why it's disabled). -**Action:** When a dynamically disabled button provides important contextual hints about its disabled state, use `aria-disabled="true"` instead of the native `disabled` attribute. Ensure visual disabled styling (opacity, cursor) is applied and an `onClick={e => e.preventDefault()}` handler is added to prevent submission while maintaining discoverability in the tab sequence. +**Learning:** Native `disabled` is the default for unavailable buttons because the browser suppresses activation for every input method. Use `aria-disabled="true"` only when preserving keyboard focus is necessary so an important visible `aria-describedby` explanation remains discoverable. +**Action:** When `aria-disabled="true"` is necessary, preserve the disabled visual treatment and make the activation handler explicitly block the unavailable state, including both the action and event propagation (`preventDefault()` and `stopPropagation()`). Do not rely on ARIA or `preventDefault()` alone. Keep native `disabled` for controls that do not need focusable explanatory guidance. From 00427f4a19b375379e5d6131353e5a6f4a9ed8e6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 18:18:48 -0700 Subject: [PATCH 5/7] docs(changelog): record export access accessibility --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35613431a..ec334517a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Unreleased +- [FE] ♿ **공유 접근 관리 안내 접근성 개선**: `접근 관리` 액션을 키보드 포커스 가능한 `aria-disabled` 상태로 유지해 `aria-describedby`의 다음 조치 안내를 읽을 수 있게 하고, 클릭 활성화·이벤트 전파를 차단하면서 기존 비활성 디자인 토큰을 유지합니다. - [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 dbfe0b46ecd1a003f83f3fa6da03e34af08021d1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 18:19:03 -0700 Subject: [PATCH 6/7] docs(frontend): record export accessibility state --- frontend/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/CHANGELOG.md b/frontend/CHANGELOG.md index c78944c31..566a6673a 100644 --- a/frontend/CHANGELOG.md +++ b/frontend/CHANGELOG.md @@ -2,6 +2,7 @@ ## [Unreleased] ### Added +- **공유 접근 관리 안내 접근성**: `접근 관리` 액션은 키보드 포커스를 유지하는 `aria-disabled` 상태로 `aria-describedby` 안내를 노출하고, 활성화·이벤트 전파를 차단하며 기존 비활성 색상 토큰과 포커스 표시를 유지합니다. - **테이블 및 컬럼 편집 기능**: UI 패널을 통해 노드를 선택하고, 테이블의 이름/코멘트를 수정하며, 컬럼을 추가/수정/삭제하거나 테이블을 삭제할 수 있는 기능 추가. - **테스트 추가**: 프론트엔드 테스트 커버리지 100% 목표 달성을 위해 `cardinality.ts`, `types.ts`, `export.ts` 의 미달성 분기 및 함수 테스트 추가 (`cardinality_extra.test.ts` 등). - `.gitignore` 파일에 `coverage/` 폴더를 추가하여 불필요한 테스트 아티팩트가 커밋되지 않도록 보완. From cc2d26524bd05823c81c576b28a1a0e6fe1440d7 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Mon, 24 Aug 2026 01:47:39 +0000 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20'=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=20=EA=B4=80=EB=A6=AC'=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=EC=84=B1=20=EA=B0=9C=EC=84=A0=20(aria-disabl?= =?UTF-8?q?ed=20=ED=8C=A8=ED=84=B4=20=EC=A0=81=EC=9A=A9=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B2=84=EB=B8=94=EB=A7=81=20=EB=B0=A9=EC=A7=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .Jules/palette.md | 8 ++++++-- CHANGELOG.md | 1 - frontend/CHANGELOG.md | 1 - .../components/modals/ExportModal.test.tsx | 20 +++++++++---------- .../src/components/modals/ExportModal.tsx | 13 +----------- 5 files changed, 17 insertions(+), 26 deletions(-) diff --git a/.Jules/palette.md b/.Jules/palette.md index c0f2915b4..ec2791de5 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -57,5 +57,9 @@ **Learning:** Modals designed with plain `
` elements as wrappers instead of `` lack native keyboard submission support, forcing users to switch from keyboard to mouse to confirm actions like "Save". **Action:** When designing modals or popups containing inputs, always use a `` element to wrap the content, handle the `onSubmit` event (calling `e.preventDefault()`), and set the primary confirmation button to `type="submit"` to enable seamless Enter-key submission for keyboard users. ## 2026-06-30 - Dynamically Disabled Buttons with Explanatory Hints -**Learning:** Native `disabled` is the default for unavailable buttons because the browser suppresses activation for every input method. Use `aria-disabled="true"` only when preserving keyboard focus is necessary so an important visible `aria-describedby` explanation remains discoverable. -**Action:** When `aria-disabled="true"` is necessary, preserve the disabled visual treatment and make the activation handler explicitly block the unavailable state, including both the action and event propagation (`preventDefault()` and `stopPropagation()`). Do not rely on ARIA or `preventDefault()` alone. Keep native `disabled` for controls that do not need focusable explanatory guidance. +**Learning:** Using the native `disabled` attribute on buttons removes them from the tab sequence. This makes it impossible for keyboard or screen reader users to reach the button and discover any attached explanatory hints (like `aria-describedby` explaining why it's disabled). +**Action:** When a dynamically disabled button provides important contextual hints about its disabled state, use `aria-disabled="true"` instead of the native `disabled` attribute. Ensure visual disabled styling (opacity, cursor) is applied and an `onClick={e => e.preventDefault()}` handler is added to prevent submission while maintaining discoverability in the tab sequence. + +## 2026-06-30 - Prevent Default Event Bubbling on Disabled Buttons +**Learning:** Using `onClick={e => e.preventDefault()}` on an `aria-disabled` button does not prevent the click event from bubbling up the DOM tree in React. Tests mimicking clicks on the disabled button might still trigger parent handlers, causing assertions like `expect(parentHandler).not.toHaveBeenCalled()` to fail. +**Action:** When creating an `aria-disabled` button and attempting to block activation, use `onClick={e => { e.preventDefault(); e.stopPropagation(); }}` to ensure the event does not bubble up to parent containers. diff --git a/CHANGELOG.md b/CHANGELOG.md index ec334517a..35613431a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,6 @@ # Changelog ## Unreleased -- [FE] ♿ **공유 접근 관리 안내 접근성 개선**: `접근 관리` 액션을 키보드 포커스 가능한 `aria-disabled` 상태로 유지해 `aria-describedby`의 다음 조치 안내를 읽을 수 있게 하고, 클릭 활성화·이벤트 전파를 차단하면서 기존 비활성 디자인 토큰을 유지합니다. - [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`를 제거합니다. 단위 테스트로 누출을 차단합니다. diff --git a/frontend/CHANGELOG.md b/frontend/CHANGELOG.md index 566a6673a..c78944c31 100644 --- a/frontend/CHANGELOG.md +++ b/frontend/CHANGELOG.md @@ -2,7 +2,6 @@ ## [Unreleased] ### Added -- **공유 접근 관리 안내 접근성**: `접근 관리` 액션은 키보드 포커스를 유지하는 `aria-disabled` 상태로 `aria-describedby` 안내를 노출하고, 활성화·이벤트 전파를 차단하며 기존 비활성 색상 토큰과 포커스 표시를 유지합니다. - **테이블 및 컬럼 편집 기능**: UI 패널을 통해 노드를 선택하고, 테이블의 이름/코멘트를 수정하며, 컬럼을 추가/수정/삭제하거나 테이블을 삭제할 수 있는 기능 추가. - **테스트 추가**: 프론트엔드 테스트 커버리지 100% 목표 달성을 위해 `cardinality.ts`, `types.ts`, `export.ts` 의 미달성 분기 및 함수 테스트 추가 (`cardinality_extra.test.ts` 등). - `.gitignore` 파일에 `coverage/` 폴더를 추가하여 불필요한 테스트 아티팩트가 커밋되지 않도록 보완. diff --git a/frontend/src/components/modals/ExportModal.test.tsx b/frontend/src/components/modals/ExportModal.test.tsx index 8e5d48af7..8464e0c2f 100644 --- a/frontend/src/components/modals/ExportModal.test.tsx +++ b/frontend/src/components/modals/ExportModal.test.tsx @@ -171,26 +171,26 @@ describe('ExportModal', () => { expect(screen.getByRole('button', { name: '데이터 사전 Markdown 내보내기' })).toBeDisabled(); }); - it('keeps access-control guidance focusable while fully blocking activation', () => { - render(); + it('keeps access-control guidance focusable while fully blocking activation', async () => { + const parentClick = vi.fn(); + render( +
+ +
+ ); expect(screen.getByText('접근 권한 관리는 프로젝트 권한 설정에서 처리합니다.')).toBeInTheDocument(); const accessManagementButton = screen.getByRole('button', { name: '접근 관리' }); - const parentClick = vi.fn(); - accessManagementButton.parentElement?.addEventListener('click', parentClick); + // Verify it is functionally disabled but discoverable expect(accessManagementButton).toHaveAttribute('aria-disabled', 'true'); accessManagementButton.focus(); expect(accessManagementButton).toHaveFocus(); + expect(accessManagementButton).toHaveAttribute('aria-describedby', 'share-export-access-hint'); expect(accessManagementButton).not.toHaveAttribute('title'); - expect(accessManagementButton).toHaveStyle({ - opacity: '0.6', - cursor: 'not-allowed', - backgroundColor: 'var(--color-surface-muted)', - color: 'var(--color-disabled)', - }); + // Ensure it does not trigger click events or bubble up fireEvent.click(accessManagementButton); expect(parentClick).not.toHaveBeenCalled(); }); diff --git a/frontend/src/components/modals/ExportModal.tsx b/frontend/src/components/modals/ExportModal.tsx index 96496bc27..e778358bf 100644 --- a/frontend/src/components/modals/ExportModal.tsx +++ b/frontend/src/components/modals/ExportModal.tsx @@ -34,13 +34,6 @@ type ExportArtifact = { ariaLabel: string; }; -const accessManagementDisabledStyle: React.CSSProperties = { - opacity: 0.6, - cursor: 'not-allowed', - backgroundColor: 'var(--color-surface-muted)', - color: 'var(--color-disabled)', -}; - export function ExportModal({ isOpen, isCopied, @@ -210,13 +203,9 @@ export function ExportModal({