Communitygithub.com

OthmanAdi/bookshelf

웹 프로젝트에 3D 책장을 추가합니다. 북마크 관리자, 나중에 읽을 목록 또는 읽기 목록을 위한 드래그 가능한 책등 행으로, 저장된 링크는 읽기 페이지로 열리는 책입니다. 네 가지 TypeScript 모듈을 제공하며 런타임 종속성이 없습니다: CSS 3D 선반(DOM, WebGL 불필요), 스프링 및 드래그 모션 엔진, 가져오기/내보내기가 가능한 버전 관리형 로컬 북마크 저장소, 그리고 읽기 위치를 기억하는 안전한 텍스트 리더입니다.

bookshelf란 무엇인가요?

이 스킬은 북마크, 나중에 읽을 목록, 독서 목록을 시각적으로 관리할 수 있는 3D 책장(Bookmark Bookshelf) 라이브러리를 프로젝트에 통합하는 방법을 설명합니다. 라이브러리는 `shelf`(3D 책등 렌더링), `motion`(스프링 및 드래그 애니메이션), `store`(로컬 스토리지 기반 북마크 저장소, 가져오기/내보내기 지원), `reader`(텍스트 리더, 읽기 위치 기억)의 네 가지 독립적인 TypeScript 모듈로 구성되어 있으며, 런타임 종속성이 없습니다. 각 모듈은 단독으로 또는 조합하여 사용할 수 있으며, 가장 완전한 형태는 전체 구성을 위해 `src/app/`의 컴포지션 파일을 함께 복사해야 합니다. 스킬의 주요 역할은 소스 루트를 찾아 필요한 모듈을 추출하는 것입니다. 사전에 체크아웃된 경로를 확인하고, 사용자가 동의한 경우에만 공개 저장소를 얕은 복제(shallow clone)합니다. 복제 후에는 각 모듈의 계약(입력, 출력, 이벤트)을 참조 문서(`references/modules.md`, `references/composition.md`)를 통해 파악하고, 워크플로우 예제를 참고하여 올바르게 연결해야 합니다. 또한, 3D 컨텍스트 유지, 드래그와 스크롤 충돌 방지, 안전한 읽기 및 가져오기 등 라이브러리의 핵심 규칙을 지키며 통합해야 합니다.

지원 대상✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/OthmanAdi/bookmark-bookshelf/tree/HEAD/.agents/skills/bookshelf

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

북마크 책장(Bookmark Bookshelf)

런타임 종속성이 없는 네 개의 작은 TypeScript 모듈입니다. 각 모듈은 단독으로 작동합니다. 함께 사용하면 저장된 링크를 드래그하고 열고 읽을 수 있는 책장이 만들어집니다. 이 스킬은 버전 0.2.0을 설명합니다.

모듈폴더기능알지 못하는 것
shelfsrc/shelf자세(pose)에 대한 책을 그리고 의도(포커스, 열기, 이동, 점프)를 보고합니다시간, 제스처, 저장소
motionsrc/motion스프링, 드래그, 플릭, 스냅, 열기 및 닫기 진행률을 처리합니다책, DOM 구조
storesrc/store로컬 저장소의 버전 관리형 라이브러리: 유효성 검사, 마이그레이션, 가져오기 및 내보내기, 검색, 정렬, 하나의 선반을 파일 또는 링크로DOM, 렌더링
readersrc/reader저장된 텍스트를 텍스트 노드로 표시하고 위치를 추적 및 복원하며, 텍스트 크기, 개인 메모를 관리하고 "읽음으로 표시"합니다선반, 저장소

모듈은 이 스킬 폴더 안에 있지 않습니다. 모듈은 Bookmark Bookshelf 저장소의 체크아웃 복사본에 있으며, 첫 번째 작업은 이를 찾는 것입니다. 모듈 폴더를 복사하고 다시 입력하지 마십시오.

0. 소스 루트 찾기

다음 경로를 순서대로 시도하고 처음 성공하는 경로에서 멈춥니다.

  1. 스킬 옆에 있습니다. 이 파일이 <root>/.agents/skills/bookshelf/SKILL.md에 있고 <root>/src/shelf/index.ts가 존재하면 <root>가 소스 루트입니다. 이는 스킬이 제자리에서 사용되거나 전체 저장소를 유지하는 플러그인으로 설치된 경우입니다.

  2. 사용자가 제공한 경로입니다. 스킬이 다른 곳(에이전트의 스킬 폴더)에 복사된 경우, 사용자가 이미 체크아웃을 가지고 있을 수 있습니다. 요청에 경로가 포함되어 있으면 이를 소스 루트로 사용합니다. 그렇지 않으면 경로를 묻고, 같은 질문에 경로 3도 제안합니다.

  3. 사용자가 동의한 후에만 얕은 복제합니다. 먼저 물어보고 무슨 일이 일어날지 명확히 말합니다. 공개 저장소 https://github.com/OthmanAdi/bookmark-bookshelf가 릴리스 태그 v0.2.0에서 새로운 임시 폴더로 복제되며, 다른 것은 다운로드되지 않습니다. 사용자가 동의하기 전에는 아무것도 가져오지 마십시오. 동의한 후에는 다음을 실행합니다:

    git clone --depth 1 --branch v0.2.0 https://github.com/OthmanAdi/bookmark-bookshelf <임시 폴더>
    

    임시 폴더는 프로젝트 외부의 새롭고 빈 디렉토리입니다(시스템 임시 디렉토리도 괜찮습니다). 사용자에게 위치를 알려줍니다. 이것이 소스 루트입니다. 여기서 복사하고 내부를 편집하지 마십시오. 마지막에 이 폴더를 삭제할 수 있다고 알려줍니다. 클론이 실패하면(네트워크 없음, git 없음, 태그 누락) 그렇게 말하고 경로 2로 돌아갑니다.

  4. 복사하기 전에 확인합니다. 소스 루트에서:

    • package.json에 "name": "bookmark-bookshelf"가 있습니다. version을 기록합니다.
    • 다음 파일이 존재합니다: src/shelf/index.ts, src/motion/index.ts, src/store/index.ts, src/reader/index.ts, src/app/bookshelf.ts, src/app/bookshelf.css, src/app/flight.ts, src/styles/tokens.css.
    • git 체크아웃인 경우 git -C <root> rev-parse --short HEAD를 기록합니다.

    마커가 누락되면 중지하고 어떤 것인지 알려줍니다. 이 스킬은 버전 0.2.x를 설명합니다. 다른 버전의 경우 참조 파일을 신뢰하기 전에 각 모듈의 types.ts를 읽으십시오: 0.1.0에는 flight.ts가 없으며, shelf, store 및 reader의 계약이 다릅니다(references/modules.md 끝 참조).

  5. 복사한 소스 경로, 버전 및 커밋, 그리고 직접 만든 클론인지 보고하여 나중에 업데이트를 비교할 수 있도록 합니다.

아래 src/, examples/ 또는 public/로 시작하는 모든 경로는 해당 소스 루트를 기준으로 합니다.

1. 먼저 범위 결정하기

사용자에게 필요한 것이 무엇인지 물어본 다음, 그것만 가져옵니다.

사용자가 원하는 것가져올 것또한 가져올 것
드래그 가능한 책등의 3D 행shelf, motionsrc/styles/tokens.css
자체 클릭 처리가 있는 선반(드래그 불필요)shelftokens.css
재개(resume) 기능이 있는 읽기 뷰readertokens.css, src/styles/base.css의 .bb-button, .bb-label 및 .bb-visually-hidden 규칙
UI가 없는 북마크 라이브러리: 가져오기, 내보내기, 검색, 선반 공유store없음
다른 행을 위한 드래그, 플릭 및 스냅motion없음
자체 데이터로 리더로 열리는 선반shelf, motion, readertokens.css, base.css의 동일한 세 가지 규칙
전체 구성네 개 모두, src/app/bookshelf.ts, src/app/bookshelf.css, src/app/flight.tssrc/styles/*, src/data/* (예제 링크가 필요하면)

모듈은 서로를 가져오지 않습니다. 두 모듈 폴더 사이에 가져오기가 나타나면 통합이 잘못된 것입니다. 만나는 지점은 호스트의 자체 컴포지션 파일에 속해야 합니다(src/app/bookshelf.ts 참조).

전체 구성은 일곱 가지 항목이며, 목록에서 하나를 빼먹기 쉽습니다: 네 개의 모듈 폴더, src/app/bookshelf.ts, 해당 스타일시트 src/app/bookshelf.css, 그리고 src/app/flight.ts(선반과 페이지 사이의 표지를 운반)입니다. 스크립트는 다른 두 개를 가져오므로, 이들이 없으면 빌드가 실패하여 파일 누락 오류가 발생합니다. 세 파일을 함께 모듈 폴더 옆에 있는 폴더로 복사합니다.

구성 주변의 앱(바와 메뉴, 링크 추가 및 가져오기 및 선반 공유를 위한 대화상자, 목록 보기)은 src/app/의 나머지와 app.html입니다. 해당 파일들은 서로 및 app.html의 요소 ID에 의존합니다. 전체를 가져와서 적용하거나, 저장소 함수를 기반으로 자체 인터페이스를 구축하십시오(방법은 references/composition.md에 나와 있습니다).

2. 파일 가져오기

  1. 선택한 각 폴더를 src/에서 변경하지 않고 테스트(*.test.ts 포함)와 함께 호스트의 소스 트리로 복사합니다.
  2. src/styles/tokens.css를 복사하고 한 번 로드합니다. 모듈은 색상, 서체, 간격 및 타이밍에 대해 --bb-* 사용자 정의 속성을 읽습니다. 스타일을 변경하려면 토큰을 재정의하고 모듈 CSS를 편집하지 마십시오. 토큰은 값만 정의합니다. 페이지 배경, 본문 글꼴 및 리셋은 src/styles/base.css에 있습니다. 빈 프로젝트에서는 해당 파일도 복사하거나(tokens.css와 fonts.css를 가져오므로 3단계를 건너뛰면 글꼴 가져오기를 제거) body에 background: var(--bb-paper); color: var(--bb-ink); font-family: var(--bb-font-serif)를 직접 설정합니다.
  3. 글꼴은 선택 사항입니다. src/styles/fonts.css는 Newsreader와 IBM Plex Mono(SIL OFL 1.1)를 번들로 제공하며 두 개의 개발 종속성이 필요합니다: @fontsource-variable/newsreader와 @fontsource/ibm-plex-mono. 이를 제공하는 경우 public/licenses/에 라이선스 텍스트도 함께 제공합니다. 제공하지 않으면 base.css에서 @import './fonts.css' 줄을 제거하면 토큰 스택이 시스템 글꼴로 대체됩니다.
  4. shelf.ts와 reader.ts는 CSS를 가져옵니다(import './shelf.css'). 이를 처리하려면 CSS 가져오기를 지원하는 번들러(Vite, webpack, Parcel, CSS 로더가 있는 esbuild)가 필요합니다. 번들러가 없으면 해당 가져오기 줄을 삭제하고 CSS 파일을 페이지에서 연결합니다: 선반의 경우 shelf.css, cover.css 및 variants.css, 리더의 경우 reader.css 및 reader-text.css.
  5. TypeScript: 코드는 strict, noUncheckedIndexedAccess 및 verbatimModuleSyntax와 함께 moduleResolution: "bundler"로 작성되었습니다. 더 느슨한 설정에서도 컴파일됩니다.
  6. 빈 프로젝트를 위한 도구: npm i -D vite typescript vitest happy-dom @playwright/test, Node ^22.12.0 || ^24.0.0 || >=26.0.0. references/checks.md에 최소 Vitest 및 Playwright 구성이 있습니다.

프레임워크 호스트(React, Vue, Svelte): 모든 모듈은 mount(element, options)이며 destroy()가 있는 핸들을 반환합니다. 프레임워크의 "첫 번째 렌더링 후" 훅에서 마운트하고 정리 훅에서 해제합니다. references/composition.md에 이를 래핑하는 간단한 함수가 있습니다.

3. 연결하기

계약(입력, 출력, 이벤트)은 references/modules.md를, 각 조합에 대한 복사 가능한 연결은 references/composition.md를 읽으십시오. 가장 짧은 유용한 예는 선반과 모션입니다:

import { createShelfMotion, prefersReducedMotion } from './motion';
import { mountShelf } from './shelf';

const motion = createShelfMotion({ count: items.length, reducedMotion: prefersReducedMotion });
const shelf = mountShelf(host, {
  items,
  label: 'My shelf',
  onLoopChange: (loop) => motion.setLoop(loop), // 선반이 행의 루핑 여부를 결정합니다
});
motion.setLoop(shelf.loop);
motion.setTiming(shelf.variant.motion);

motion.subscribe((state) => {
  shelf.setPose({ position: state.position, open: 0, reduced: state.reduced });
  shelf.element.dataset.settled = state.settled ? '1' : '0'; // 테스트가 안정될 때까지 기다릴 수 있게 함
});
motion.attach(shelf.surface, {
  pxPerItem: () => shelf.pitch(),
  extent: () => shelf.surface.clientWidth,
  enabled: () => !shelf.fixed, // 짧은 행은 고정되어 있으므로 드래그가 움직이지 않아야 함
});

shelf.on((intent) => {
  if (intent.type === 'focus') motion.goTo(intent.slot);
  else if (intent.type === 'step') motion.step(intent.direction);
  else if (intent.type === 'jump') motion.goTo(intent.to === 'first' ? 0 : items.length - 1);
  else openSomething(intent.id);
});

items는 { id, title, source?, author?, cloth?, pattern?, seed?, progress?, thickness?, stature?, outline?, note?, done? }의 배열입니다. 조합의 실행 가능한 버전은 examples/에 있습니다.

루핑은 선반이 결정합니다. 행은 볼륨이 장면의 4/5 이상을 채울 때만 루핑됩니다. 더 짧은 행은 두 개의 책 지지대 사이 중간에 고정됩니다(shelf.fixed): 화살표, 방향키 및 책등 클릭은 표시된 볼륨을 선택하며 드래그는 아무 작업도 수행하지 않습니다. 사용자에게 항목 수에 따라 두 가지 중 어떤 것을 볼지 알려주십시오. 데스크톱 화면에서 8개의 제목은 고정되어 있습니다.

4. 작동을 유지하는 규칙

변경하면 깨지는 것들입니다. 각각 이유가 있습니다.

  • .bb-shelf__row와 책 면 사이의 어떤 요소도 overflow, filter, clip-path, mask, contain: paint 또는 1 미만의 opacity를 설정해서는 안 됩니다. 이들 중 하나라도 3D 컨텍스트를 평평하게 만들어 책이 평면으로 붕괴됩니다. 대신 장면(원근 요소)을 페이드 처리하십시오.
  • 선반은 주어진 자세를 그립니다. 행을 따라 이동하고 여는 것은 모션 모듈이 소유한 프레임 루프에서 setPose를 통해 발생합니다. .bb-book에 CSS 전환을 추가하면 이 루프와 충돌합니다. 호버 효과는 .bb-book__body에 적용하십시오.
  • 선반은 표지를 날리지 않습니다. pose.open이 0보다 높으면 중앙 볼륨을 숨기고 이웃을 분리합니다. 이동하는 표지는 호스트의 작업입니다(src/app/flight.ts): 고정된 레이어에 하나의 복사본을 transform만으로 shelf.coverPlacement()에서 reader.flightTarget()으로 이동합니다. 선반의 볼륨, 복사본 및 리더의 자체 표지는 함께 표시되지 않습니다.
  • 항목당 하나의 네이티브 버튼, 렌더링된 책당이 아닙니다. 루핑 행은 복사본을 렌더링합니다. 실제 복사본만 button.bb-book__control을 가지며, 복사본은 aria-hidden입니다. 면은 일반 스팬이며 버튼은 그 앞에 있는 별도의 보이지 않는 요소입니다. 면을 포커스 가능하게 만들거나 면을 <button>으로 감싸지 마십시오.
  • 드래그 표면에 touch-action: pan-y를 유지하십시오. none을 사용하면 선반에 닿은 손가락이 더 이상 페이지를 스크롤할 수 없습니다.
  • 터치 드래그 중에는 하위 요소의 lostpointercapture를 무시하십시오. 손가락 아래의 요소가 암시적으로 포인터를 캡처하고, 표면이 캡처를 취하면 해당 하위 요소의 손실 이벤트가 위로 버블링됩니다. gesture.ts는 event.target === element를 확인합니다. 이 확인을 유지하십시오.
  • 볼륨의 터치 탭은 click이 아닌 포인터 이벤트에서 인식됩니다. 속도로 스와이프한 후 브라우저는 다음 탭의 클릭을 삼킬 수 있습니다. 따라서 shelf.ts는 터치가 10px 반경을 벗어나지 않고 700ms 미만 지속된 경우 pointerup에서 볼륨을 활성화하고, 동일한 탭에 대해 브라우저가 보내는 클릭을 건너뜁니다(detail이 0인 키보드 클릭은 항상 계산됨). gesture.ts도 드래그가 잠기면 touchmove를 소비하여 브라우저 플링크가 시작되지 않도록 합니다.
  • 리더는 createElement와 textContent로만 DOM을 구성합니다. src/reader 어디에도 innerHTML이 없으며, 소스 수준 테스트에서 이를 강제합니다. 링크는 http: 및 https:에 대해서만 유지됩니다.
  • 저장소는 http: 및 https: 주소만 허용하며 절대 가져오지 않습니다. 앱은 링크와 사용자가 붙여넣은 텍스트를 표시합니다. 리더를 "더 똑똑하게" 만들기 위해 원격 가져오기를 추가하지 마십시오. 브라우저는 대부분의 사이트에서 이를 차단하고 프록시는 보안 모델을 변경합니다.
  • 가져오기는 절대 덮어쓰지 않으며, 외부에서 도착한 것은 미리보기 없이 적용되지 않습니다. 선반 파일 또는 선반 링크는 낯선 사람에게서 온 것입니다. readShelfFile 또는 decodeShelfLink로 읽고, summariseShelfFile을 표시한 후 사용자가 동의한 경우에만 importShelf를 호출하십시오. 읽기 위치, 읽음 표시 및 개인 메모는 절대 선반 파일에 들어가지 않습니다.
  • 축소 모션(reduced motion)은 자체 경로이며, 아무것도 이동하지 않습니다. 모션 모듈의 reducedMotion은 이동을 즉시 만들고 열기 및 닫기를 150ms 페이드로 전환합니다. 한 번 읽은 값이 아닌 실시간 선호도(prefersReducedMotion)를 전달하십시오.
  • 사용자가 볼 수 있는 곳에 항상 리더 경계를 명시하십시오. 텍스트가 없는 링크는 "원본 열기"를 표시하고 텍스트를 추가하는 한 단계 방법을 제공해야 합니다.

5. 결과 확인

적용되는 항목을 실행하십시오. references/checks.md에 명령, 어설션할 DOM 훅 및 실제 마우스 및 터치 드래그를 위한 Playwright 스니펫이 있습니다.

가져온 항목최소 증명
모든 모듈npx tsc --noEmit 깔끔히 통과, 해당 모듈의 *.test.ts가 happy-dom과 함께 Vitest에서 통과
shelf책 렌더링, setPose 시 data-focus 변경, 항목당 정확히 하나의 .bb-book__control, 방향키가 step을 내보냄
shelf + motion루핑하기에 충분히 긴 행에서 실제 마우스 드래그가 data-focus-id를 변경하고 data-settled="1"로 끝남. 고정된 행에서는 방향키가 변경하고 드래그는 변경하지 않음. 수직 터치 드래그가 페이지를 스크롤함
readerEscape가 onClose를 호출, 새로고침이 scrollTop을 복원, 텍스트의 <script>가 텍스트로 표시됨
storejavascript: 주소가 거부됨, 내보내기 후 가져오기 왕복, 동일한 저장소에 대한 두 번째 createStore가 데이터를 봄, 선반이 encodeShelfLink 후 decodeShelfLink에서 살아남음
네 개 모두references/checks.md의 설정 스모크 테스트 통과. 볼륨 열기, 스크롤, 새로고침: 동일한 볼륨이 같은 위치에서 열림. 닫기: 포커스가 해당 볼륨으로 돌아옴

무엇을 실행했고 무엇을 실행하지 않았는지 보고하십시오. 타입 검사를 통과한 빌드는 드래그가 테스트되었음을 의미하지 않습니다.

관련 스킬