이 글에서 다루는 것
갤러리 카드에서 문서로 들어가는 peek 흐름을 옆(side) → 중앙(center) → 재귀 push → 편집 가능한 peek까지 올렸고, 하나로 뭉쳐 있던 큰 editor.css를 @layer + 디자인 토큰 구조로 쪼개 소비자가 테마를 덮어쓸 수 있게 했다. 각 작업이 어떤 상황에서 무엇을 목표로 어떻게 풀렸고 무엇을 배웠는지를 작업별로 정리한다.
하루 요약
| 작업 | 무엇을 하고 싶었나 | 한 일 | 결과 |
|---|---|---|---|
| Peek UX | 단일 shell + in-place push + breadcrumb stack | overlay 하나 유지하고 activeDocumentId만 교체, RELATED는 in-modal push, editable promotion 시 onPeekContentChange persist | side/center → 재귀 push → 편집 가능 peek, spatial context 유지 |
| State | stale 중간 프레임 차단, refresh 시 cache 정책 분리 | id 변경 시 render-time reset, reopen 경로는 skip 또는 force network | (newId, oldContent) “되돌아간 듯” 프레임·이전 revision 차단 |
| CSS | tokens/base/blocks/chrome/print @layer 분리 | 단일 CSS를 @layer sdui-doc.*로 기계 분리, byte-identical 확인 | 시각 변경 없이 layer별 파일 분리, index/viewer 진입점 분리 |
| Token | --sdui-doc-* 토큰 — 값 동일, 구조만 layer | --sdui-doc-surface 등 토큰 승격 + 기본값 alias | 소비자가 unlayered 한 줄로 테마 override 가능 |
| 배포 | exports/files에 CSS entry 포함, byte-identical 검증 | manifest에 CSS export 추가, changeset minor(시각 변경) 메모 | unstyled viewer 배포 사고 방지 |
1. Nested modal document navigation
배경지식 (개념)
- Peek / nested modal navigation: 오버레이 창(dialog)을 하나만 유지하고 그 안의
documentId만 바꿔 가며 문서를 파고드는 UX. 매번 새 창을 열지 않는다.
어떤 상황이었나
gallery → peek → related page로 이어질 때 depth마다 새 modal/route를 열면 focus trap·scroll lock·animation이 중첩된다. document async resolve 중 (newId, oldDoc content) 가 한 프레임 그려지면 사용자는 “되돌아갔다”고 느낀다. refresh 직후 peek reopen이 cache-first면 이전 revision이 다시 보일 수 있다.
핵심 작업
overlay 하나를 유지하고 내부 activeDocumentId만 교체하면 spatial context가 유지된다. readOnly → editable promotion 시 onPeekContentChange로 persist하고, RELATED 링크는 in-modal push(새 tab 아님)로 처리했다. id 변경 시 render-time reset으로 stale 중간 프레임을 차단하고, reopen 경로는 skip 또는 force network로 cache 정책을 분리했다. router.back()·popstate는 view transition과 incompatible하므로 explicit stack pop + URL push가 안전하다. 결과적으로 side/center → 재귀 push → 편집 가능 peek까지 spatial context를 유지했다.
교훈
- peek는 문서 스택 UX — 겉모습이 아니라 경계 설계다.
- async resolve hook은 id 변경 시 즉시 stale 차단 — “잠깐 옛 문서”가 UX 버그다.
- refresh + cache-first는 reopen 경로에서 skip 또는 force network로 나눈다.
- E2E drag 실패는 baseline 분리 후 제품 vs 시뮬레이션 한계를 구분한다.
→ Nested modal document navigation
2. Layered CSS package
배경지식 (개념)
- Cascade layer (
@layer): CSS 규칙에 우선순위 계층을 매기는 기능.@layer안에 넣은 규칙은, 계층에 넣지 않은(unlayered) 소비자 규칙에게 항상 진다 — 흔히 쓰는 selector 상세도(specificity)와 무관하게. - specificity(상세도): 같은 속성을 두고 어떤 CSS 규칙이 이길지 정하는 셈법. layer가 없으면 이 상세도 싸움만 남는다.
- 디자인 토큰: 색·간격 같은 값을
--sdui-doc-*같은 CSS 변수로 이름 붙여 둔 것. 소비자는 값만 바꿔 테마를 만든다.
어떤 상황이었나
단일 editor.css는 fork·테마·viewer-only 번들 모두 어렵고, 소비자 커스텀 CSS가 layer 밖이면 specificity 전쟁만 남는다. npm publish manifest에 CSS export가 빠지면 소비자는 unstyled viewer를 받는다.
핵심 작업
하나로 뭉쳐 있던 단일 CSS를 @layer sdui-doc.*로 기계적으로 분리하고 --sdui-doc-surface 같은 토큰을 승격했다. 구간을 나눠 이어 붙인 결과가 **원본과 바이트 단위로 동일(byte-identical)**함을 확인해 “시각 변경 없음”을 증명한 뒤, README에 소비자 override 레시피를 적었다. 핵심 규칙: 패키지 CSS는 전부 layer 안, 소비자 테마는 layer 밖(unlayered) 한 줄로 덮는다. --sdui-doc-surface가 정의되지 않아 규칙이 통째로 무효화되던 문제는 기본값 alias로 잡았다. manifest에 CSS export를 추가하고 changeset minor(시각 변경) 메모를 남겨 unstyled viewer 배포 사고를 막았다.
교훈
- CSS split은 cascade 우선순위 UX — 겉모습이 아니라 경계 설계다.
- layer 분리는 “값 바꾸기”가 아니라 소비자 override 가능성을 여는 구조 작업이다.