Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
270ef93
🎨 Palette: [UX improvement] hero 섹션 접근성 이름 추가
seonghobae Sep 16, 2026
84ce793
🎨 Palette: [UX improvement] hero 섹션 접근성 이름 추가
seonghobae Sep 17, 2026
d6916c4
🎨 Palette: [UX improvement] hero 섹션 접근성 이름 추가
seonghobae Sep 17, 2026
01d0f18
🎨 Palette: [UX improvement] hero 섹션 접근성 이름 추가
seonghobae Sep 17, 2026
dc4706b
🎨 Palette: [UX improvement] hero 섹션 접근성 이름 추가
seonghobae Sep 18, 2026
cd9251a
🎨 Palette: [UX improvement] hero 섹션 접근성 이름 추가
seonghobae Sep 18, 2026
97e480a
🎨 Palette: [UX improvement] hero 섹션 접근성 이름 추가
seonghobae Sep 19, 2026
a5ea2ae
test(a11y): cover every section landmark
seonghobae Sep 19, 2026
7cd48a5
docs(a11y): correct landmark contract guidance
seonghobae Sep 19, 2026
e2e67ad
docs(changelog): record section landmark contract
seonghobae Sep 19, 2026
4cb6969
docs(assurance): establish product technical gap baseline
seonghobae Sep 19, 2026
56783a2
test(docs): reject escaped release-list newlines
seonghobae Sep 19, 2026
818d380
fix(docs): restore release-list line break
seonghobae Sep 19, 2026
df6bdeb
docs: bind release-ledger regression to exact-head gap baseline
seonghobae Sep 19, 2026
9314d2c
🎨 Palette: [UX improvement] hero 섹션 접근성 이름 추가
seonghobae Sep 19, 2026
786d710
repair: restore verified .Jules/palette.md after landmark-contract re…
seonghobae Sep 19, 2026
0001be3
repair: restore verified CHANGELOG.md after landmark-contract regress…
seonghobae Sep 19, 2026
b9bca4e
repair: restore verified tests/test_section_landmarks.py after landma…
seonghobae Sep 19, 2026
1b828b0
repair: restore verified docs/product-technical-gap-baseline.md after…
seonghobae Sep 19, 2026
369cce7
repair: restore verified tests/test_changelog.py after landmark-contr…
seonghobae Sep 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .Jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@

## 2026-08-22 - Add aria-labelledby to section landmarks
**Learning:** `<section>`은 접근성 이름이 있을 때만 `region` 랜드마크로 노출되고, 이름이 없으면 `generic`으로 매핑되어 화면 탐색 랜드마크 목록에 나타나지 않습니다. `id` 속성만으로는 부족합니다.
**Action:** `<section>`에는 고유한 `id`를 가진 내부 헤딩(`<h2>`)을 `aria-labelledby`로 참조시켜 접근성 이름을 부여합니다. 회귀 테스트가 참조 대상 id의 실재 여부와 아이디가 있는 모든 섹션의 레이블링을 검증합니다.
**Action:** 홈페이지의 모든 `<section>`은 내부 제목(`<h1>`–`<h6>`)의 고유 `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은 정확히 하나의 기존 `<h1>`–`<h6>` id를 `aria-labelledby`로 참조합니다. 실제 브라우저 접근성 트리와 보조기술 검증 전에는 정적 계약을 전달 완료 증거로 표현하지 않습니다.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# CHANGELOG

## [Unreleased]
- **UX/접근성 계약 강화**: hero를 포함한 홈페이지의 모든 `<section>`이 정확히 하나의 실제 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`를 연결해 스크린 리더 사용자가 화면 탐색 중 각 영역의 목적을 제목으로 듣고 이동할 수 있게 했습니다. 아이디가 있는 모든 `<section>`은 실제로 존재하는 제목(`<h2>`)의 아이디를 참조하며, 회귀 테스트가 참조 대상의 실재 여부를 검증합니다.
Expand Down
130 changes: 130 additions & 0 deletions docs/product-technical-gap-baseline.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@
</header>

<main id="top" tabindex="-1">
<section class="hero">
<section id="hero" class="hero" aria-labelledby="hero-title">
<div class="hero-copy">
<h1 data-i18n="hero.title">맥락지혜 연구실</h1>
<h1 id="hero-title" data-i18n="hero.title">맥락지혜 연구실</h1>
<p class="lab-name" data-i18n="hero.labName">Contextual Wisdom Lab</p>
<p class="thesis" data-i18n="hero.thesis">
구슬이 서 말이어도 꿰어야 보배이듯, 문서, 메일, 로그, 회의록을 맥락 안에서 엮어 사람이 무엇을 결정하고 무엇을 실행할지 보이게 하는 AI 의사결정 지원 시스템을 연구하고 만듭니다.
Expand Down
15 changes: 15 additions & 0 deletions tests/test_changelog.py
Original file line number Diff line number Diff line change
@@ -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
46 changes: 24 additions & 22 deletions tests/test_section_landmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,49 +8,51 @@


class _LandmarkParser(HTMLParser):
"""Collect <section> 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)
Loading