Communitygithub.com

ブックマーク本棚

3D本棚をウェブプロジェクトに追加し、ドラッグ可能な背表紙の行からブックマークマネージャー、後で読むリスト、または読書リストを作成します。保存されたリンクは本になり、読書ページで開きます。

ブックマーク本棚 とは?

これは、依存関係のない4つのTypeScriptモジュール(shelf、motion、store、reader)を提供するAIエージェントスキルです。shelfはDOMベースのCSS 3D本棚を描画し、motionはバネとドラッグのアニメーションエンジン、storeはバージョン管理されたローカルストレージのブックマークストア、readerはテキストリーダーで読書位置を記憶します。各モジュールは独立して動作し、ユーザーの要求に応じて組み合わせて使用できます。例えば、3D本棚の行、ドラッグ可能なカルーセル、ブックマークマネージャー、またはカバーが開いて読書ビューに移行する機能を提供します。スキルはソースコードの場所を特定し、適切なモジュールをコピーしてプロジェクトに統合します。対応プラットフォームはClaude Code、Cursor、Codexなど、AIエージェントがTypeScriptとCSSを扱える環境全般です。

対応✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/OthmanAdi/bookmark-bookshelf/tree/HEAD/.agents/skills/bookshelf

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

ブックマーク本棚 は何をしますか?

依存関係のない4つの小さなTypeScriptモジュール。それぞれ単独で動作します。まとめると、保存したリンクをドラッグ、開封、読書できる本棚になります。このスキルはバージョン0.2.0を説明します。

モジュールフォルダ動作認識しないもの
shelfsrc/shelfポーズに応じて本を描画し、インテント(フォーカス、開く、ステップ、ジャンプ)を報告する時間、ジェスチャー、ストレージ
motionsrc/motionバネ、ドラッグ、フリック、スナップ、開閉の進行度本、DOM構造
storesrc/storeローカルストレージ内のバージョン管理されたライブラリ:検証、移行、インポート/エクスポート、検索、ソート、ファイルまたはリンクとしての本棚1つDOM、レンダリング
readersrc/reader保存されたテキストをテキストノードとして表示し、位置、テキストサイズ、プライベートメモ、「既読」を追跡および復元する本棚、ストレージ

モジュールはこのスキルフォルダ内にはありません。ブックマーク本棚リポジトリのチェックアウトにあり、最初の仕事はそれを見つけることです。モジュールフォルダをそこからコピーし、再入力しないでください。

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
再開機能付きの読書ビューreadertokens.css、src/styles/base.css の .bb-button、.bb-label、.bb-visually-hidden ルール
UIなしのブックマークライブラリ:インポート、エクスポート、検索、本棚の共有storeなし
他の行のためのドラッグ、フリック、スナップmotionなし
独自データでリーダーに開く本棚shelf、motion、readertokens.css、base.css の同じ3つのルール
全体構成4つすべて、src/app/bookshelf.ts、src/app/bookshelf.css、src/app/flight.tssrc/styles/*、例のリンクが必要な場合は src/data/*

モジュールは互いにインポートしません。2つのモジュールフォルダ間にインポートがある場合、統合が間違っています:接合点はホスト自身の構成ファイル(src/app/bookshelf.ts を参照)に属します。

全体構成は7つのファイルで構成され、リストを1つ忘れやすいです:4つのモジュールフォルダ、src/app/bookshelf.ts、そのスタイルシート src/app/bookshelf.css、およびカバーを本棚とページ間で運ぶ src/app/flight.ts。スクリプトは他の2つをインポートするため、それらなしでビルドするとファイル欠落エラーで失敗します。3つのファイルをまとめて、モジュールフォルダの隣に配置するフォルダにコピーします。

構成の周りのアプリ(バーとそのメニュー、リンク追加、複数リンクの取り込みと本棚の共有のためのダイアログ、リストビュー)は、src/app/ の残りと app.html です。これらのファイルは相互に依存し、app.html の要素IDにも依存します。全体として取得して適応させるか、ストアの関数に基づいて独自のインターフェースを構築します。references/composition.md にその方法が示されています。

2. ファイルを取り込む

  1. 選択した各フォルダを src/ からそのままコピーし(テスト *.test.ts を含む)、ホストのソースツリーに配置します。
  2. src/styles/tokens.css をコピーし、1回だけ読み込みます。モジュールは色、タイプ、スペーシング、タイミングに --bb-* カスタムプロパティを読み取ります。再スタイルするには、トークンをオーバーライドし、モジュールCSSを編集しないでください。トークンは値のみを定義します:ページの背景、ボディフォント、リセットは src/styles/base.css にあります。空のプロジェクトでは、そのファイルもコピーします(tokens.css と fonts.css をインポートするため、手順3をスキップする場合はfonts importを削除します)。または、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)をバンドルし、2つの開発依存関係が必要です:@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用に shelf.css、cover.css、variants.css、reader用に 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 で各組み合わせのコピー可能な配線を読んでください。最も短い有用なもの、shelf + motion:

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を埋める場合にのみループします。短い行は2つのブックエンドの間の中央に静止します(shelf.fixed):矢印、矢印キー、背表紙のクリックで表示される本を選択し、ドラッグは何もしません。アイテム数に応じてどちらが表示されるかをユーザーに伝えます。なぜなら、デスクトップ画面で8タイトルは静止するからです。

4. 動作を維持するルール

これらは変更すると壊れるものです。それぞれに理由があります。

  • .bb-shelf__row と本の表面の間にある要素は、overflow、filter、clip-path、mask、contain: paint、または opacity を1未満に設定してはいけません。これらのいずれかが3Dコンテキストを平坦化し、本が平面に崩れます。代わりにシーン(パースペクティブ要素)をフェードアウトします。
  • 本棚は与えられたポーズを描画します。行に沿った移動と開閉は、motionモジュールが所有するフレームループ上の setPose から来ます。.bb-book にCSSトランジションを追加すると、そのループと競合します。ホバー効果は .bb-book__body に適用します。
  • 本棚はカバーを飛行させません。pose.open が0より大きい間、中央の本を非表示にし、隣の本を分離します。移動するカバーはホストの仕事です(src/app/flight.ts):固定レイヤー上に1つのコピーを transform のみで移動させ、shelf.coverPlacement() から reader.flightTarget() へ。本棚の本、コピー、リーダー自身のカバーが同時に表示されることはありません。
  • レンダリングされた本ごとではなく、アイテムごとに1つのネイティブボタン。ループする行はコピーをレンダリングします。実際のコピーのみが button.bb-book__control を持ち、コピーは aria-hidden です。表面はプレーンなspanであり、ボタンはそれらの前にある別の非表示要素です。表面をフォーカス可能にしたり、<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 を呼び出します。読書位置、既読マーク、プライベートメモは本棚ファイルに決して含まれません。
  • モーション削減は独自のパスであり、何も移動しません。motionモジュールの reducedMotion は移動を即座にし、開閉を150msのフェードにします。ライブの設定(prefersReducedMotion)を渡し、一度だけ読み取った値を渡さないでください。
  • ユーザーがリーダーの境界を常に確認できる場所に表示します。テキストのないリンクは「オリジナルを開く」とテキストを追加するためのワンステップの方法を表示する必要があります。

5. 結果を確認する

該当するものを実行します。references/checks.md にコマンド、アサートするDOMフック、実際のマウスおよびタッチドラッグ用のPlaywrightスニペットがあります。

取得したもの最小限の証明
任意のモジュールnpx tsc --noEmit がクリーン、モジュール自身の *.test.ts がVitestと happy-dom でパス
shelf本がレンダリングされ、data-focus が setPose で変更され、アイテムごとに正確に1つの .bb-book__control、矢印キーが step を発行
shelf + motionループするのに十分長い行で、実際のマウスドラッグが data-focus-id を変更し、data-settled="1" で終了。静止行で矢印キーが変更し、ドラッグは変更しない。垂直タッチドラッグがページをスクロール
readerEscapeが onClose を呼び出し、リロードが scrollTop を復元し、テキスト内の <script> がテキストとして表示
storejavascript: アドレスが拒否され、エクスポート後にインポートがラウンドトリップし、同じストレージでの2回目の createStore がデータを認識し、本棚が encodeShelfLink 後に decodeShelfLink でサバイブ
4つすべてreferences/checks.md のセットアップスモークチェックがパス。ボリュームを開き、スクロール、リロード:同じボリュームが同じ場所で開く。閉じる:フォーカスがそのボリュームに戻る

実行したものとしなかったものを報告します。型チェックを通ったビルドは、ドラッグが示されていません。

見た目

paper(デフォルト...

関連スキル