Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@
## 2026-07-10 - Remove unnecessary DOMPurify for performance
**Learning:** 애플리케이션이 `textContent`와 같은 안전한 DOM API만 사용하고 `innerHTML` 등의 위험한 싱크를 사용하지 않는다면 DOMPurify와 같은 라이브러리를 통해 Trusted Types 정책을 생성할 필요가 없음.
**Action:** 불필요한 번들 다운로드 및 스크립트 실행을 방지하기 위해 사용하지 않는 라이브러리를 식별하고 제거할 것.

## 2024-07-25 - SVG와 LCP 이미지의 decoding="async" 사용
**Learning:** SVG 이미지는 디코딩되지 않고 파싱되며, `decoding="async"` 속성은 브라우저의 파싱/렌더링 우선순위에 영향을 주어 오히려 렌더링을 지연시킬 수 있습니다. 특히 LCP(Largest Contentful Paint)에 해당하는 중요 이미지에 이 속성을 적용하는 것은 성능 안티 패턴입니다.
**Action:** SVG 이미지와 `fetchpriority="high"`가 적용된 LCP 이미지에는 `decoding="async"`를 사용하지 않고, 뷰포트 밖의 래스터 이미지(비-SVG)에만 적용하여 렌더링 성능을 최적화해야 합니다.
Comment on lines +16 to +18
14 changes: 9 additions & 5 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
<a href="#top" class="skip-link" data-i18n="nav.skipToContent">본문으로 건너뛰기</a>
<header class="site-header">
<a class="brand" href="#top" aria-label="Contextual Wisdom Lab home">
<img src="assets/context-wisdom-lab-avatar.svg" alt="" width="44" height="44" decoding="async">
<!-- ⚡ Bolt: Removed decoding="async" for SVG/LCP optimization -->
<img src="assets/context-wisdom-lab-avatar.svg" alt="" width="44" height="44">
<span>Contextual Wisdom Lab</span>
</a>
<nav class="site-nav" aria-label="Primary navigation">
Expand Down Expand Up @@ -64,7 +65,8 @@ <h1 data-i18n="hero.title">맥락지혜 연구실</h1>
</div>

<div class="hero-visual">
<img class="context-art" src="assets/context-thread-map.svg" alt="" aria-hidden="true" width="760" height="560" fetchpriority="high" decoding="async">
<!-- ⚡ Bolt: Removed decoding="async" for SVG/LCP optimization -->
<img class="context-art" src="assets/context-thread-map.svg" alt="" aria-hidden="true" width="760" height="560" fetchpriority="high">
<div class="ladder" role="list" aria-label="Data to wisdom ladder">
<div class="ladder-row" role="listitem">
<span>Data</span>
Expand Down Expand Up @@ -194,7 +196,8 @@ <h2 data-i18n="dikw.title">DIKW는 판단 흐름을 점검하는 질문입니다
</p>
</div>
<!-- ⚡ Bolt: Lazy load off-screen image -->
<img class="section-diagram" src="assets/dikw-checkpoints.svg" alt="" aria-hidden="true" loading="lazy" width="1120" height="330" decoding="async">
<!-- ⚡ Bolt: Removed decoding="async" for SVG/LCP optimization -->
<img class="section-diagram" src="assets/dikw-checkpoints.svg" alt="" aria-hidden="true" loading="lazy" width="1120" height="330">
<div class="dikw-grid">
<article>
<span>Data</span>
Expand Down Expand Up @@ -272,7 +275,8 @@ <h2 data-i18n="references.title">참고문헌</h2>
<section id="logo" class="section logo-story">
<div class="logo-mark">
<!-- ⚡ Bolt: Lazy load off-screen image -->
<img src="assets/context-wisdom-lab-avatar.svg" alt="Contextual Wisdom Lab square mark" loading="lazy" width="500" height="500" decoding="async">
<!-- ⚡ Bolt: Removed decoding="async" for SVG/LCP optimization -->
<img src="assets/context-wisdom-lab-avatar.svg" alt="Contextual Wisdom Lab square mark" loading="lazy" width="500" height="500">
</div>
<div>
<h2 data-i18n="logo.title">로고는 흩어진 기록을 하나의 흐름으로 묶습니다</h2>
Expand Down Expand Up @@ -418,14 +422,14 @@ <h2 data-i18n="work.title">연구에서 제품으로</h2>

<footer class="site-footer">
<!-- ⚡ Bolt: Lazy load off-screen image -->
<!-- ⚡ Bolt: Removed decoding="async" for SVG/LCP optimization -->
<img
id="footer-logo"
src="assets/context-wisdom-lab-logo.svg"
alt="맥락지혜 연구실 · Contextual Wisdom Lab"
loading="lazy"
width="920"
height="260"
decoding="async"
>
<p>
<span data-i18n="footer.founded">Founded by</span>
Expand Down
5 changes: 3 additions & 2 deletions tests/test_styles.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ def test_tall_sections_reserve_larger_intrinsic_block_size():


def test_images_decode_without_blocking_rendering():
"""All site images opt into asynchronous decoding."""
"""All site raster images opt into asynchronous decoding (SVGs excluded)."""
parser = _ImageParser()
parser.feed(INDEX.read_text(encoding="utf-8"))

assert parser.images
assert all(image.get("decoding") == "async" for image in parser.images)
raster_images = [img for img in parser.images if not str(img.get("src")).endswith(".svg")]
assert all(image.get("decoding") == "async" for image in raster_images)
Loading