diff --git a/.Jules/palette.md b/.Jules/palette.md index 7cf7b0d..5ce0b5d 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -31,4 +31,12 @@ ## 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`로 참조합니다. 실제 브라우저 접근성 트리와 보조기술 검증 전에는 정적 계약을 전달 완료 증거로 표현하지 않습니다. + +## 2026-09-20 - hero 섹션에 aria-labelledby 랜드마크 이름 추가 +**학습:** 랜드마크 영역을 스크린 리더에 올바르게 노출하기 위해서는 문서 내 모든 `
` 요소에 고유한 접근성 이름(`aria-labelledby`)이 필요하며, 예외적으로 첫 번째(hero) 섹션이라도 누락하면 region으로 탐색되지 않습니다. +**적용:** 앞으로 모든 `
` 태그를 사용할 때는 반드시 내부에 해당하는 제목 요소의 `id`를 `aria-labelledby`로 연결하여 랜드마크 접근성을 보장해야 합니다. 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`를 연결해 스크린 리더 사용자가 화면 탐색 중 각 영역의 목적을 제목으로 듣고 이동할 수 있게 했습니다. 아이디가 있는 모든 `
`은 실제로 존재하는 제목(`

`)의 아이디를 참조하며, 회귀 테스트가 참조 대상의 실재 여부를 검증합니다. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md new file mode 100644 index 0000000..561d0c3 --- /dev/null +++ b/docs/product-technical-gap-baseline.md @@ -0,0 +1,187 @@ +# 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 | + +## External-link indicator acceptance — #268 + +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: `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: `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 | +| --- | --- | --- | +| 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 #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 + +- 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. + +## 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. 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 의사결정 지원 시스템을 연구하고 만듭니다. 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 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)