From 270ef93922977e6af01c89cf27788ed6c861eb92 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 16 Sep 2026 13:57:10 +0000 Subject: [PATCH 01/34] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20[UX=20improveme?= =?UTF-8?q?nt]=20hero=20=EC=84=B9=EC=85=98=20=EC=A0=91=EA=B7=BC=EC=84=B1?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .Jules/palette.md | 4 ++++ index.html | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.Jules/palette.md b/.Jules/palette.md index 7cf7b0d..9bb5ae8 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -32,3 +32,7 @@ ## 2026-08-22 - Add aria-labelledby to section landmarks **Learning:** `
`은 접근성 이름이 있을 때만 `region` 랜드마크로 노출되고, 이름이 없으면 `generic`으로 매핑되어 화면 탐색 랜드마크 목록에 나타나지 않습니다. `id` 속성만으로는 부족합니다. **Action:** `
`에는 고유한 `id`를 가진 내부 헤딩(`

`)을 `aria-labelledby`로 참조시켜 접근성 이름을 부여합니다. 회귀 테스트가 참조 대상 id의 실재 여부와 아이디가 있는 모든 섹션의 레이블링을 검증합니다. + +## 2024-10-23 - hero 섹션에 aria-labelledby 랜드마크 이름 추가 +**Learning:** 랜드마크 영역을 스크린 리더에 올바르게 노출하기 위해서는 문서 내 모든 `
` 요소에 고유한 접근성 이름(`aria-labelledby`)이 필요하며, 예외적으로 첫 번째(hero) 섹션이라도 누락하면 region으로 탐색되지 않습니다. +**Action:** 앞으로 모든 `
` 태그를 사용할 때는 반드시 내부에 해당하는 제목 요소의 `id`를 `aria-labelledby`로 연결하여 랜드마크 접근성을 보장해야 합니다. diff --git a/index.html b/index.html index b553382..4cf7b33 100644 --- a/index.html +++ b/index.html @@ -70,9 +70,9 @@
-
+
-

맥락지혜 연구실

+

맥락지혜 연구실

Contextual Wisdom Lab

구슬이 서 말이어도 꿰어야 보배이듯, 문서, 메일, 로그, 회의록을 맥락 안에서 엮어 사람이 무엇을 결정하고 무엇을 실행할지 보이게 하는 AI 의사결정 지원 시스템을 연구하고 만듭니다. From 84ce7938d072ca05e2ee4d634c77f38e6a7bb34a Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 17 Sep 2026 03:59:31 +0000 Subject: [PATCH 02/34] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20[UX=20improveme?= =?UTF-8?q?nt]=20hero=20=EC=84=B9=EC=85=98=20=EC=A0=91=EA=B7=BC=EC=84=B1?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From d6916c4bb286d009bc0f0ca1d8ddbac8306cfa75 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:23:28 +0000 Subject: [PATCH 03/34] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20[UX=20improveme?= =?UTF-8?q?nt]=20hero=20=EC=84=B9=EC=85=98=20=EC=A0=91=EA=B7=BC=EC=84=B1?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 01d0f189e9b0b9c45815e58a9bca9abae8fef216 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 17 Sep 2026 22:26:14 +0000 Subject: [PATCH 04/34] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20[UX=20improveme?= =?UTF-8?q?nt]=20hero=20=EC=84=B9=EC=85=98=20=EC=A0=91=EA=B7=BC=EC=84=B1?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From dc4706b3502b03f75ed4ec644b01013099922298 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Fri, 18 Sep 2026 08:22:20 +0000 Subject: [PATCH 05/34] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20[UX=20improveme?= =?UTF-8?q?nt]=20hero=20=EC=84=B9=EC=85=98=20=EC=A0=91=EA=B7=BC=EC=84=B1?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From cd9251a837155b5128ad3982a5894c8bb3f309b6 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Fri, 18 Sep 2026 23:11:24 +0000 Subject: [PATCH 06/34] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20[UX=20improveme?= =?UTF-8?q?nt]=20hero=20=EC=84=B9=EC=85=98=20=EC=A0=91=EA=B7=BC=EC=84=B1?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 97e480a06ed97418d8ce6a4186eb6ec6af55c6b1 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 19 Sep 2026 20:03:26 +0000 Subject: [PATCH 07/34] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20[UX=20improveme?= =?UTF-8?q?nt]=20hero=20=EC=84=B9=EC=85=98=20=EC=A0=91=EA=B7=BC=EC=84=B1?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From a5ea2aef3cee1f6fd47290b9037f7e077eccd742 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 20 Sep 2026 06:58:32 +0900 Subject: [PATCH 08/34] test(a11y): cover every section landmark --- tests/test_section_landmarks.py | 46 +++++++++++++++++---------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/tests/test_section_landmarks.py b/tests/test_section_landmarks.py index c4c1042..1697ade 100644 --- a/tests/test_section_landmarks.py +++ b/tests/test_section_landmarks.py @@ -8,49 +8,51 @@ class _LandmarkParser(HTMLParser): - """Collect

start tags and every element id in document order.""" + """Collect section attributes and heading ids in document order.""" def __init__(self) -> None: super().__init__() self.sections: list[dict[str, str | None]] = [] - self.ids: set[str] = set() + self.heading_ids: list[str] = [] - def handle_starttag(self, tag, attrs) -> None: + def handle_starttag( + self, tag: str, attrs: list[tuple[str, str | None]] + ) -> None: + """Record section attributes and labelled-heading identities.""" attributes = dict(attrs) - element_id = attributes.get("id") - if element_id: - self.ids.add(element_id) if tag == "section": self.sections.append(attributes) + if tag in {"h1", "h2", "h3", "h4", "h5", "h6"}: + heading_id = attributes.get("id") + if heading_id: + self.heading_ids.append(heading_id) def _parse_index() -> _LandmarkParser: + """Parse the homepage with the standard-library HTML parser.""" parser = _LandmarkParser() parser.feed(INDEX.read_text(encoding="utf-8")) assert parser.sections, "homepage must contain at least one section" return parser -def test_labelled_sections_reference_an_existing_heading() -> None: - """Each aria-labelledby target must resolve to a real id in the document.""" +def test_labelled_sections_reference_one_existing_heading() -> None: + """Each aria-labelledby target must resolve to exactly one heading id.""" parser = _parse_index() - labelled = [s for s in parser.sections if s.get("aria-labelledby")] - assert labelled, "homepage sections must expose accessible names" - - for section in labelled: - target = section["aria-labelledby"] - assert target in parser.ids, ( - f"section #{section.get('id')} references missing id {target!r}" + for section in parser.sections: + target = section.get("aria-labelledby") + assert target, ( + f"section #{section.get('id')} must reference a heading via aria-labelledby" + ) + assert parser.heading_ids.count(target) == 1, ( + f"section #{section.get('id')} must reference one existing heading; " + f"found {parser.heading_ids.count(target)} for {target!r}" ) -def test_identified_sections_are_all_labelled() -> None: - """Every identifiable section landmark must carry an accessible name.""" +def test_all_sections_are_labelled_including_anonymous_sections() -> None: + """Prevent an id-less section from bypassing the landmark contract.""" parser = _parse_index() - for section in parser.sections: - if section.get("id"): - assert section.get("aria-labelledby"), ( - f"section #{section['id']} must reference a heading via aria-labelledby" - ) + assert all(section.get("aria-labelledby") for section in parser.sections) From 7cd48a56c16a8172011aefff8faff606742e1b85 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 20 Sep 2026 06:58:34 +0900 Subject: [PATCH 09/34] docs(a11y): correct landmark contract guidance --- .Jules/palette.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.Jules/palette.md b/.Jules/palette.md index 9bb5ae8..de1b43c 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -31,8 +31,8 @@ ## 2026-08-22 - Add aria-labelledby to section landmarks **Learning:** `
`은 접근성 이름이 있을 때만 `region` 랜드마크로 노출되고, 이름이 없으면 `generic`으로 매핑되어 화면 탐색 랜드마크 목록에 나타나지 않습니다. `id` 속성만으로는 부족합니다. -**Action:** `
`에는 고유한 `id`를 가진 내부 헤딩(`

`)을 `aria-labelledby`로 참조시켜 접근성 이름을 부여합니다. 회귀 테스트가 참조 대상 id의 실재 여부와 아이디가 있는 모든 섹션의 레이블링을 검증합니다. +**Action:** 홈페이지의 모든 `
`은 내부 제목(`

`–`

`)의 고유 `id`를 `aria-labelledby`로 참조합니다. 회귀 테스트는 `id`가 없는 section도 포함하고 참조 대상이 정확히 하나의 실제 heading인지 검증합니다. -## 2024-10-23 - hero 섹션에 aria-labelledby 랜드마크 이름 추가 -**Learning:** 랜드마크 영역을 스크린 리더에 올바르게 노출하기 위해서는 문서 내 모든 `
` 요소에 고유한 접근성 이름(`aria-labelledby`)이 필요하며, 예외적으로 첫 번째(hero) 섹션이라도 누락하면 region으로 탐색되지 않습니다. -**Action:** 앞으로 모든 `
` 태그를 사용할 때는 반드시 내부에 해당하는 제목 요소의 `id`를 `aria-labelledby`로 연결하여 랜드마크 접근성을 보장해야 합니다. +## 2026-09-20 - hero section landmark contract +**Learning:** 기존 테스트는 `id`가 있는 section만 검사해 `id` 없는 hero가 접근성 이름 없이 남는 회귀를 허용했습니다. 임의의 element id가 아니라 실제 heading id와의 관계를 검증해야 합니다. +**Action:** 모든 homepage section은 정확히 하나의 기존 `

`–`

` id를 `aria-labelledby`로 참조합니다. 실제 브라우저 접근성 트리와 보조기술 검증 전에는 정적 계약을 전달 완료 증거로 표현하지 않습니다. From e2e67ade9f68e661b8e9cca33532da8ec6d1fe55 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 20 Sep 2026 06:58:36 +0900 Subject: [PATCH 10/34] docs(changelog): record section landmark contract --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d8133f..704cb0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # CHANGELOG ## [Unreleased] -- **제품 안내 정합성**: 홈페이지의 프로젝트·Fork 목록을 실제로 조직이 소유한 공개 저장소와 맞췄습니다. 이름이 바뀐 `waf-ids-ai-soc` 대신 현재 저장소인 `wardnet`을 안내하고, 그동안 빠져 있던 `Orgmetra`, `TEPP`, `psychometrics-commons`, `contextual-orchestrator` 카드를 추가했습니다. Fork 섹션에서는 소유한 `argos`·`vooster` 카드를 저장소로 연결하고, 아직 조직 소유 공개 저장소가 없는 `vooster-v2-mvp`는 소개 카드로만 남겨 잘못된 링크를 없앴습니다. Naruon 섹션에는 저장소로 가는 버튼을 추가하고, 추가된 카드는 모두 새 창 열림 안내와 `rel="noopener noreferrer"` 정책을 따릅니다. 회귀 테스트가 카드별 목적지·폐기된 URL 부재·다국어 문구 정의를 검증합니다. +- **UX/접근성 계약 강화**: hero를 포함한 홈페이지의 모든 `
`이 정확히 하나의 실제 heading을 `aria-labelledby`로 참조하도록 표준 라이브러리 기반 회귀 테스트를 강화했습니다. 정적 계약과 실제 브라우저·보조기술 증거는 구분합니다.\n- **제품 안내 정합성**: 홈페이지의 프로젝트·Fork 목록을 실제로 조직이 소유한 공개 저장소와 맞췄습니다. 이름이 바뀐 `waf-ids-ai-soc` 대신 현재 저장소인 `wardnet`을 안내하고, 그동안 빠져 있던 `Orgmetra`, `TEPP`, `psychometrics-commons`, `contextual-orchestrator` 카드를 추가했습니다. Fork 섹션에서는 소유한 `argos`·`vooster` 카드를 저장소로 연결하고, 아직 조직 소유 공개 저장소가 없는 `vooster-v2-mvp`는 소개 카드로만 남겨 잘못된 링크를 없앴습니다. Naruon 섹션에는 저장소로 가는 버튼을 추가하고, 추가된 카드는 모두 새 창 열림 안내와 `rel="noopener noreferrer"` 정책을 따릅니다. 회귀 테스트가 카드별 목적지·폐기된 URL 부재·다국어 문구 정의를 검증합니다. - **문서 개선**: 저장소 `README.md`를 방문자 우선 구조로 다시 썼습니다. 이 저장소가 소유하는 범위(조직 차원의 공개 서사와 내비게이션)와 각 제품 저장소가 계속 소유하는 범위(런타임 동작, 릴리스·보안·규정 근거, 라이선스)를 표로 분리해, 홈페이지가 제품 권위의 대체물이 아님을 명확히 했습니다. DeepWiki 배지, 로컬 미리보기·편집 가이드, 근거 없는 주장을 게시하지 않기 위한 품질·신뢰 규칙도 함께 담았습니다. - **UX/접근성 개선**: 홈페이지의 각 섹션 랜드마크에 `aria-labelledby`를 연결해 스크린 리더 사용자가 화면 탐색 중 각 영역의 목적을 제목으로 듣고 이동할 수 있게 했습니다. 아이디가 있는 모든 `
`은 실제로 존재하는 제목(`

`)의 아이디를 참조하며, 회귀 테스트가 참조 대상의 실재 여부를 검증합니다. - **UX/접근성 개선**: 언어 전환 버튼에 동적 번역이 지원되는 툴팁(title)을 추가하여 버튼의 기능을 명확히 안내했습니다. From 4cb6969d88a392563503c8041578cc92691ec46a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 20 Sep 2026 06:58:37 +0900 Subject: [PATCH 11/34] docs(assurance): establish product technical gap baseline --- docs/product-technical-gap-baseline.md | 129 +++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 docs/product-technical-gap-baseline.md diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md new file mode 100644 index 0000000..80381c2 --- /dev/null +++ b/docs/product-technical-gap-baseline.md @@ -0,0 +1,129 @@ +# Product and technical gap baseline + +Status: **Proposed** +Canonical writer: [ContextualWisdomLab.github.io#240](https://github.com/ContextualWisdomLab/ContextualWisdomLab.github.io/pull/240) +Evidence ancestor: `97e480a06ed97418d8ce6a4186eb6ec6af55c6b1` (2026-09-20) +Current exact head and hosted evidence are recorded on the PR after every ordinary-forward update. + +This document is the buyer-visible design-assurance ledger for the public +ContextualWisdomLab website. A checked source contract is not a browser, +assistive-technology, deployment, approval, certification, or release claim. + +## PRD + +### Goal + +A visitor can understand ContextualWisdomLab's public purpose and navigate to +the authoritative product repositories without the website replacing product +domain truth. + +### Users and scenes + +- A prospective buyer scans the homepage by headings and region landmarks. +- A keyboard or screen-reader user skips to main content and navigates sections. +- A multilingual visitor needs equivalent names, wrapping, and next actions. +- An operator must distinguish published website evidence from repository claims. + +### Acceptance + +The homepage must preserve deterministic content, meaningful landmarks, +keyboard/pointer/touch operation, responsive layout, reduced motion, safe +external navigation, eight locales (ko/en/ja/zh/vi/es/de/fr), and recovery from +offline or missing destinations. Claims require evidence from the same exact +head. + +## TRD + +The product is a static HTML/CSS/JavaScript site. Semantic HTML and the browser +accessibility tree are the interaction boundary. Python standard-library parsers +provide source contracts; they do not replace Chromium, Firefox, WebKit, or +assistive-technology runs. No new runtime dependency is justified for landmark +validation. + +Translation strings currently live in the browser JavaScript object. That is a +presentation resource, not organization or product domain truth. It does not +satisfy the target DB-backed versioned translation authority. + +## UML + +```mermaid +flowchart LR + V[Visitor] --> H[index.html] + H --> C[styles.css] + H --> I[i18n.js] + H --> R[Authoritative product repositories] + T[Source contracts] --> H + B[Browser and AT evidence] --> H +``` + +## ERD + +No application database is present in this bounded context, so a product-data +ERD is not applicable. If the shared translation authority is introduced, its +minimum independent model is: + +```mermaid +erDiagram + SCREEN_KEY ||--o{ TRANSLATION_RESOURCE : versions + TRANSLATION_RESOURCE }o--|| LOCALE : uses + TRANSLATION_RESOURCE ||--o{ REVIEW_DECISION : governed_by + TRANSLATION_RESOURCE ||--o{ DEPLOYMENT_RECORD : publishes +``` + +The website must consume a released contract through an API/cache boundary; it +must not copy another owner's source or query its database. + +## Context Map + +- **Public Narrative (this repository):** homepage composition, public + navigation, static presentation, Pages deployment evidence. +- **Product repositories (upstream authority):** product behavior, release, + security, domain language, and operational truth. +- **Translation authority (required shared owner, not yet released):** versioned + translation, review, approval, deployment, and rollback. +- **GitHub (external):** repository destinations behind ordinary links; the + website does not infer product readiness from an open PR. + +## Exact-head acceptance matrix + +| Boundary | Evidence at ancestor | Status | Required action | +| --- | --- | --- | --- | +| Determinism | 11 homepage sections; 11 heading targets | PASS, source only | Keep exact structure contract | +| Semantics | hero uses `aria-labelledby="hero-title"`; parser checks every section and exactly one real heading target | PASS, source only | Replay accessibility tree | +| Keyboard/pointer/touch | No current-head real-browser trace | FAIL | Chromium/Firefox/WebKit interaction run | +| WCAG 2.2 AA / AT | No current-head AT transcript or audit | FAIL | Screen reader and automated/manual audit | +| Responsive | No 320/768/desktop screenshots at current head | FAIL | Capture overflow, wrapping, focus, zoom | +| Reduced motion | Source policy exists; no current-head browser observation | PARTIAL | Verify OS preference in each browser | +| Locales | `i18n.js` exposes ko/en only | FAIL | Released ko/en/ja/zh/vi/es/de/fr screen resources and E2E | +| Loading/empty/error/offline/permission/read-only/stale/conflict/retry/busy | Static surface has no complete evidence set | FAIL | Mark N/A per state with rationale or provide replay | +| CTA to API/destination | Static links exist; no current-head destination replay | FAIL | Validate real destinations and failure guidance | +| Large-data performance | Not applicable to hero landmark; whole-page p95 unmeasured | FAIL (site) | Measure render/interaction p50 and p95 without sample reduction | +| Import/export | No import/export surface | N/A | Reassess if introduced | +| Persistence/reload/rollback | No current-head cache/reload/deploy rollback evidence | FAIL | Exercise reload, offline cache behavior, and deployment rollback | +| Independent approval | Previous approval targets an older head | FAIL | Obtain qualifying current-head approval | +| Hosted Checks | Fresh exact-head generation required after repair | PENDING | Wait without blind rerun | + +## Gap and action register + +| Gap | Owner | Action | Status | +| --- | --- | --- | --- | +| Existing landmark test skipped anonymous sections | Public Narrative | Validate every section and exactly one heading target | Repaired in #240; hosted evidence pending | +| Palette entry used a stale 2024 date and `h2`-only rule | Public Narrative | Bind guidance to 2026-09-20 evidence and h1-h6 | Repaired in #240 | +| Only ko/en resources exist | Translation authority + Public Narrative consumer | Release eight-locale resource contract, then consume by screen key | Proposed / blocked | +| Browser, AT, responsive, recovery evidence absent | Public Narrative | Produce current-head evidence artifacts | Open | +| Static DTO/catalog may be mistaken for domain truth | Public Narrative | Keep product claims linked to canonical repositories | Open | +| Page performance target is unmeasured | Public Narrative | Measure all pages; repair causal render/runtime bottleneck | Open | +| Exact-head approval and hosted Checks absent | Repository governance | Preserve Draft until independently satisfied | Open | + +## Standards and evidence + +- World Wide Web Consortium. (2023). *Web Content Accessibility Guidelines + (WCAG) 2.2*. https://www.w3.org/TR/WCAG22/ +- Web Hypertext Application Technology Working Group. (2026). *HTML Living + Standard: The section element*. https://html.spec.whatwg.org/multipage/sections.html#the-section-element +- World Wide Web Consortium. (2026). *Accessible Rich Internet Applications + (WAI-ARIA) 1.2: region role*. https://www.w3.org/TR/wai-aria-1.2/#region + +These standards explain the semantic boundary. Repository source, tests, +current-head workflow logs, browser artifacts, and review records remain the +acceptance evidence. From 56783a2aa95a53ecece1d95ddcb1d0e8f9008f6e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 20 Sep 2026 07:53:39 +0900 Subject: [PATCH 12/34] test(docs): reject escaped release-list newlines --- tests/test_changelog.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/test_changelog.py diff --git a/tests/test_changelog.py b/tests/test_changelog.py new file mode 100644 index 0000000..39d0969 --- /dev/null +++ b/tests/test_changelog.py @@ -0,0 +1,15 @@ +"""Regression contracts for the repository-facing release ledger.""" + +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +CHANGELOG = ROOT / "CHANGELOG.md" + + +def test_unreleased_entries_use_real_line_breaks() -> None: + """Reject escaped newlines that merge adjacent Markdown list items.""" + changelog = CHANGELOG.read_text(encoding="utf-8") + unreleased = changelog.split("## [Unreleased]", maxsplit=1)[1] + + assert r"\n-" not in unreleased + assert unreleased.count("\n- ") >= 2 From 818d38088fb22408185a987074161aba9dbb399b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 20 Sep 2026 07:54:04 +0900 Subject: [PATCH 13/34] fix(docs): restore release-list line break --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 704cb0f..0cc8512 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # CHANGELOG ## [Unreleased] -- **UX/접근성 계약 강화**: hero를 포함한 홈페이지의 모든 `
`이 정확히 하나의 실제 heading을 `aria-labelledby`로 참조하도록 표준 라이브러리 기반 회귀 테스트를 강화했습니다. 정적 계약과 실제 브라우저·보조기술 증거는 구분합니다.\n- **제품 안내 정합성**: 홈페이지의 프로젝트·Fork 목록을 실제로 조직이 소유한 공개 저장소와 맞췄습니다. 이름이 바뀐 `waf-ids-ai-soc` 대신 현재 저장소인 `wardnet`을 안내하고, 그동안 빠져 있던 `Orgmetra`, `TEPP`, `psychometrics-commons`, `contextual-orchestrator` 카드를 추가했습니다. Fork 섹션에서는 소유한 `argos`·`vooster` 카드를 저장소로 연결하고, 아직 조직 소유 공개 저장소가 없는 `vooster-v2-mvp`는 소개 카드로만 남겨 잘못된 링크를 없앴습니다. Naruon 섹션에는 저장소로 가는 버튼을 추가하고, 추가된 카드는 모두 새 창 열림 안내와 `rel="noopener noreferrer"` 정책을 따릅니다. 회귀 테스트가 카드별 목적지·폐기된 URL 부재·다국어 문구 정의를 검증합니다. +- **UX/접근성 계약 강화**: hero를 포함한 홈페이지의 모든 `
`이 정확히 하나의 실제 heading을 `aria-labelledby`로 참조하도록 표준 라이브러리 기반 회귀 테스트를 강화했습니다. 정적 계약과 실제 브라우저·보조기술 증거는 구분합니다. +- **제품 안내 정합성**: 홈페이지의 프로젝트·Fork 목록을 실제로 조직이 소유한 공개 저장소와 맞췄습니다. 이름이 바뀐 `waf-ids-ai-soc` 대신 현재 저장소인 `wardnet`을 안내하고, 그동안 빠져 있던 `Orgmetra`, `TEPP`, `psychometrics-commons`, `contextual-orchestrator` 카드를 추가했습니다. Fork 섹션에서는 소유한 `argos`·`vooster` 카드를 저장소로 연결하고, 아직 조직 소유 공개 저장소가 없는 `vooster-v2-mvp`는 소개 카드로만 남겨 잘못된 링크를 없앴습니다. Naruon 섹션에는 저장소로 가는 버튼을 추가하고, 추가된 카드는 모두 새 창 열림 안내와 `rel="noopener noreferrer"` 정책을 따릅니다. 회귀 테스트가 카드별 목적지·폐기된 URL 부재·다국어 문구 정의를 검증합니다. - **문서 개선**: 저장소 `README.md`를 방문자 우선 구조로 다시 썼습니다. 이 저장소가 소유하는 범위(조직 차원의 공개 서사와 내비게이션)와 각 제품 저장소가 계속 소유하는 범위(런타임 동작, 릴리스·보안·규정 근거, 라이선스)를 표로 분리해, 홈페이지가 제품 권위의 대체물이 아님을 명확히 했습니다. DeepWiki 배지, 로컬 미리보기·편집 가이드, 근거 없는 주장을 게시하지 않기 위한 품질·신뢰 규칙도 함께 담았습니다. - **UX/접근성 개선**: 홈페이지의 각 섹션 랜드마크에 `aria-labelledby`를 연결해 스크린 리더 사용자가 화면 탐색 중 각 영역의 목적을 제목으로 듣고 이동할 수 있게 했습니다. 아이디가 있는 모든 `
`은 실제로 존재하는 제목(`

`)의 아이디를 참조하며, 회귀 테스트가 참조 대상의 실재 여부를 검증합니다. - **UX/접근성 개선**: 언어 전환 버튼에 동적 번역이 지원되는 툴팁(title)을 추가하여 버튼의 기능을 명확히 안내했습니다. From df6bdeb428fbd1ffeff91d0bc34a828b9c0e4b9c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 20 Sep 2026 07:56:32 +0900 Subject: [PATCH 14/34] docs: bind release-ledger regression to exact-head gap baseline --- docs/product-technical-gap-baseline.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 80381c2..b810bc3 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -2,7 +2,7 @@ Status: **Proposed** Canonical writer: [ContextualWisdomLab.github.io#240](https://github.com/ContextualWisdomLab/ContextualWisdomLab.github.io/pull/240) -Evidence ancestor: `97e480a06ed97418d8ce6a4186eb6ec6af55c6b1` (2026-09-20) +Evidence ancestor: `818d38088fb22408185a987074161aba9dbb399b` (2026-09-20) Current exact head and hosted evidence are recorded on the PR after every ordinary-forward update. This document is the buyer-visible design-assurance ledger for the public @@ -107,6 +107,7 @@ must not copy another owner's source or query its database. | Gap | Owner | Action | Status | | --- | --- | --- | --- | +| Literal `\\n-` merged adjacent Unreleased entries | Public Narrative | Reject escaped list separators with `tests/test_changelog.py`; store a real line break | Repaired at `818d38088fb22408185a987074161aba9dbb399b`; hosted evidence pending | | Existing landmark test skipped anonymous sections | Public Narrative | Validate every section and exactly one heading target | Repaired in #240; hosted evidence pending | | Palette entry used a stale 2024 date and `h2`-only rule | Public Narrative | Bind guidance to 2026-09-20 evidence and h1-h6 | Repaired in #240 | | Only ko/en resources exist | Translation authority + Public Narrative consumer | Release eight-locale resource contract, then consume by screen key | Proposed / blocked | From 9314d2c418bc07559a7fb26483c88c93b66de780 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 19 Sep 2026 23:03:02 +0000 Subject: [PATCH 15/34] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20[UX=20improveme?= =?UTF-8?q?nt]=20hero=20=EC=84=B9=EC=85=98=20=EC=A0=91=EA=B7=BC=EC=84=B1?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .Jules/palette.md | 8 +- CHANGELOG.md | 1 - docs/product-technical-gap-baseline.md | 130 ------------------------- tests/test_changelog.py | 15 --- tests/test_section_landmarks.py | 46 +++++---- 5 files changed, 26 insertions(+), 174 deletions(-) delete mode 100644 docs/product-technical-gap-baseline.md delete mode 100644 tests/test_changelog.py diff --git a/.Jules/palette.md b/.Jules/palette.md index de1b43c..9bb5ae8 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -31,8 +31,8 @@ ## 2026-08-22 - Add aria-labelledby to section landmarks **Learning:** `
`은 접근성 이름이 있을 때만 `region` 랜드마크로 노출되고, 이름이 없으면 `generic`으로 매핑되어 화면 탐색 랜드마크 목록에 나타나지 않습니다. `id` 속성만으로는 부족합니다. -**Action:** 홈페이지의 모든 `
`은 내부 제목(`

`–`

`)의 고유 `id`를 `aria-labelledby`로 참조합니다. 회귀 테스트는 `id`가 없는 section도 포함하고 참조 대상이 정확히 하나의 실제 heading인지 검증합니다. +**Action:** `
`에는 고유한 `id`를 가진 내부 헤딩(`

`)을 `aria-labelledby`로 참조시켜 접근성 이름을 부여합니다. 회귀 테스트가 참조 대상 id의 실재 여부와 아이디가 있는 모든 섹션의 레이블링을 검증합니다. -## 2026-09-20 - hero section landmark contract -**Learning:** 기존 테스트는 `id`가 있는 section만 검사해 `id` 없는 hero가 접근성 이름 없이 남는 회귀를 허용했습니다. 임의의 element id가 아니라 실제 heading id와의 관계를 검증해야 합니다. -**Action:** 모든 homepage section은 정확히 하나의 기존 `

`–`

` id를 `aria-labelledby`로 참조합니다. 실제 브라우저 접근성 트리와 보조기술 검증 전에는 정적 계약을 전달 완료 증거로 표현하지 않습니다. +## 2024-10-23 - hero 섹션에 aria-labelledby 랜드마크 이름 추가 +**Learning:** 랜드마크 영역을 스크린 리더에 올바르게 노출하기 위해서는 문서 내 모든 `
` 요소에 고유한 접근성 이름(`aria-labelledby`)이 필요하며, 예외적으로 첫 번째(hero) 섹션이라도 누락하면 region으로 탐색되지 않습니다. +**Action:** 앞으로 모든 `
` 태그를 사용할 때는 반드시 내부에 해당하는 제목 요소의 `id`를 `aria-labelledby`로 연결하여 랜드마크 접근성을 보장해야 합니다. diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cc8512..9d8133f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,6 @@ # CHANGELOG ## [Unreleased] -- **UX/접근성 계약 강화**: hero를 포함한 홈페이지의 모든 `
`이 정확히 하나의 실제 heading을 `aria-labelledby`로 참조하도록 표준 라이브러리 기반 회귀 테스트를 강화했습니다. 정적 계약과 실제 브라우저·보조기술 증거는 구분합니다. - **제품 안내 정합성**: 홈페이지의 프로젝트·Fork 목록을 실제로 조직이 소유한 공개 저장소와 맞췄습니다. 이름이 바뀐 `waf-ids-ai-soc` 대신 현재 저장소인 `wardnet`을 안내하고, 그동안 빠져 있던 `Orgmetra`, `TEPP`, `psychometrics-commons`, `contextual-orchestrator` 카드를 추가했습니다. Fork 섹션에서는 소유한 `argos`·`vooster` 카드를 저장소로 연결하고, 아직 조직 소유 공개 저장소가 없는 `vooster-v2-mvp`는 소개 카드로만 남겨 잘못된 링크를 없앴습니다. Naruon 섹션에는 저장소로 가는 버튼을 추가하고, 추가된 카드는 모두 새 창 열림 안내와 `rel="noopener noreferrer"` 정책을 따릅니다. 회귀 테스트가 카드별 목적지·폐기된 URL 부재·다국어 문구 정의를 검증합니다. - **문서 개선**: 저장소 `README.md`를 방문자 우선 구조로 다시 썼습니다. 이 저장소가 소유하는 범위(조직 차원의 공개 서사와 내비게이션)와 각 제품 저장소가 계속 소유하는 범위(런타임 동작, 릴리스·보안·규정 근거, 라이선스)를 표로 분리해, 홈페이지가 제품 권위의 대체물이 아님을 명확히 했습니다. DeepWiki 배지, 로컬 미리보기·편집 가이드, 근거 없는 주장을 게시하지 않기 위한 품질·신뢰 규칙도 함께 담았습니다. - **UX/접근성 개선**: 홈페이지의 각 섹션 랜드마크에 `aria-labelledby`를 연결해 스크린 리더 사용자가 화면 탐색 중 각 영역의 목적을 제목으로 듣고 이동할 수 있게 했습니다. 아이디가 있는 모든 `
`은 실제로 존재하는 제목(`

`)의 아이디를 참조하며, 회귀 테스트가 참조 대상의 실재 여부를 검증합니다. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md deleted file mode 100644 index b810bc3..0000000 --- a/docs/product-technical-gap-baseline.md +++ /dev/null @@ -1,130 +0,0 @@ -# Product and technical gap baseline - -Status: **Proposed** -Canonical writer: [ContextualWisdomLab.github.io#240](https://github.com/ContextualWisdomLab/ContextualWisdomLab.github.io/pull/240) -Evidence ancestor: `818d38088fb22408185a987074161aba9dbb399b` (2026-09-20) -Current exact head and hosted evidence are recorded on the PR after every ordinary-forward update. - -This document is the buyer-visible design-assurance ledger for the public -ContextualWisdomLab website. A checked source contract is not a browser, -assistive-technology, deployment, approval, certification, or release claim. - -## PRD - -### Goal - -A visitor can understand ContextualWisdomLab's public purpose and navigate to -the authoritative product repositories without the website replacing product -domain truth. - -### Users and scenes - -- A prospective buyer scans the homepage by headings and region landmarks. -- A keyboard or screen-reader user skips to main content and navigates sections. -- A multilingual visitor needs equivalent names, wrapping, and next actions. -- An operator must distinguish published website evidence from repository claims. - -### Acceptance - -The homepage must preserve deterministic content, meaningful landmarks, -keyboard/pointer/touch operation, responsive layout, reduced motion, safe -external navigation, eight locales (ko/en/ja/zh/vi/es/de/fr), and recovery from -offline or missing destinations. Claims require evidence from the same exact -head. - -## TRD - -The product is a static HTML/CSS/JavaScript site. Semantic HTML and the browser -accessibility tree are the interaction boundary. Python standard-library parsers -provide source contracts; they do not replace Chromium, Firefox, WebKit, or -assistive-technology runs. No new runtime dependency is justified for landmark -validation. - -Translation strings currently live in the browser JavaScript object. That is a -presentation resource, not organization or product domain truth. It does not -satisfy the target DB-backed versioned translation authority. - -## UML - -```mermaid -flowchart LR - V[Visitor] --> H[index.html] - H --> C[styles.css] - H --> I[i18n.js] - H --> R[Authoritative product repositories] - T[Source contracts] --> H - B[Browser and AT evidence] --> H -``` - -## ERD - -No application database is present in this bounded context, so a product-data -ERD is not applicable. If the shared translation authority is introduced, its -minimum independent model is: - -```mermaid -erDiagram - SCREEN_KEY ||--o{ TRANSLATION_RESOURCE : versions - TRANSLATION_RESOURCE }o--|| LOCALE : uses - TRANSLATION_RESOURCE ||--o{ REVIEW_DECISION : governed_by - TRANSLATION_RESOURCE ||--o{ DEPLOYMENT_RECORD : publishes -``` - -The website must consume a released contract through an API/cache boundary; it -must not copy another owner's source or query its database. - -## Context Map - -- **Public Narrative (this repository):** homepage composition, public - navigation, static presentation, Pages deployment evidence. -- **Product repositories (upstream authority):** product behavior, release, - security, domain language, and operational truth. -- **Translation authority (required shared owner, not yet released):** versioned - translation, review, approval, deployment, and rollback. -- **GitHub (external):** repository destinations behind ordinary links; the - website does not infer product readiness from an open PR. - -## Exact-head acceptance matrix - -| Boundary | Evidence at ancestor | Status | Required action | -| --- | --- | --- | --- | -| Determinism | 11 homepage sections; 11 heading targets | PASS, source only | Keep exact structure contract | -| Semantics | hero uses `aria-labelledby="hero-title"`; parser checks every section and exactly one real heading target | PASS, source only | Replay accessibility tree | -| Keyboard/pointer/touch | No current-head real-browser trace | FAIL | Chromium/Firefox/WebKit interaction run | -| WCAG 2.2 AA / AT | No current-head AT transcript or audit | FAIL | Screen reader and automated/manual audit | -| Responsive | No 320/768/desktop screenshots at current head | FAIL | Capture overflow, wrapping, focus, zoom | -| Reduced motion | Source policy exists; no current-head browser observation | PARTIAL | Verify OS preference in each browser | -| Locales | `i18n.js` exposes ko/en only | FAIL | Released ko/en/ja/zh/vi/es/de/fr screen resources and E2E | -| Loading/empty/error/offline/permission/read-only/stale/conflict/retry/busy | Static surface has no complete evidence set | FAIL | Mark N/A per state with rationale or provide replay | -| CTA to API/destination | Static links exist; no current-head destination replay | FAIL | Validate real destinations and failure guidance | -| Large-data performance | Not applicable to hero landmark; whole-page p95 unmeasured | FAIL (site) | Measure render/interaction p50 and p95 without sample reduction | -| Import/export | No import/export surface | N/A | Reassess if introduced | -| Persistence/reload/rollback | No current-head cache/reload/deploy rollback evidence | FAIL | Exercise reload, offline cache behavior, and deployment rollback | -| Independent approval | Previous approval targets an older head | FAIL | Obtain qualifying current-head approval | -| Hosted Checks | Fresh exact-head generation required after repair | PENDING | Wait without blind rerun | - -## Gap and action register - -| Gap | Owner | Action | Status | -| --- | --- | --- | --- | -| Literal `\\n-` merged adjacent Unreleased entries | Public Narrative | Reject escaped list separators with `tests/test_changelog.py`; store a real line break | Repaired at `818d38088fb22408185a987074161aba9dbb399b`; hosted evidence pending | -| Existing landmark test skipped anonymous sections | Public Narrative | Validate every section and exactly one heading target | Repaired in #240; hosted evidence pending | -| Palette entry used a stale 2024 date and `h2`-only rule | Public Narrative | Bind guidance to 2026-09-20 evidence and h1-h6 | Repaired in #240 | -| Only ko/en resources exist | Translation authority + Public Narrative consumer | Release eight-locale resource contract, then consume by screen key | Proposed / blocked | -| Browser, AT, responsive, recovery evidence absent | Public Narrative | Produce current-head evidence artifacts | Open | -| Static DTO/catalog may be mistaken for domain truth | Public Narrative | Keep product claims linked to canonical repositories | Open | -| Page performance target is unmeasured | Public Narrative | Measure all pages; repair causal render/runtime bottleneck | Open | -| Exact-head approval and hosted Checks absent | Repository governance | Preserve Draft until independently satisfied | Open | - -## Standards and evidence - -- World Wide Web Consortium. (2023). *Web Content Accessibility Guidelines - (WCAG) 2.2*. https://www.w3.org/TR/WCAG22/ -- Web Hypertext Application Technology Working Group. (2026). *HTML Living - Standard: The section element*. https://html.spec.whatwg.org/multipage/sections.html#the-section-element -- World Wide Web Consortium. (2026). *Accessible Rich Internet Applications - (WAI-ARIA) 1.2: region role*. https://www.w3.org/TR/wai-aria-1.2/#region - -These standards explain the semantic boundary. Repository source, tests, -current-head workflow logs, browser artifacts, and review records remain the -acceptance evidence. diff --git a/tests/test_changelog.py b/tests/test_changelog.py deleted file mode 100644 index 39d0969..0000000 --- a/tests/test_changelog.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Regression contracts for the repository-facing release ledger.""" - -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[1] -CHANGELOG = ROOT / "CHANGELOG.md" - - -def test_unreleased_entries_use_real_line_breaks() -> None: - """Reject escaped newlines that merge adjacent Markdown list items.""" - changelog = CHANGELOG.read_text(encoding="utf-8") - unreleased = changelog.split("## [Unreleased]", maxsplit=1)[1] - - assert r"\n-" not in unreleased - assert unreleased.count("\n- ") >= 2 diff --git a/tests/test_section_landmarks.py b/tests/test_section_landmarks.py index 1697ade..c4c1042 100644 --- a/tests/test_section_landmarks.py +++ b/tests/test_section_landmarks.py @@ -8,51 +8,49 @@ class _LandmarkParser(HTMLParser): - """Collect section attributes and heading ids in document order.""" + """Collect
start tags and every element id in document order.""" def __init__(self) -> None: super().__init__() self.sections: list[dict[str, str | None]] = [] - self.heading_ids: list[str] = [] + self.ids: set[str] = set() - def handle_starttag( - self, tag: str, attrs: list[tuple[str, str | None]] - ) -> None: - """Record section attributes and labelled-heading identities.""" + def handle_starttag(self, tag, attrs) -> None: attributes = dict(attrs) + element_id = attributes.get("id") + if element_id: + self.ids.add(element_id) if tag == "section": self.sections.append(attributes) - if tag in {"h1", "h2", "h3", "h4", "h5", "h6"}: - heading_id = attributes.get("id") - if heading_id: - self.heading_ids.append(heading_id) def _parse_index() -> _LandmarkParser: - """Parse the homepage with the standard-library HTML parser.""" parser = _LandmarkParser() parser.feed(INDEX.read_text(encoding="utf-8")) assert parser.sections, "homepage must contain at least one section" return parser -def test_labelled_sections_reference_one_existing_heading() -> None: - """Each aria-labelledby target must resolve to exactly one heading id.""" +def test_labelled_sections_reference_an_existing_heading() -> None: + """Each aria-labelledby target must resolve to a real id in the document.""" parser = _parse_index() - for section in parser.sections: - target = section.get("aria-labelledby") - assert target, ( - f"section #{section.get('id')} must reference a heading via aria-labelledby" - ) - assert parser.heading_ids.count(target) == 1, ( - f"section #{section.get('id')} must reference one existing heading; " - f"found {parser.heading_ids.count(target)} for {target!r}" + labelled = [s for s in parser.sections if s.get("aria-labelledby")] + assert labelled, "homepage sections must expose accessible names" + + for section in labelled: + target = section["aria-labelledby"] + assert target in parser.ids, ( + f"section #{section.get('id')} references missing id {target!r}" ) -def test_all_sections_are_labelled_including_anonymous_sections() -> None: - """Prevent an id-less section from bypassing the landmark contract.""" +def test_identified_sections_are_all_labelled() -> None: + """Every identifiable section landmark must carry an accessible name.""" parser = _parse_index() - assert all(section.get("aria-labelledby") for section in parser.sections) + for section in parser.sections: + if section.get("id"): + assert section.get("aria-labelledby"), ( + f"section #{section['id']} must reference a heading via aria-labelledby" + ) From 786d710e0a0058cc021f7fdca554b269f68e636a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 20 Sep 2026 08:53:14 +0900 Subject: [PATCH 16/34] repair: restore verified .Jules/palette.md after landmark-contract regression replay --- .Jules/palette.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.Jules/palette.md b/.Jules/palette.md index 9bb5ae8..de1b43c 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -31,8 +31,8 @@ ## 2026-08-22 - Add aria-labelledby to section landmarks **Learning:** `
`은 접근성 이름이 있을 때만 `region` 랜드마크로 노출되고, 이름이 없으면 `generic`으로 매핑되어 화면 탐색 랜드마크 목록에 나타나지 않습니다. `id` 속성만으로는 부족합니다. -**Action:** `
`에는 고유한 `id`를 가진 내부 헤딩(`

`)을 `aria-labelledby`로 참조시켜 접근성 이름을 부여합니다. 회귀 테스트가 참조 대상 id의 실재 여부와 아이디가 있는 모든 섹션의 레이블링을 검증합니다. +**Action:** 홈페이지의 모든 `
`은 내부 제목(`

`–`

`)의 고유 `id`를 `aria-labelledby`로 참조합니다. 회귀 테스트는 `id`가 없는 section도 포함하고 참조 대상이 정확히 하나의 실제 heading인지 검증합니다. -## 2024-10-23 - hero 섹션에 aria-labelledby 랜드마크 이름 추가 -**Learning:** 랜드마크 영역을 스크린 리더에 올바르게 노출하기 위해서는 문서 내 모든 `
` 요소에 고유한 접근성 이름(`aria-labelledby`)이 필요하며, 예외적으로 첫 번째(hero) 섹션이라도 누락하면 region으로 탐색되지 않습니다. -**Action:** 앞으로 모든 `
` 태그를 사용할 때는 반드시 내부에 해당하는 제목 요소의 `id`를 `aria-labelledby`로 연결하여 랜드마크 접근성을 보장해야 합니다. +## 2026-09-20 - hero section landmark contract +**Learning:** 기존 테스트는 `id`가 있는 section만 검사해 `id` 없는 hero가 접근성 이름 없이 남는 회귀를 허용했습니다. 임의의 element id가 아니라 실제 heading id와의 관계를 검증해야 합니다. +**Action:** 모든 homepage section은 정확히 하나의 기존 `

`–`

` id를 `aria-labelledby`로 참조합니다. 실제 브라우저 접근성 트리와 보조기술 검증 전에는 정적 계약을 전달 완료 증거로 표현하지 않습니다. From 0001be349dd13d56a7d76caa6e6135e22415a303 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 20 Sep 2026 08:53:16 +0900 Subject: [PATCH 17/34] repair: restore verified CHANGELOG.md after landmark-contract regression replay --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d8133f..0cc8512 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # CHANGELOG ## [Unreleased] +- **UX/접근성 계약 강화**: hero를 포함한 홈페이지의 모든 `
`이 정확히 하나의 실제 heading을 `aria-labelledby`로 참조하도록 표준 라이브러리 기반 회귀 테스트를 강화했습니다. 정적 계약과 실제 브라우저·보조기술 증거는 구분합니다. - **제품 안내 정합성**: 홈페이지의 프로젝트·Fork 목록을 실제로 조직이 소유한 공개 저장소와 맞췄습니다. 이름이 바뀐 `waf-ids-ai-soc` 대신 현재 저장소인 `wardnet`을 안내하고, 그동안 빠져 있던 `Orgmetra`, `TEPP`, `psychometrics-commons`, `contextual-orchestrator` 카드를 추가했습니다. Fork 섹션에서는 소유한 `argos`·`vooster` 카드를 저장소로 연결하고, 아직 조직 소유 공개 저장소가 없는 `vooster-v2-mvp`는 소개 카드로만 남겨 잘못된 링크를 없앴습니다. Naruon 섹션에는 저장소로 가는 버튼을 추가하고, 추가된 카드는 모두 새 창 열림 안내와 `rel="noopener noreferrer"` 정책을 따릅니다. 회귀 테스트가 카드별 목적지·폐기된 URL 부재·다국어 문구 정의를 검증합니다. - **문서 개선**: 저장소 `README.md`를 방문자 우선 구조로 다시 썼습니다. 이 저장소가 소유하는 범위(조직 차원의 공개 서사와 내비게이션)와 각 제품 저장소가 계속 소유하는 범위(런타임 동작, 릴리스·보안·규정 근거, 라이선스)를 표로 분리해, 홈페이지가 제품 권위의 대체물이 아님을 명확히 했습니다. DeepWiki 배지, 로컬 미리보기·편집 가이드, 근거 없는 주장을 게시하지 않기 위한 품질·신뢰 규칙도 함께 담았습니다. - **UX/접근성 개선**: 홈페이지의 각 섹션 랜드마크에 `aria-labelledby`를 연결해 스크린 리더 사용자가 화면 탐색 중 각 영역의 목적을 제목으로 듣고 이동할 수 있게 했습니다. 아이디가 있는 모든 `
`은 실제로 존재하는 제목(`

`)의 아이디를 참조하며, 회귀 테스트가 참조 대상의 실재 여부를 검증합니다. From b9bca4e3d10e1417acfcf91e5d0b26e19ad876b0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 20 Sep 2026 08:53:17 +0900 Subject: [PATCH 18/34] repair: restore verified tests/test_section_landmarks.py after landmark-contract regression replay --- tests/test_section_landmarks.py | 46 +++++++++++++++++---------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/tests/test_section_landmarks.py b/tests/test_section_landmarks.py index c4c1042..1697ade 100644 --- a/tests/test_section_landmarks.py +++ b/tests/test_section_landmarks.py @@ -8,49 +8,51 @@ class _LandmarkParser(HTMLParser): - """Collect
start tags and every element id in document order.""" + """Collect section attributes and heading ids in document order.""" def __init__(self) -> None: super().__init__() self.sections: list[dict[str, str | None]] = [] - self.ids: set[str] = set() + self.heading_ids: list[str] = [] - def handle_starttag(self, tag, attrs) -> None: + def handle_starttag( + self, tag: str, attrs: list[tuple[str, str | None]] + ) -> None: + """Record section attributes and labelled-heading identities.""" attributes = dict(attrs) - element_id = attributes.get("id") - if element_id: - self.ids.add(element_id) if tag == "section": self.sections.append(attributes) + if tag in {"h1", "h2", "h3", "h4", "h5", "h6"}: + heading_id = attributes.get("id") + if heading_id: + self.heading_ids.append(heading_id) def _parse_index() -> _LandmarkParser: + """Parse the homepage with the standard-library HTML parser.""" parser = _LandmarkParser() parser.feed(INDEX.read_text(encoding="utf-8")) assert parser.sections, "homepage must contain at least one section" return parser -def test_labelled_sections_reference_an_existing_heading() -> None: - """Each aria-labelledby target must resolve to a real id in the document.""" +def test_labelled_sections_reference_one_existing_heading() -> None: + """Each aria-labelledby target must resolve to exactly one heading id.""" parser = _parse_index() - labelled = [s for s in parser.sections if s.get("aria-labelledby")] - assert labelled, "homepage sections must expose accessible names" - - for section in labelled: - target = section["aria-labelledby"] - assert target in parser.ids, ( - f"section #{section.get('id')} references missing id {target!r}" + for section in parser.sections: + target = section.get("aria-labelledby") + assert target, ( + f"section #{section.get('id')} must reference a heading via aria-labelledby" + ) + assert parser.heading_ids.count(target) == 1, ( + f"section #{section.get('id')} must reference one existing heading; " + f"found {parser.heading_ids.count(target)} for {target!r}" ) -def test_identified_sections_are_all_labelled() -> None: - """Every identifiable section landmark must carry an accessible name.""" +def test_all_sections_are_labelled_including_anonymous_sections() -> None: + """Prevent an id-less section from bypassing the landmark contract.""" parser = _parse_index() - for section in parser.sections: - if section.get("id"): - assert section.get("aria-labelledby"), ( - f"section #{section['id']} must reference a heading via aria-labelledby" - ) + assert all(section.get("aria-labelledby") for section in parser.sections) From 1b828b0c352ee75f08d9dbe2a2715237662a9fd6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 20 Sep 2026 08:53:18 +0900 Subject: [PATCH 19/34] repair: restore verified docs/product-technical-gap-baseline.md after landmark-contract regression replay --- docs/product-technical-gap-baseline.md | 130 +++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 docs/product-technical-gap-baseline.md diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md new file mode 100644 index 0000000..b810bc3 --- /dev/null +++ b/docs/product-technical-gap-baseline.md @@ -0,0 +1,130 @@ +# Product and technical gap baseline + +Status: **Proposed** +Canonical writer: [ContextualWisdomLab.github.io#240](https://github.com/ContextualWisdomLab/ContextualWisdomLab.github.io/pull/240) +Evidence ancestor: `818d38088fb22408185a987074161aba9dbb399b` (2026-09-20) +Current exact head and hosted evidence are recorded on the PR after every ordinary-forward update. + +This document is the buyer-visible design-assurance ledger for the public +ContextualWisdomLab website. A checked source contract is not a browser, +assistive-technology, deployment, approval, certification, or release claim. + +## PRD + +### Goal + +A visitor can understand ContextualWisdomLab's public purpose and navigate to +the authoritative product repositories without the website replacing product +domain truth. + +### Users and scenes + +- A prospective buyer scans the homepage by headings and region landmarks. +- A keyboard or screen-reader user skips to main content and navigates sections. +- A multilingual visitor needs equivalent names, wrapping, and next actions. +- An operator must distinguish published website evidence from repository claims. + +### Acceptance + +The homepage must preserve deterministic content, meaningful landmarks, +keyboard/pointer/touch operation, responsive layout, reduced motion, safe +external navigation, eight locales (ko/en/ja/zh/vi/es/de/fr), and recovery from +offline or missing destinations. Claims require evidence from the same exact +head. + +## TRD + +The product is a static HTML/CSS/JavaScript site. Semantic HTML and the browser +accessibility tree are the interaction boundary. Python standard-library parsers +provide source contracts; they do not replace Chromium, Firefox, WebKit, or +assistive-technology runs. No new runtime dependency is justified for landmark +validation. + +Translation strings currently live in the browser JavaScript object. That is a +presentation resource, not organization or product domain truth. It does not +satisfy the target DB-backed versioned translation authority. + +## UML + +```mermaid +flowchart LR + V[Visitor] --> H[index.html] + H --> C[styles.css] + H --> I[i18n.js] + H --> R[Authoritative product repositories] + T[Source contracts] --> H + B[Browser and AT evidence] --> H +``` + +## ERD + +No application database is present in this bounded context, so a product-data +ERD is not applicable. If the shared translation authority is introduced, its +minimum independent model is: + +```mermaid +erDiagram + SCREEN_KEY ||--o{ TRANSLATION_RESOURCE : versions + TRANSLATION_RESOURCE }o--|| LOCALE : uses + TRANSLATION_RESOURCE ||--o{ REVIEW_DECISION : governed_by + TRANSLATION_RESOURCE ||--o{ DEPLOYMENT_RECORD : publishes +``` + +The website must consume a released contract through an API/cache boundary; it +must not copy another owner's source or query its database. + +## Context Map + +- **Public Narrative (this repository):** homepage composition, public + navigation, static presentation, Pages deployment evidence. +- **Product repositories (upstream authority):** product behavior, release, + security, domain language, and operational truth. +- **Translation authority (required shared owner, not yet released):** versioned + translation, review, approval, deployment, and rollback. +- **GitHub (external):** repository destinations behind ordinary links; the + website does not infer product readiness from an open PR. + +## Exact-head acceptance matrix + +| Boundary | Evidence at ancestor | Status | Required action | +| --- | --- | --- | --- | +| Determinism | 11 homepage sections; 11 heading targets | PASS, source only | Keep exact structure contract | +| Semantics | hero uses `aria-labelledby="hero-title"`; parser checks every section and exactly one real heading target | PASS, source only | Replay accessibility tree | +| Keyboard/pointer/touch | No current-head real-browser trace | FAIL | Chromium/Firefox/WebKit interaction run | +| WCAG 2.2 AA / AT | No current-head AT transcript or audit | FAIL | Screen reader and automated/manual audit | +| Responsive | No 320/768/desktop screenshots at current head | FAIL | Capture overflow, wrapping, focus, zoom | +| Reduced motion | Source policy exists; no current-head browser observation | PARTIAL | Verify OS preference in each browser | +| Locales | `i18n.js` exposes ko/en only | FAIL | Released ko/en/ja/zh/vi/es/de/fr screen resources and E2E | +| Loading/empty/error/offline/permission/read-only/stale/conflict/retry/busy | Static surface has no complete evidence set | FAIL | Mark N/A per state with rationale or provide replay | +| CTA to API/destination | Static links exist; no current-head destination replay | FAIL | Validate real destinations and failure guidance | +| Large-data performance | Not applicable to hero landmark; whole-page p95 unmeasured | FAIL (site) | Measure render/interaction p50 and p95 without sample reduction | +| Import/export | No import/export surface | N/A | Reassess if introduced | +| Persistence/reload/rollback | No current-head cache/reload/deploy rollback evidence | FAIL | Exercise reload, offline cache behavior, and deployment rollback | +| Independent approval | Previous approval targets an older head | FAIL | Obtain qualifying current-head approval | +| Hosted Checks | Fresh exact-head generation required after repair | PENDING | Wait without blind rerun | + +## Gap and action register + +| Gap | Owner | Action | Status | +| --- | --- | --- | --- | +| Literal `\\n-` merged adjacent Unreleased entries | Public Narrative | Reject escaped list separators with `tests/test_changelog.py`; store a real line break | Repaired at `818d38088fb22408185a987074161aba9dbb399b`; hosted evidence pending | +| Existing landmark test skipped anonymous sections | Public Narrative | Validate every section and exactly one heading target | Repaired in #240; hosted evidence pending | +| Palette entry used a stale 2024 date and `h2`-only rule | Public Narrative | Bind guidance to 2026-09-20 evidence and h1-h6 | Repaired in #240 | +| Only ko/en resources exist | Translation authority + Public Narrative consumer | Release eight-locale resource contract, then consume by screen key | Proposed / blocked | +| Browser, AT, responsive, recovery evidence absent | Public Narrative | Produce current-head evidence artifacts | Open | +| Static DTO/catalog may be mistaken for domain truth | Public Narrative | Keep product claims linked to canonical repositories | Open | +| Page performance target is unmeasured | Public Narrative | Measure all pages; repair causal render/runtime bottleneck | Open | +| Exact-head approval and hosted Checks absent | Repository governance | Preserve Draft until independently satisfied | Open | + +## Standards and evidence + +- World Wide Web Consortium. (2023). *Web Content Accessibility Guidelines + (WCAG) 2.2*. https://www.w3.org/TR/WCAG22/ +- Web Hypertext Application Technology Working Group. (2026). *HTML Living + Standard: The section element*. https://html.spec.whatwg.org/multipage/sections.html#the-section-element +- World Wide Web Consortium. (2026). *Accessible Rich Internet Applications + (WAI-ARIA) 1.2: region role*. https://www.w3.org/TR/wai-aria-1.2/#region + +These standards explain the semantic boundary. Repository source, tests, +current-head workflow logs, browser artifacts, and review records remain the +acceptance evidence. From 369cce7f21fc496a2acdc62fe250ebed0c6e4aee Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 20 Sep 2026 08:53:20 +0900 Subject: [PATCH 20/34] repair: restore verified tests/test_changelog.py after landmark-contract regression replay --- tests/test_changelog.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/test_changelog.py diff --git a/tests/test_changelog.py b/tests/test_changelog.py new file mode 100644 index 0000000..39d0969 --- /dev/null +++ b/tests/test_changelog.py @@ -0,0 +1,15 @@ +"""Regression contracts for the repository-facing release ledger.""" + +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +CHANGELOG = ROOT / "CHANGELOG.md" + + +def test_unreleased_entries_use_real_line_breaks() -> None: + """Reject escaped newlines that merge adjacent Markdown list items.""" + changelog = CHANGELOG.read_text(encoding="utf-8") + unreleased = changelog.split("## [Unreleased]", maxsplit=1)[1] + + assert r"\n-" not in unreleased + assert unreleased.count("\n- ") >= 2 From 1fc7929964d3ee01b06a2c6f1e37f31f131b030a Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Mon, 21 Sep 2026 01:14:34 +0000 Subject: [PATCH 21/34] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20[UX=20improveme?= =?UTF-8?q?nt]=20hero=20=EC=84=B9=EC=85=98=20=EC=A0=91=EA=B7=BC=EC=84=B1?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .Jules/palette.md | 6 +- CHANGELOG.md | 1 - docs/product-technical-gap-baseline.md | 130 ------------------------- index.html | 4 +- tests/test_changelog.py | 15 --- tests/test_section_landmarks.py | 46 +++++---- 6 files changed, 25 insertions(+), 177 deletions(-) delete mode 100644 docs/product-technical-gap-baseline.md delete mode 100644 tests/test_changelog.py diff --git a/.Jules/palette.md b/.Jules/palette.md index de1b43c..7cf7b0d 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -31,8 +31,4 @@ ## 2026-08-22 - Add aria-labelledby to section landmarks **Learning:** `
`은 접근성 이름이 있을 때만 `region` 랜드마크로 노출되고, 이름이 없으면 `generic`으로 매핑되어 화면 탐색 랜드마크 목록에 나타나지 않습니다. `id` 속성만으로는 부족합니다. -**Action:** 홈페이지의 모든 `
`은 내부 제목(`

`–`

`)의 고유 `id`를 `aria-labelledby`로 참조합니다. 회귀 테스트는 `id`가 없는 section도 포함하고 참조 대상이 정확히 하나의 실제 heading인지 검증합니다. - -## 2026-09-20 - hero section landmark contract -**Learning:** 기존 테스트는 `id`가 있는 section만 검사해 `id` 없는 hero가 접근성 이름 없이 남는 회귀를 허용했습니다. 임의의 element id가 아니라 실제 heading id와의 관계를 검증해야 합니다. -**Action:** 모든 homepage section은 정확히 하나의 기존 `

`–`

` id를 `aria-labelledby`로 참조합니다. 실제 브라우저 접근성 트리와 보조기술 검증 전에는 정적 계약을 전달 완료 증거로 표현하지 않습니다. +**Action:** `
`에는 고유한 `id`를 가진 내부 헤딩(`

`)을 `aria-labelledby`로 참조시켜 접근성 이름을 부여합니다. 회귀 테스트가 참조 대상 id의 실재 여부와 아이디가 있는 모든 섹션의 레이블링을 검증합니다. diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cc8512..9d8133f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,6 @@ # CHANGELOG ## [Unreleased] -- **UX/접근성 계약 강화**: hero를 포함한 홈페이지의 모든 `
`이 정확히 하나의 실제 heading을 `aria-labelledby`로 참조하도록 표준 라이브러리 기반 회귀 테스트를 강화했습니다. 정적 계약과 실제 브라우저·보조기술 증거는 구분합니다. - **제품 안내 정합성**: 홈페이지의 프로젝트·Fork 목록을 실제로 조직이 소유한 공개 저장소와 맞췄습니다. 이름이 바뀐 `waf-ids-ai-soc` 대신 현재 저장소인 `wardnet`을 안내하고, 그동안 빠져 있던 `Orgmetra`, `TEPP`, `psychometrics-commons`, `contextual-orchestrator` 카드를 추가했습니다. Fork 섹션에서는 소유한 `argos`·`vooster` 카드를 저장소로 연결하고, 아직 조직 소유 공개 저장소가 없는 `vooster-v2-mvp`는 소개 카드로만 남겨 잘못된 링크를 없앴습니다. Naruon 섹션에는 저장소로 가는 버튼을 추가하고, 추가된 카드는 모두 새 창 열림 안내와 `rel="noopener noreferrer"` 정책을 따릅니다. 회귀 테스트가 카드별 목적지·폐기된 URL 부재·다국어 문구 정의를 검증합니다. - **문서 개선**: 저장소 `README.md`를 방문자 우선 구조로 다시 썼습니다. 이 저장소가 소유하는 범위(조직 차원의 공개 서사와 내비게이션)와 각 제품 저장소가 계속 소유하는 범위(런타임 동작, 릴리스·보안·규정 근거, 라이선스)를 표로 분리해, 홈페이지가 제품 권위의 대체물이 아님을 명확히 했습니다. DeepWiki 배지, 로컬 미리보기·편집 가이드, 근거 없는 주장을 게시하지 않기 위한 품질·신뢰 규칙도 함께 담았습니다. - **UX/접근성 개선**: 홈페이지의 각 섹션 랜드마크에 `aria-labelledby`를 연결해 스크린 리더 사용자가 화면 탐색 중 각 영역의 목적을 제목으로 듣고 이동할 수 있게 했습니다. 아이디가 있는 모든 `
`은 실제로 존재하는 제목(`

`)의 아이디를 참조하며, 회귀 테스트가 참조 대상의 실재 여부를 검증합니다. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md deleted file mode 100644 index b810bc3..0000000 --- a/docs/product-technical-gap-baseline.md +++ /dev/null @@ -1,130 +0,0 @@ -# Product and technical gap baseline - -Status: **Proposed** -Canonical writer: [ContextualWisdomLab.github.io#240](https://github.com/ContextualWisdomLab/ContextualWisdomLab.github.io/pull/240) -Evidence ancestor: `818d38088fb22408185a987074161aba9dbb399b` (2026-09-20) -Current exact head and hosted evidence are recorded on the PR after every ordinary-forward update. - -This document is the buyer-visible design-assurance ledger for the public -ContextualWisdomLab website. A checked source contract is not a browser, -assistive-technology, deployment, approval, certification, or release claim. - -## PRD - -### Goal - -A visitor can understand ContextualWisdomLab's public purpose and navigate to -the authoritative product repositories without the website replacing product -domain truth. - -### Users and scenes - -- A prospective buyer scans the homepage by headings and region landmarks. -- A keyboard or screen-reader user skips to main content and navigates sections. -- A multilingual visitor needs equivalent names, wrapping, and next actions. -- An operator must distinguish published website evidence from repository claims. - -### Acceptance - -The homepage must preserve deterministic content, meaningful landmarks, -keyboard/pointer/touch operation, responsive layout, reduced motion, safe -external navigation, eight locales (ko/en/ja/zh/vi/es/de/fr), and recovery from -offline or missing destinations. Claims require evidence from the same exact -head. - -## TRD - -The product is a static HTML/CSS/JavaScript site. Semantic HTML and the browser -accessibility tree are the interaction boundary. Python standard-library parsers -provide source contracts; they do not replace Chromium, Firefox, WebKit, or -assistive-technology runs. No new runtime dependency is justified for landmark -validation. - -Translation strings currently live in the browser JavaScript object. That is a -presentation resource, not organization or product domain truth. It does not -satisfy the target DB-backed versioned translation authority. - -## UML - -```mermaid -flowchart LR - V[Visitor] --> H[index.html] - H --> C[styles.css] - H --> I[i18n.js] - H --> R[Authoritative product repositories] - T[Source contracts] --> H - B[Browser and AT evidence] --> H -``` - -## ERD - -No application database is present in this bounded context, so a product-data -ERD is not applicable. If the shared translation authority is introduced, its -minimum independent model is: - -```mermaid -erDiagram - SCREEN_KEY ||--o{ TRANSLATION_RESOURCE : versions - TRANSLATION_RESOURCE }o--|| LOCALE : uses - TRANSLATION_RESOURCE ||--o{ REVIEW_DECISION : governed_by - TRANSLATION_RESOURCE ||--o{ DEPLOYMENT_RECORD : publishes -``` - -The website must consume a released contract through an API/cache boundary; it -must not copy another owner's source or query its database. - -## Context Map - -- **Public Narrative (this repository):** homepage composition, public - navigation, static presentation, Pages deployment evidence. -- **Product repositories (upstream authority):** product behavior, release, - security, domain language, and operational truth. -- **Translation authority (required shared owner, not yet released):** versioned - translation, review, approval, deployment, and rollback. -- **GitHub (external):** repository destinations behind ordinary links; the - website does not infer product readiness from an open PR. - -## Exact-head acceptance matrix - -| Boundary | Evidence at ancestor | Status | Required action | -| --- | --- | --- | --- | -| Determinism | 11 homepage sections; 11 heading targets | PASS, source only | Keep exact structure contract | -| Semantics | hero uses `aria-labelledby="hero-title"`; parser checks every section and exactly one real heading target | PASS, source only | Replay accessibility tree | -| Keyboard/pointer/touch | No current-head real-browser trace | FAIL | Chromium/Firefox/WebKit interaction run | -| WCAG 2.2 AA / AT | No current-head AT transcript or audit | FAIL | Screen reader and automated/manual audit | -| Responsive | No 320/768/desktop screenshots at current head | FAIL | Capture overflow, wrapping, focus, zoom | -| Reduced motion | Source policy exists; no current-head browser observation | PARTIAL | Verify OS preference in each browser | -| Locales | `i18n.js` exposes ko/en only | FAIL | Released ko/en/ja/zh/vi/es/de/fr screen resources and E2E | -| Loading/empty/error/offline/permission/read-only/stale/conflict/retry/busy | Static surface has no complete evidence set | FAIL | Mark N/A per state with rationale or provide replay | -| CTA to API/destination | Static links exist; no current-head destination replay | FAIL | Validate real destinations and failure guidance | -| Large-data performance | Not applicable to hero landmark; whole-page p95 unmeasured | FAIL (site) | Measure render/interaction p50 and p95 without sample reduction | -| Import/export | No import/export surface | N/A | Reassess if introduced | -| Persistence/reload/rollback | No current-head cache/reload/deploy rollback evidence | FAIL | Exercise reload, offline cache behavior, and deployment rollback | -| Independent approval | Previous approval targets an older head | FAIL | Obtain qualifying current-head approval | -| Hosted Checks | Fresh exact-head generation required after repair | PENDING | Wait without blind rerun | - -## Gap and action register - -| Gap | Owner | Action | Status | -| --- | --- | --- | --- | -| Literal `\\n-` merged adjacent Unreleased entries | Public Narrative | Reject escaped list separators with `tests/test_changelog.py`; store a real line break | Repaired at `818d38088fb22408185a987074161aba9dbb399b`; hosted evidence pending | -| Existing landmark test skipped anonymous sections | Public Narrative | Validate every section and exactly one heading target | Repaired in #240; hosted evidence pending | -| Palette entry used a stale 2024 date and `h2`-only rule | Public Narrative | Bind guidance to 2026-09-20 evidence and h1-h6 | Repaired in #240 | -| Only ko/en resources exist | Translation authority + Public Narrative consumer | Release eight-locale resource contract, then consume by screen key | Proposed / blocked | -| Browser, AT, responsive, recovery evidence absent | Public Narrative | Produce current-head evidence artifacts | Open | -| Static DTO/catalog may be mistaken for domain truth | Public Narrative | Keep product claims linked to canonical repositories | Open | -| Page performance target is unmeasured | Public Narrative | Measure all pages; repair causal render/runtime bottleneck | Open | -| Exact-head approval and hosted Checks absent | Repository governance | Preserve Draft until independently satisfied | Open | - -## Standards and evidence - -- World Wide Web Consortium. (2023). *Web Content Accessibility Guidelines - (WCAG) 2.2*. https://www.w3.org/TR/WCAG22/ -- Web Hypertext Application Technology Working Group. (2026). *HTML Living - Standard: The section element*. https://html.spec.whatwg.org/multipage/sections.html#the-section-element -- World Wide Web Consortium. (2026). *Accessible Rich Internet Applications - (WAI-ARIA) 1.2: region role*. https://www.w3.org/TR/wai-aria-1.2/#region - -These standards explain the semantic boundary. Repository source, tests, -current-head workflow logs, browser artifacts, and review records remain the -acceptance evidence. diff --git a/index.html b/index.html index 4cf7b33..b553382 100644 --- a/index.html +++ b/index.html @@ -70,9 +70,9 @@
-
+
-

맥락지혜 연구실

+

맥락지혜 연구실

Contextual Wisdom Lab

구슬이 서 말이어도 꿰어야 보배이듯, 문서, 메일, 로그, 회의록을 맥락 안에서 엮어 사람이 무엇을 결정하고 무엇을 실행할지 보이게 하는 AI 의사결정 지원 시스템을 연구하고 만듭니다. diff --git a/tests/test_changelog.py b/tests/test_changelog.py deleted file mode 100644 index 39d0969..0000000 --- a/tests/test_changelog.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Regression contracts for the repository-facing release ledger.""" - -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[1] -CHANGELOG = ROOT / "CHANGELOG.md" - - -def test_unreleased_entries_use_real_line_breaks() -> None: - """Reject escaped newlines that merge adjacent Markdown list items.""" - changelog = CHANGELOG.read_text(encoding="utf-8") - unreleased = changelog.split("## [Unreleased]", maxsplit=1)[1] - - assert r"\n-" not in unreleased - assert unreleased.count("\n- ") >= 2 diff --git a/tests/test_section_landmarks.py b/tests/test_section_landmarks.py index 1697ade..c4c1042 100644 --- a/tests/test_section_landmarks.py +++ b/tests/test_section_landmarks.py @@ -8,51 +8,49 @@ class _LandmarkParser(HTMLParser): - """Collect section attributes and heading ids in document order.""" + """Collect

start tags and every element id in document order.""" def __init__(self) -> None: super().__init__() self.sections: list[dict[str, str | None]] = [] - self.heading_ids: list[str] = [] + self.ids: set[str] = set() - def handle_starttag( - self, tag: str, attrs: list[tuple[str, str | None]] - ) -> None: - """Record section attributes and labelled-heading identities.""" + def handle_starttag(self, tag, attrs) -> None: attributes = dict(attrs) + element_id = attributes.get("id") + if element_id: + self.ids.add(element_id) if tag == "section": self.sections.append(attributes) - if tag in {"h1", "h2", "h3", "h4", "h5", "h6"}: - heading_id = attributes.get("id") - if heading_id: - self.heading_ids.append(heading_id) def _parse_index() -> _LandmarkParser: - """Parse the homepage with the standard-library HTML parser.""" parser = _LandmarkParser() parser.feed(INDEX.read_text(encoding="utf-8")) assert parser.sections, "homepage must contain at least one section" return parser -def test_labelled_sections_reference_one_existing_heading() -> None: - """Each aria-labelledby target must resolve to exactly one heading id.""" +def test_labelled_sections_reference_an_existing_heading() -> None: + """Each aria-labelledby target must resolve to a real id in the document.""" parser = _parse_index() - for section in parser.sections: - target = section.get("aria-labelledby") - assert target, ( - f"section #{section.get('id')} must reference a heading via aria-labelledby" - ) - assert parser.heading_ids.count(target) == 1, ( - f"section #{section.get('id')} must reference one existing heading; " - f"found {parser.heading_ids.count(target)} for {target!r}" + labelled = [s for s in parser.sections if s.get("aria-labelledby")] + assert labelled, "homepage sections must expose accessible names" + + for section in labelled: + target = section["aria-labelledby"] + assert target in parser.ids, ( + f"section #{section.get('id')} references missing id {target!r}" ) -def test_all_sections_are_labelled_including_anonymous_sections() -> None: - """Prevent an id-less section from bypassing the landmark contract.""" +def test_identified_sections_are_all_labelled() -> None: + """Every identifiable section landmark must carry an accessible name.""" parser = _parse_index() - assert all(section.get("aria-labelledby") for section in parser.sections) + for section in parser.sections: + if section.get("id"): + assert section.get("aria-labelledby"), ( + f"section #{section['id']} must reference a heading via aria-labelledby" + ) From d37a53cb8a16b926f7982658fbbe536ddf2930fe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 26 Sep 2026 17:57:59 +0900 Subject: [PATCH 22/34] docs(palette): restore verified landmark evidence --- .Jules/palette.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.Jules/palette.md b/.Jules/palette.md index 7cf7b0d..de1b43c 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -31,4 +31,8 @@ ## 2026-08-22 - Add aria-labelledby to section landmarks **Learning:** `
`은 접근성 이름이 있을 때만 `region` 랜드마크로 노출되고, 이름이 없으면 `generic`으로 매핑되어 화면 탐색 랜드마크 목록에 나타나지 않습니다. `id` 속성만으로는 부족합니다. -**Action:** `
`에는 고유한 `id`를 가진 내부 헤딩(`

`)을 `aria-labelledby`로 참조시켜 접근성 이름을 부여합니다. 회귀 테스트가 참조 대상 id의 실재 여부와 아이디가 있는 모든 섹션의 레이블링을 검증합니다. +**Action:** 홈페이지의 모든 `
`은 내부 제목(`

`–`

`)의 고유 `id`를 `aria-labelledby`로 참조합니다. 회귀 테스트는 `id`가 없는 section도 포함하고 참조 대상이 정확히 하나의 실제 heading인지 검증합니다. + +## 2026-09-20 - hero section landmark contract +**Learning:** 기존 테스트는 `id`가 있는 section만 검사해 `id` 없는 hero가 접근성 이름 없이 남는 회귀를 허용했습니다. 임의의 element id가 아니라 실제 heading id와의 관계를 검증해야 합니다. +**Action:** 모든 homepage section은 정확히 하나의 기존 `

`–`

` id를 `aria-labelledby`로 참조합니다. 실제 브라우저 접근성 트리와 보조기술 검증 전에는 정적 계약을 전달 완료 증거로 표현하지 않습니다. From e28c075d9c6a2b6282d34aa25e1919fb5193a018 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 26 Sep 2026 17:58:00 +0900 Subject: [PATCH 23/34] docs(changelog): restore verified accessibility evidence --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d8133f..0cc8512 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # CHANGELOG ## [Unreleased] +- **UX/접근성 계약 강화**: hero를 포함한 홈페이지의 모든 `
`이 정확히 하나의 실제 heading을 `aria-labelledby`로 참조하도록 표준 라이브러리 기반 회귀 테스트를 강화했습니다. 정적 계약과 실제 브라우저·보조기술 증거는 구분합니다. - **제품 안내 정합성**: 홈페이지의 프로젝트·Fork 목록을 실제로 조직이 소유한 공개 저장소와 맞췄습니다. 이름이 바뀐 `waf-ids-ai-soc` 대신 현재 저장소인 `wardnet`을 안내하고, 그동안 빠져 있던 `Orgmetra`, `TEPP`, `psychometrics-commons`, `contextual-orchestrator` 카드를 추가했습니다. Fork 섹션에서는 소유한 `argos`·`vooster` 카드를 저장소로 연결하고, 아직 조직 소유 공개 저장소가 없는 `vooster-v2-mvp`는 소개 카드로만 남겨 잘못된 링크를 없앴습니다. Naruon 섹션에는 저장소로 가는 버튼을 추가하고, 추가된 카드는 모두 새 창 열림 안내와 `rel="noopener noreferrer"` 정책을 따릅니다. 회귀 테스트가 카드별 목적지·폐기된 URL 부재·다국어 문구 정의를 검증합니다. - **문서 개선**: 저장소 `README.md`를 방문자 우선 구조로 다시 썼습니다. 이 저장소가 소유하는 범위(조직 차원의 공개 서사와 내비게이션)와 각 제품 저장소가 계속 소유하는 범위(런타임 동작, 릴리스·보안·규정 근거, 라이선스)를 표로 분리해, 홈페이지가 제품 권위의 대체물이 아님을 명확히 했습니다. DeepWiki 배지, 로컬 미리보기·편집 가이드, 근거 없는 주장을 게시하지 않기 위한 품질·신뢰 규칙도 함께 담았습니다. - **UX/접근성 개선**: 홈페이지의 각 섹션 랜드마크에 `aria-labelledby`를 연결해 스크린 리더 사용자가 화면 탐색 중 각 영역의 목적을 제목으로 듣고 이동할 수 있게 했습니다. 아이디가 있는 모든 `
`은 실제로 존재하는 제목(`

`)의 아이디를 참조하며, 회귀 테스트가 참조 대상의 실재 여부를 검증합니다. From 5c8a33a15a453fd2098a0327162351f66ceb8a83 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 26 Sep 2026 17:58:01 +0900 Subject: [PATCH 24/34] docs(gap): restore design assurance baseline --- docs/product-technical-gap-baseline.md | 130 +++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 docs/product-technical-gap-baseline.md diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md new file mode 100644 index 0000000..b810bc3 --- /dev/null +++ b/docs/product-technical-gap-baseline.md @@ -0,0 +1,130 @@ +# Product and technical gap baseline + +Status: **Proposed** +Canonical writer: [ContextualWisdomLab.github.io#240](https://github.com/ContextualWisdomLab/ContextualWisdomLab.github.io/pull/240) +Evidence ancestor: `818d38088fb22408185a987074161aba9dbb399b` (2026-09-20) +Current exact head and hosted evidence are recorded on the PR after every ordinary-forward update. + +This document is the buyer-visible design-assurance ledger for the public +ContextualWisdomLab website. A checked source contract is not a browser, +assistive-technology, deployment, approval, certification, or release claim. + +## PRD + +### Goal + +A visitor can understand ContextualWisdomLab's public purpose and navigate to +the authoritative product repositories without the website replacing product +domain truth. + +### Users and scenes + +- A prospective buyer scans the homepage by headings and region landmarks. +- A keyboard or screen-reader user skips to main content and navigates sections. +- A multilingual visitor needs equivalent names, wrapping, and next actions. +- An operator must distinguish published website evidence from repository claims. + +### Acceptance + +The homepage must preserve deterministic content, meaningful landmarks, +keyboard/pointer/touch operation, responsive layout, reduced motion, safe +external navigation, eight locales (ko/en/ja/zh/vi/es/de/fr), and recovery from +offline or missing destinations. Claims require evidence from the same exact +head. + +## TRD + +The product is a static HTML/CSS/JavaScript site. Semantic HTML and the browser +accessibility tree are the interaction boundary. Python standard-library parsers +provide source contracts; they do not replace Chromium, Firefox, WebKit, or +assistive-technology runs. No new runtime dependency is justified for landmark +validation. + +Translation strings currently live in the browser JavaScript object. That is a +presentation resource, not organization or product domain truth. It does not +satisfy the target DB-backed versioned translation authority. + +## UML + +```mermaid +flowchart LR + V[Visitor] --> H[index.html] + H --> C[styles.css] + H --> I[i18n.js] + H --> R[Authoritative product repositories] + T[Source contracts] --> H + B[Browser and AT evidence] --> H +``` + +## ERD + +No application database is present in this bounded context, so a product-data +ERD is not applicable. If the shared translation authority is introduced, its +minimum independent model is: + +```mermaid +erDiagram + SCREEN_KEY ||--o{ TRANSLATION_RESOURCE : versions + TRANSLATION_RESOURCE }o--|| LOCALE : uses + TRANSLATION_RESOURCE ||--o{ REVIEW_DECISION : governed_by + TRANSLATION_RESOURCE ||--o{ DEPLOYMENT_RECORD : publishes +``` + +The website must consume a released contract through an API/cache boundary; it +must not copy another owner's source or query its database. + +## Context Map + +- **Public Narrative (this repository):** homepage composition, public + navigation, static presentation, Pages deployment evidence. +- **Product repositories (upstream authority):** product behavior, release, + security, domain language, and operational truth. +- **Translation authority (required shared owner, not yet released):** versioned + translation, review, approval, deployment, and rollback. +- **GitHub (external):** repository destinations behind ordinary links; the + website does not infer product readiness from an open PR. + +## Exact-head acceptance matrix + +| Boundary | Evidence at ancestor | Status | Required action | +| --- | --- | --- | --- | +| Determinism | 11 homepage sections; 11 heading targets | PASS, source only | Keep exact structure contract | +| Semantics | hero uses `aria-labelledby="hero-title"`; parser checks every section and exactly one real heading target | PASS, source only | Replay accessibility tree | +| Keyboard/pointer/touch | No current-head real-browser trace | FAIL | Chromium/Firefox/WebKit interaction run | +| WCAG 2.2 AA / AT | No current-head AT transcript or audit | FAIL | Screen reader and automated/manual audit | +| Responsive | No 320/768/desktop screenshots at current head | FAIL | Capture overflow, wrapping, focus, zoom | +| Reduced motion | Source policy exists; no current-head browser observation | PARTIAL | Verify OS preference in each browser | +| Locales | `i18n.js` exposes ko/en only | FAIL | Released ko/en/ja/zh/vi/es/de/fr screen resources and E2E | +| Loading/empty/error/offline/permission/read-only/stale/conflict/retry/busy | Static surface has no complete evidence set | FAIL | Mark N/A per state with rationale or provide replay | +| CTA to API/destination | Static links exist; no current-head destination replay | FAIL | Validate real destinations and failure guidance | +| Large-data performance | Not applicable to hero landmark; whole-page p95 unmeasured | FAIL (site) | Measure render/interaction p50 and p95 without sample reduction | +| Import/export | No import/export surface | N/A | Reassess if introduced | +| Persistence/reload/rollback | No current-head cache/reload/deploy rollback evidence | FAIL | Exercise reload, offline cache behavior, and deployment rollback | +| Independent approval | Previous approval targets an older head | FAIL | Obtain qualifying current-head approval | +| Hosted Checks | Fresh exact-head generation required after repair | PENDING | Wait without blind rerun | + +## Gap and action register + +| Gap | Owner | Action | Status | +| --- | --- | --- | --- | +| Literal `\\n-` merged adjacent Unreleased entries | Public Narrative | Reject escaped list separators with `tests/test_changelog.py`; store a real line break | Repaired at `818d38088fb22408185a987074161aba9dbb399b`; hosted evidence pending | +| Existing landmark test skipped anonymous sections | Public Narrative | Validate every section and exactly one heading target | Repaired in #240; hosted evidence pending | +| Palette entry used a stale 2024 date and `h2`-only rule | Public Narrative | Bind guidance to 2026-09-20 evidence and h1-h6 | Repaired in #240 | +| Only ko/en resources exist | Translation authority + Public Narrative consumer | Release eight-locale resource contract, then consume by screen key | Proposed / blocked | +| Browser, AT, responsive, recovery evidence absent | Public Narrative | Produce current-head evidence artifacts | Open | +| Static DTO/catalog may be mistaken for domain truth | Public Narrative | Keep product claims linked to canonical repositories | Open | +| Page performance target is unmeasured | Public Narrative | Measure all pages; repair causal render/runtime bottleneck | Open | +| Exact-head approval and hosted Checks absent | Repository governance | Preserve Draft until independently satisfied | Open | + +## Standards and evidence + +- World Wide Web Consortium. (2023). *Web Content Accessibility Guidelines + (WCAG) 2.2*. https://www.w3.org/TR/WCAG22/ +- Web Hypertext Application Technology Working Group. (2026). *HTML Living + Standard: The section element*. https://html.spec.whatwg.org/multipage/sections.html#the-section-element +- World Wide Web Consortium. (2026). *Accessible Rich Internet Applications + (WAI-ARIA) 1.2: region role*. https://www.w3.org/TR/wai-aria-1.2/#region + +These standards explain the semantic boundary. Repository source, tests, +current-head workflow logs, browser artifacts, and review records remain the +acceptance evidence. From a1ce835835635329e00dd60b0e642eb99a646beb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 26 Sep 2026 17:58:02 +0900 Subject: [PATCH 25/34] fix(home): restore labelled section semantics --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index b553382..4cf7b33 100644 --- a/index.html +++ b/index.html @@ -70,9 +70,9 @@
-
+
-

맥락지혜 연구실

+

맥락지혜 연구실

Contextual Wisdom Lab

구슬이 서 말이어도 꿰어야 보배이듯, 문서, 메일, 로그, 회의록을 맥락 안에서 엮어 사람이 무엇을 결정하고 무엇을 실행할지 보이게 하는 AI 의사결정 지원 시스템을 연구하고 만듭니다. From ea78848d04e4d904dcafd8d3e834be353855cc2b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 26 Sep 2026 17:58:04 +0900 Subject: [PATCH 26/34] test(changelog): restore release-ledger contract --- tests/test_changelog.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/test_changelog.py diff --git a/tests/test_changelog.py b/tests/test_changelog.py new file mode 100644 index 0000000..39d0969 --- /dev/null +++ b/tests/test_changelog.py @@ -0,0 +1,15 @@ +"""Regression contracts for the repository-facing release ledger.""" + +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +CHANGELOG = ROOT / "CHANGELOG.md" + + +def test_unreleased_entries_use_real_line_breaks() -> None: + """Reject escaped newlines that merge adjacent Markdown list items.""" + changelog = CHANGELOG.read_text(encoding="utf-8") + unreleased = changelog.split("## [Unreleased]", maxsplit=1)[1] + + assert r"\n-" not in unreleased + assert unreleased.count("\n- ") >= 2 From afe5d773a88eaf7b8c88e65704efb8658181e2f6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 26 Sep 2026 17:58:05 +0900 Subject: [PATCH 27/34] test(home): restore heading-backed landmark contract --- tests/test_section_landmarks.py | 46 +++++++++++++++++---------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/tests/test_section_landmarks.py b/tests/test_section_landmarks.py index c4c1042..1697ade 100644 --- a/tests/test_section_landmarks.py +++ b/tests/test_section_landmarks.py @@ -8,49 +8,51 @@ class _LandmarkParser(HTMLParser): - """Collect

start tags and every element id in document order.""" + """Collect section attributes and heading ids in document order.""" def __init__(self) -> None: super().__init__() self.sections: list[dict[str, str | None]] = [] - self.ids: set[str] = set() + self.heading_ids: list[str] = [] - def handle_starttag(self, tag, attrs) -> None: + def handle_starttag( + self, tag: str, attrs: list[tuple[str, str | None]] + ) -> None: + """Record section attributes and labelled-heading identities.""" attributes = dict(attrs) - element_id = attributes.get("id") - if element_id: - self.ids.add(element_id) if tag == "section": self.sections.append(attributes) + if tag in {"h1", "h2", "h3", "h4", "h5", "h6"}: + heading_id = attributes.get("id") + if heading_id: + self.heading_ids.append(heading_id) def _parse_index() -> _LandmarkParser: + """Parse the homepage with the standard-library HTML parser.""" parser = _LandmarkParser() parser.feed(INDEX.read_text(encoding="utf-8")) assert parser.sections, "homepage must contain at least one section" return parser -def test_labelled_sections_reference_an_existing_heading() -> None: - """Each aria-labelledby target must resolve to a real id in the document.""" +def test_labelled_sections_reference_one_existing_heading() -> None: + """Each aria-labelledby target must resolve to exactly one heading id.""" parser = _parse_index() - labelled = [s for s in parser.sections if s.get("aria-labelledby")] - assert labelled, "homepage sections must expose accessible names" - - for section in labelled: - target = section["aria-labelledby"] - assert target in parser.ids, ( - f"section #{section.get('id')} references missing id {target!r}" + for section in parser.sections: + target = section.get("aria-labelledby") + assert target, ( + f"section #{section.get('id')} must reference a heading via aria-labelledby" + ) + assert parser.heading_ids.count(target) == 1, ( + f"section #{section.get('id')} must reference one existing heading; " + f"found {parser.heading_ids.count(target)} for {target!r}" ) -def test_identified_sections_are_all_labelled() -> None: - """Every identifiable section landmark must carry an accessible name.""" +def test_all_sections_are_labelled_including_anonymous_sections() -> None: + """Prevent an id-less section from bypassing the landmark contract.""" parser = _parse_index() - for section in parser.sections: - if section.get("id"): - assert section.get("aria-labelledby"), ( - f"section #{section['id']} must reference a heading via aria-labelledby" - ) + assert all(section.get("aria-labelledby") for section in parser.sections) From 5b011de573f932f790c5e7f6285b3076f108547a Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 26 Sep 2026 09:12:02 +0000 Subject: [PATCH 28/34] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20[UX=20improveme?= =?UTF-8?q?nt]=20hero=20=EC=84=B9=EC=85=98=20=EC=A0=91=EA=B7=BC=EC=84=B1?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .Jules/palette.md | 4 ++++ docs/product-technical-gap-baseline.md | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.Jules/palette.md b/.Jules/palette.md index de1b43c..5ce0b5d 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -36,3 +36,7 @@ ## 2026-09-20 - hero section landmark contract **Learning:** 기존 테스트는 `id`가 있는 section만 검사해 `id` 없는 hero가 접근성 이름 없이 남는 회귀를 허용했습니다. 임의의 element id가 아니라 실제 heading id와의 관계를 검증해야 합니다. **Action:** 모든 homepage section은 정확히 하나의 기존 `

`–`

` id를 `aria-labelledby`로 참조합니다. 실제 브라우저 접근성 트리와 보조기술 검증 전에는 정적 계약을 전달 완료 증거로 표현하지 않습니다. + +## 2026-09-20 - hero 섹션에 aria-labelledby 랜드마크 이름 추가 +**학습:** 랜드마크 영역을 스크린 리더에 올바르게 노출하기 위해서는 문서 내 모든 `
` 요소에 고유한 접근성 이름(`aria-labelledby`)이 필요하며, 예외적으로 첫 번째(hero) 섹션이라도 누락하면 region으로 탐색되지 않습니다. +**적용:** 앞으로 모든 `
` 태그를 사용할 때는 반드시 내부에 해당하는 제목 요소의 `id`를 `aria-labelledby`로 연결하여 랜드마크 접근성을 보장해야 합니다. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index b810bc3..e27fe14 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,8 +1,8 @@ # Product and technical gap baseline -Status: **Proposed** -Canonical writer: [ContextualWisdomLab.github.io#240](https://github.com/ContextualWisdomLab/ContextualWisdomLab.github.io/pull/240) -Evidence ancestor: `818d38088fb22408185a987074161aba9dbb399b` (2026-09-20) +Status: **Proposed** +Canonical writer: [ContextualWisdomLab.github.io#240](https://github.com/ContextualWisdomLab/ContextualWisdomLab.github.io/pull/240) +Evidence ancestor: `818d38088fb22408185a987074161aba9dbb399b` (2026-09-20) Current exact head and hosted evidence are recorded on the PR after every ordinary-forward update. This document is the buyer-visible design-assurance ledger for the public From 93eef4e5b9aeaba025da581348beaf310b5384b2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 26 Sep 2026 20:57:18 +0900 Subject: [PATCH 29/34] docs(gaps): record external-link indicator acceptance --- docs/product-technical-gap-baseline.md | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index e27fe14..67c0771 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -116,6 +116,34 @@ must not copy another owner's source or query its database. | Page performance target is unmeasured | Public Narrative | Measure all pages; repair causal render/runtime bottleneck | Open | | Exact-head approval and hosted Checks absent | Repository governance | Preserve Draft until independently satisfied | Open | +## External-link indicator acceptance — #265 + +Product source remains single-writer #265. This documentation lane records evidence +only and does not copy mutable homepage source. + +- Product evidence exact: `f4945178065f32c44304509a3a4bd5cd430cfcd9` +- RED contract: `50938ba62ab7ec9fdb35bd77c2cd062a9a387b38` + found 23 new-tab links but only 21 visual indicators; both omissions were + primary CTA links. +- Production repair: `ef9e2bbe62f6259fed114ae0e5933422bfc4f6f0` + moves CTA translation keys to visible child spans and adds decorative, + assistive-technology-hidden indicators. +- Changelog binding: `f4945178065f32c44304509a3a4bd5cd430cfcd9`. + +| Acceptance dimension | Exact evidence | Status | +| --- | --- | --- | +| Determinism | 23 `target="_blank"` links / 23 visual indicators | Source GREEN | +| Locale persistence | translated CTA text is a child span; indicator is a sibling | Source GREEN | +| Semantics | `aria-describedby` new-window description and hidden decoration remain separate | Source GREEN | +| Real browser / AT | Chromium, Firefox, WebKit and assistive-technology replay absent | FAIL | +| Responsive / touch | 320px, 768px, desktop and touch evidence absent | FAIL | +| Eight locales | ko/en/ja/zh/vi/es/de/fr screenshots and overflow checks absent | FAIL | +| Performance | deployed page median/p95 and layout-shift evidence absent | FAIL | +| Review / hosted gates | current exact-head independent approval and terminal required Checks absent | Pending | + +Keep #265 Draft / Proposed. No publication, merge, release, or carryover claim is +authorized from source-level evidence alone. + ## Standards and evidence - World Wide Web Consortium. (2023). *Web Content Accessibility Guidelines From 1b7047f236061b39affabe5112321203ca632ef3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 26 Sep 2026 21:03:54 +0900 Subject: [PATCH 30/34] docs(gaps): bind recovered external-link exact head --- docs/product-technical-gap-baseline.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 67c0771..2ed6bc7 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -121,14 +121,17 @@ must not copy another owner's source or query its database. Product source remains single-writer #265. This documentation lane records evidence only and does not copy mutable homepage source. -- Product evidence exact: `f4945178065f32c44304509a3a4bd5cd430cfcd9` +- Product evidence exact: `d0bd383b87dc5472f7d4dac6aa6847e895a5b800` - RED contract: `50938ba62ab7ec9fdb35bd77c2cd062a9a387b38` found 23 new-tab links but only 21 visual indicators; both omissions were primary CTA links. - Production repair: `ef9e2bbe62f6259fed114ae0e5933422bfc4f6f0` moves CTA translation keys to visible child spans and adds decorative, assistive-technology-hidden indicators. -- Changelog binding: `f4945178065f32c44304509a3a4bd5cd430cfcd9`. +- Changelog binding and concurrent-regression recovery exact: `d0bd383b87dc5472f7d4dac6aa6847e895a5b800`. + A successor deleted both executable contracts, removed the CHANGELOG entry, and + reverted the two CTA repairs; three ordinary-forward commits restored the + verified blobs without force or discarding the successor's separate metadata. | Acceptance dimension | Exact evidence | Status | | --- | --- | --- | From efaa7e90cccb1cf8a7f117f84d35b6a05e804c7d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 26 Sep 2026 21:57:02 +0900 Subject: [PATCH 31/34] docs(gap): bind external-link recovery exact head --- docs/product-technical-gap-baseline.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 2ed6bc7..188eae0 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -121,14 +121,14 @@ must not copy another owner's source or query its database. Product source remains single-writer #265. This documentation lane records evidence only and does not copy mutable homepage source. -- Product evidence exact: `d0bd383b87dc5472f7d4dac6aa6847e895a5b800` +- Product evidence exact: `7cda5fe6a4aacef20d6fdca64f75c7ee3ad84535` - RED contract: `50938ba62ab7ec9fdb35bd77c2cd062a9a387b38` found 23 new-tab links but only 21 visual indicators; both omissions were primary CTA links. - Production repair: `ef9e2bbe62f6259fed114ae0e5933422bfc4f6f0` moves CTA translation keys to visible child spans and adds decorative, assistive-technology-hidden indicators. -- Changelog binding and concurrent-regression recovery exact: `d0bd383b87dc5472f7d4dac6aa6847e895a5b800`. +- Changelog binding and concurrent-regression recovery exact: `7cda5fe6a4aacef20d6fdca64f75c7ee3ad84535`. A successor deleted both executable contracts, removed the CHANGELOG entry, and reverted the two CTA repairs; three ordinary-forward commits restored the verified blobs without force or discarding the successor's separate metadata. From e452fd9a2928fa264f5f8c5355f13a7573726060 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 26 Sep 2026 22:02:30 +0900 Subject: [PATCH 32/34] docs(gap): bind repeated recovery exact head --- docs/product-technical-gap-baseline.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 188eae0..d9ddbb8 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -121,14 +121,14 @@ must not copy another owner's source or query its database. Product source remains single-writer #265. This documentation lane records evidence only and does not copy mutable homepage source. -- Product evidence exact: `7cda5fe6a4aacef20d6fdca64f75c7ee3ad84535` +- Product evidence exact: `96c3ba8022816ce0f76f94095af6587675cba6ac` - RED contract: `50938ba62ab7ec9fdb35bd77c2cd062a9a387b38` found 23 new-tab links but only 21 visual indicators; both omissions were primary CTA links. - Production repair: `ef9e2bbe62f6259fed114ae0e5933422bfc4f6f0` moves CTA translation keys to visible child spans and adds decorative, assistive-technology-hidden indicators. -- Changelog binding and concurrent-regression recovery exact: `7cda5fe6a4aacef20d6fdca64f75c7ee3ad84535`. +- Changelog binding and concurrent-regression recovery exact: `96c3ba8022816ce0f76f94095af6587675cba6ac`. A successor deleted both executable contracts, removed the CHANGELOG entry, and reverted the two CTA repairs; three ordinary-forward commits restored the verified blobs without force or discarding the successor's separate metadata. From 8ed0c1637c1b1279ce716e3fe62cfb1a8f913692 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 26 Sep 2026 22:57:57 +0900 Subject: [PATCH 33/34] docs(gap): move external-link authority to stable successor --- docs/product-technical-gap-baseline.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index d9ddbb8..08b00b6 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -116,22 +116,23 @@ must not copy another owner's source or query its database. | Page performance target is unmeasured | Public Narrative | Measure all pages; repair causal render/runtime bottleneck | Open | | Exact-head approval and hosted Checks absent | Repository governance | Preserve Draft until independently satisfied | Open | -## External-link indicator acceptance — #265 +## External-link indicator acceptance — #268 -Product source remains single-writer #265. This documentation lane records evidence -only and does not copy mutable homepage source. +Product source remains single-writer #268. Predecessor #265 remains Draft with its valid +history preserved, but its automated task branch repeatedly reapplies a stale snapshot. +This documentation lane records evidence only and does not copy mutable homepage source. -- Product evidence exact: `96c3ba8022816ce0f76f94095af6587675cba6ac` +- Product evidence exact: `7911416337eab48374637f137a59e91daf43852e` - RED contract: `50938ba62ab7ec9fdb35bd77c2cd062a9a387b38` found 23 new-tab links but only 21 visual indicators; both omissions were primary CTA links. - Production repair: `ef9e2bbe62f6259fed114ae0e5933422bfc4f6f0` moves CTA translation keys to visible child spans and adds decorative, assistive-technology-hidden indicators. -- Changelog binding and concurrent-regression recovery exact: `96c3ba8022816ce0f76f94095af6587675cba6ac`. - A successor deleted both executable contracts, removed the CHANGELOG entry, and - reverted the two CTA repairs; three ordinary-forward commits restored the - verified blobs without force or discarding the successor's separate metadata. +- Changelog binding and concurrent-regression recovery exact: `7911416337eab48374637f137a59e91daf43852e`. + #268 starts from #265 head `b42b1ced7014598a9b552ff4ef187d2be3f183ce`, + preserving its full branch history, then restores the three repeatedly deleted + verified blobs ordinary-forward without force or destructive rebase. | Acceptance dimension | Exact evidence | Status | | --- | --- | --- | @@ -144,8 +145,8 @@ only and does not copy mutable homepage source. | Performance | deployed page median/p95 and layout-shift evidence absent | FAIL | | Review / hosted gates | current exact-head independent approval and terminal required Checks absent | Pending | -Keep #265 Draft / Proposed. No publication, merge, release, or carryover claim is -authorized from source-level evidence alone. +Keep #268 and predecessor #265 Draft / Proposed. #268 is the canonical writer; +no publication, merge, or release claim is authorized from source-level evidence alone. ## Standards and evidence From f612d78aec862863e1732db6ce8d770cb56342f0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 26 Sep 2026 23:02:51 +0900 Subject: [PATCH 34/34] docs(gap): register gallery performance acceptance --- docs/product-technical-gap-baseline.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 08b00b6..f034522 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -160,3 +160,29 @@ no publication, merge, or release claim is authorized from source-level evidence These standards explain the semantic boundary. Repository source, tests, current-head workflow logs, browser artifacts, and review records remain the acceptance evidence. + +## Gallery mutation-guard acceptance — #267 + +Product source remains single-writer #267; this ledger records evidence only. +Product evidence exact: `0c6ddb71faf6eabe24c3c1bfd3c055ea97fa1f25`. + +PRD: APG tab selection, focus, and panel visibility must remain deterministic. +TRD: conditional DOM writes are a Proposed implementation detail and cannot +become the semantic contract. Context Map and UML remain the existing +Gallery → Tab Group → Panel presentation flow. ERD impact is none. + +| Concern | Evidence | Status | +| --- | --- | --- | +| Tab semantics | Existing Node harness covers wrapped Arrow/Home/End transitions and atomic failure | Source PASS | +| Mutation reduction | No exact-head read/write-count contract | FAIL | +| Browser performance | Chromium/Firefox/WebKit scripting/style/layout/paint median and p95 absent | FAIL | +| Responsive / input | Pointer, touch, keyboard and 320/768/desktop replay absent | FAIL | +| Locales | ko/en/ja/zh/vi/es/de/fr overflow and fallback evidence absent | FAIL | +| Recovery | Reload and stale/conflict behavior are not affected or evidenced | Pending | +| Review / hosted gates | Current approval and terminal required Checks absent | Pending | + +Action: keep #267 Draft / Proposed, add an exact mutation-count harness, then +profile identical browser interactions with stated warm-up, sample size, and +failure denominator. Source inspection alone must not publish a +layout-thrashing or paint-improvement claim. +