Bücherregal für Lesezeichen
Vier kleine TypeScript-Module ohne Laufzeitabhängigkeiten. Jedes funktioniert allein. Zusammen ergeben sie ein Regal mit gespeicherten Links, die man ziehen, öffnen und lesen kann. Dieser Skill beschreibt Version 0.2.0.
| Modul | Ordner | Macht | Weiß nichts von |
|---|---|---|---|
| shelf | src/shelf | Zeichnet Volumen für eine Pose, meldet Absichten (fokussieren, öffnen, schrittweise, springen) | Zeit, Gesten, Speicher |
| motion | src/motion | Federn, Ziehen, Wischen, Einrasten, Öffnungs- und Schließfortschritt | Bücher, DOM-Struktur |
| store | src/store | Versionierte Bibliothek im lokalen Speicher: Validierung, Migration, Import und Export, Suche, Sortierung, ein Regal als Datei oder Link | DOM, Rendering |
| reader | src/reader | Zeigt gespeicherten Text als Textknoten an, verfolgt und stellt die Position wieder her, Textgröße, private Notizen, „Als gelesen markieren“ | Regale, Speicher |
Die Module befinden sich nicht in diesem Skill-Ordner. Sie liegen in einem Checkout des Bookmark-Bookshelf-Repositorys. Die erste Aufgabe ist es, dieses zu finden. Kopiere die Modul-Ordner daraus und tippe sie nicht neu ab.
0. Quellverzeichnis finden
Probiere die Routen in dieser Reihenfolge und stoppe bei der ersten, die funktioniert.
-
Neben dem Skill. Wenn sich diese Datei unter
<root>/.agents/skills/bookshelf/SKILL.mdund<root>/src/shelf/index.tsexistiert, dann ist<root>das Quellverzeichnis. Dies ist der Fall, wenn der Skill direkt genutzt wird oder als Plugin installiert wurde, das das gesamte Repository behält. -
Ein Pfad, den die Person angibt. Wenn der Skill woanders hin kopiert wurde (z. B. in den Skills-Ordner eines Agenten), hat die Person möglicherweise bereits ein Checkout. Wenn die Anfrage einen Pfad enthält, verwende ihn als Quellverzeichnis. Wenn nicht, frage danach und biete Route 3 in derselben Frage an.
-
Ein flaches Klon, nur nach Zustimmung der Person. Frage zuerst und sage klar, was passieren wird: Das öffentliche Repository
https://github.com/OthmanAdi/bookmark-bookshelfwird unter dem Release-Tagv0.2.0in einen neuen temporären Ordner geklont, und nichts weiter wird heruntergeladen. Lade nichts herunter, bevor die Person nicht zugestimmt hat. Nach einem Ja, führe aus:git clone --depth 1 --branch v0.2.0 https://github.com/OthmanAdi/bookmark-bookshelf <temporärer Ordner>Der temporäre Ordner ist ein neues, leeres Verzeichnis außerhalb des Projekts (das System-Temp-Verzeichnis ist in Ordnung). Sage der Person, wo es ist. Es ist das Quellverzeichnis. Kopiere daraus, bearbeite niemals darin, und sage am Ende, dass es gelöscht werden kann. Wenn das Klonen fehlschlägt (kein Netzwerk, kein Git, fehlendes Tag), sage das und gehe zurück zu Route 2.
-
Überprüfe vor dem Kopieren. Im Quellverzeichnis:
package.jsonhat"name": "bookmark-bookshelf". Notiere dessenversion.- Diese Marker existieren:
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. - Wenn es ein Git-Checkout ist, notiere
git -C <root> rev-parse --short HEAD.
Wenn ein Marker fehlt, stoppe und sage, welcher fehlt. Dieser Skill beschreibt Version
0.2.x. Für eine andere Version lies dietypes.tsjedes Moduls, bevor du den Referenzdateien hier vertraust: 0.1.0 hat keinflight.ts, und seine shelf-, store- und reader-Verträge unterscheiden sich (siehe Ende vonreferences/modules.md). -
Melde den Quellpfad, die Version und den Commit, von dem du kopiert hast, und ob es ein von dir erstellter Klon war, damit ein späteres Update damit verglichen werden kann.
Jeder Pfad, der unten mit src/, examples/ oder public/ beginnt, ist relativ zu diesem Quellverzeichnis.
1. Zuerst den Umfang festlegen
Frage, was die Person braucht, und nimm dann nur das.
| Sie wollen | Nimm | Nimm auch |
|---|---|---|
| Eine 3D-Reihe von Buchrücken, die man ziehen kann | shelf, motion | src/styles/tokens.css |
| Ein Regal mit eigenem Klick-Handling, ohne Ziehen | shelf | tokens.css |
| Eine Leseansicht mit Wiederaufnahme | reader | tokens.css, die Regeln .bb-button, .bb-label und .bb-visually-hidden aus src/styles/base.css |
| Eine Lesezeichen-Bibliothek ohne UI: Import, Export, Suche, Teilen eines Regals | store | nichts |
| Ziehen, Wischen und Einrasten für eine andere Reihe | motion | nichts |
| Ein Regal, das in einen Reader aufgeht, mit eigenen Daten | shelf, motion, reader | tokens.css, dieselben drei Regeln aus base.css |
| Die gesamte Komposition | alle vier, src/app/bookshelf.ts, src/app/bookshelf.css, src/app/flight.ts | src/styles/*, src/data/* falls die Beispiel-Links gewünscht sind |
Module importieren einander nie. Wenn ein Import zwischen zwei Modul-Ordnern auftaucht, ist die Integration falsch: Der Treffpunkt gehört in die eigene Kompositionsdatei des Hosts (siehe src/app/bookshelf.ts).
Die gesamte Komposition besteht aus sieben Dingen, und es ist leicht, eines zu vergessen: die vier Modul-Ordner, src/app/bookshelf.ts, sein Stylesheet src/app/bookshelf.css und src/app/flight.ts, das den Buchdeckel zwischen Regal und Seite transportiert. Das Skript importiert die anderen beiden, daher schlägt ein Build ohne sie mit einem Fehler fehl. Kopiere die drei Dateien zusammen in einen Ordner, der neben den Modul-Ordnern liegt.
Die App rund um die Komposition (die Leiste und ihre Menüs, die Dialoge zum Hinzufügen von Links, zum Importieren vieler Links und zum Teilen eines Regals, die Listenansicht) ist der Rest von src/app/ zusammen mit app.html. Diese Dateien hängen voneinander und von Element-IDs in app.html ab. Nimm sie als Ganzes und passe sie an, oder baue deine eigene Oberfläche auf den Funktionen des Stores, die references/composition.md zeigt.
2. Dateien einbringen
- Kopiere jeden ausgewählten Ordner aus
src/unverändert, einschließlich der Tests (*.test.ts), in den Quellbaum des Hosts. - Kopiere
src/styles/tokens.cssund lade es einmal. Die Module lesen--bb-*Custom Properties für Farbe, Typografie, Abstände und Timing. Zum Umstyling überschreibe die Tokens und bearbeite kein Modul-CSS. Tokens definieren nur Werte: Der Hintergrund, die Schriftart und die Resets leben insrc/styles/base.css. In einem leeren Projekt kopiere diese Datei ebenfalls (sie importierttokens.cssundfonts.css, also entferne den Fonts-Import, wenn du Schritt 3 überspringst) oder setzebackground: var(--bb-paper); color: var(--bb-ink); font-family: var(--bb-font-serif)selbst aufbody. - Schriftarten sind optional.
src/styles/fonts.cssbindet Newsreader und IBM Plex Mono (SIL OFL 1.1) und benötigt zwei Dev-Abhängigkeiten:@fontsource-variable/newsreaderund@fontsource/ibm-plex-mono. Wenn du sie auslieferst, liefere auch die Lizenztexte inpublic/licenses/mit. Ohne sie entferne die Zeile@import './fonts.css'ausbase.css, und die Token-Stapel fallen auf System-Schriftarten zurück. shelf.tsundreader.tsimportieren ihr CSS (import './shelf.css'). Das benötigt einen Bundler, der CSS-Imports verarbeitet (Vite, webpack, Parcel, esbuild mit einem CSS-Loader). Ohne einen solchen lösche diese Importzeilen und verlinke die CSS-Dateien von der Seite aus:shelf.css,cover.cssundvariants.cssfür das Regal,reader.cssundreader-text.cssfür den Reader.- TypeScript: Der Code ist geschrieben für
strict,noUncheckedIndexedAccessundverbatimModuleSyntax, mitmoduleResolution: "bundler". Er kompiliert auch unter weniger strengen Einstellungen. - Werkzeuge für ein leeres Projekt:
npm i -D vite typescript vitest happy-dom @playwright/test, Node^22.12.0 || ^24.0.0 || >=26.0.0.references/checks.mdenthält die minimale Vitest- und Playwright-Konfiguration.
Framework-Hosts (React, Vue, Svelte): Jedes Modul ist mount(element, options) und gibt ein Handle mit destroy() zurück. Mounte im „nach dem ersten Rendern“-Hook des Frameworks und zerstöre es in dessen Aufräumfunktion. references/composition.md enthält eine einfache Funktion zum Einwickeln.
3. Verdrahten
Lies references/modules.md für die Verträge (Eingaben, Ausgaben, Ereignisse) und references/composition.md für kopierfertige Verdrahtung jeder Kombination. Die kürzeste nützliche, Regal plus Bewegung:
import { createShelfMotion, prefersReducedMotion } from './motion';
import { mountShelf } from './shelf';
const motion = createShelfMotion({ count: items.length, reducedMotion: prefersReducedMotion });
const shelf = mountShelf(host, {
items,
label: 'Mein Regal',
onLoopChange: (loop) => motion.setLoop(loop), // das Regal entscheidet, ob die Reihe schleift
});
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'; // ermöglicht Tests zu warten, bis Ruhe einkehrt
});
motion.attach(shelf.surface, {
pxPerItem: () => shelf.pitch(),
extent: () => shelf.surface.clientWidth,
enabled: () => !shelf.fixed, // eine kurze Reihe steht still, also darf kein Zug sie bewegen
});
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 ist ein Array von { id, title, source?, author?, cloth?, pattern?, seed?, progress?, thickness?, stature?, outline?, note?, done? }. Ausführbare Versionen der Kombinationen befinden sich in examples/.
Das Schleifen wird vom Regal entschieden. Eine Reihe schleift nur, wenn ihre Volumen mindestens vier Fünftel der Szene ausfüllen. Eine kürzere Reihe steht still in der Mitte zwischen zwei Buchstützen (shelf.fixed): Die Pfeile, die Pfeiltasten und ein Klick auf einen Buchrücken wählen das präsentierte Volumen aus, und ein Zug tut nichts. Sage der Person, welche der beiden sie mit ihrer Anzahl an Elementen sehen wird, denn acht Titel auf einem Desktop-Bildschirm stehen still.
4. Regeln, die dafür sorgen, dass es funktioniert
Dies sind die Dinge, die kaputt gehen, wenn sie geändert werden. Jedes hat einen Grund.
- Nichts zwischen
.bb-shelf__rowund einer Buchseite darfoverflow,filter,clip-path,mask,contain: paintoderopacityunter 1 setzen. Jedes von ihnen flacht den 3D-Kontext ab, und die Bücher fallen zu einer Ebene zusammen. Blende stattdessen die Szene (das Perspektivelement) aus. - Das Regal zeichnet die Pose, die ihm gegeben wird. Bewegung entlang der Reihe und das Öffnen kommen von
setPosein einer Frameschleife, die dem Bewegungsmodul gehört. Das Hinzufügen von CSS-Transitionen zu.bb-bookkämpft gegen diese Schleife. Hover-Effekte gehören auf.bb-book__body. - Das Regal fliegt den Buchdeckel nicht. Während
pose.openüber 0 ist, versteckt es das zentrierte Volumen und teilt seine Nachbarn. Ein reisender Buchdeckel ist die Aufgabe des Hosts (src/app/flight.ts): Eine Kopie auf einer fixierten Ebene, nur übertransformbewegt, vonshelf.coverPlacement()zureader.flightTarget(). Das Volumen des Regals, die Kopie und der eigene Deckel des Readers sind nie gleichzeitig sichtbar. - Ein nativer Button pro Element, nicht pro gerendertem Buch. Eine schleifende Reihe rendert Kopien. Nur die echte Kopie trägt
button.bb-book__control, und Kopien sindaria-hidden. Die Buchseiten sind einfache Spans, und der Button ist ein separates, unsichtbares Element davor. Mache Buchseiten nicht fokussierbar und wickele die Buchseiten nicht in einen<button>. - Behalte
touch-action: pan-yauf der Ziehfläche. Mitnonekann ein Finger, der auf dem Regal landet, die Seite nicht mehr scrollen. - Ignoriere während eines Touch-Zugs
lostpointercapturevon Nachkommen. Das Element unter dem Finger fängt den Zeiger implizit ein, und wenn die Fläche die Aufnahme übernimmt, steigt das Verlustereignis dieses Nachkommens auf.gesture.tsprüftevent.target === element. Behalte die Prüfung. - Touch-Taps auf Volumen werden von Zeigerereignissen erkannt, nicht von
click. Nach einem schnell losgelassenen Wisch können Browser den Klick des nächsten Taps verschlucken.shelf.tsaktiviert daher ein Volumen beipointerup, wenn die Berührung einen Radius von 10 Pixeln nie verlassen hat und unter 700 ms dauerte, und überspringt den Klick, den der Browser für denselben Tap sendet (Tastaturklicks mitdetail0 zählen immer).gesture.tsverbraucht auchtouchmove, sobald ein Zug gesperrt ist, damit kein Browser-Fling startet. - Der Reader baut DOM nur mit
createElementundtextContent. Es gibt keininnerHTMLirgendwo insrc/reader, und ein Test auf Quellcodeebene erzwingt dies. Links werden nur fürhttp:undhttps:behalten. - Der Store akzeptiert nur
http:- undhttps:-Adressen und ruft niemals eine ab. Die App zeigt einen Link und den Text an, den die Person eingefügt hat. Füge kein Remote-Fetching hinzu, um den Reader „intelligenter“ zu machen: Browser blockieren dies für die meisten Websites, und ein Proxy ändert das Sicherheitsmodell. - Ein Import überschreibt niemals, und nichts, das von außen kommt, wird ohne Vorschau angewendet. Eine Shelf-Datei oder ein Shelf-Link kommt von einer fremden Person: Lies sie mit
readShelfFileoderdecodeShelfLink, zeigesummariseShelfFile, und rufeimportShelfnur nach Zustimmung der Person auf. Lesepositionen, Lesezeichen und private Notizen gehen niemals in eine Shelf-Datei. - Reduzierte Bewegung ist ein eigener Pfad, bei dem nichts reist.
reducedMotionim Bewegungsmodul macht Bewegungen sofort und verwandelt Öffnen und Schließen in ein 150 ms Einblenden. Übergib die aktuelle Präferenz (prefersReducedMotion), nicht einen einmal gelesenen Wert. - Gib immer die Reader-Grenze an, wo die Person sie sehen kann. Ein Link ohne Text muss „Original öffnen“ zeigen und einen einstufigen Weg, Text hinzuzufügen.
5. Ergebnis prüfen
Führe aus, was zutrifft. references/checks.md enthält die Befehle, die DOM-Hooks, auf die geprüft werden soll, und Playwright-Schnipsel für echte Maus- und Touch-Züge.
| Genommen | Minimale Überprüfung |
|---|---|
| jedes Modul | npx tsc --noEmit sauber, die eigenen *.test.ts bestehen unter Vitest mit happy-dom |
| shelf | Bücher werden gerendert, data-focus ändert sich bei setPose, genau ein .bb-book__control pro Element, Pfeiltasten emittieren step |
| shelf + motion | Bei einer lang genug schleifenden Reihe ändert ein echter Mauszug data-focus-id und endet mit data-settled="1". Bei einer stehenden Reihe ändern die Pfeiltasten es, und ein Zug tut nichts. Ein vertikaler Touch-Zug scrollt die Seite |
| reader | Escape ruft onClose auf, ein Neuladen stellt scrollTop wieder her, ein <script> im Text wird als Text angezeigt |
| store | Eine javascript:-Adresse wird abgelehnt, Export und dann Import ergeben eine Rundreise, ein zweites createStore auf demselben Speicher sieht die Daten, ein Shelf überlebt encodeShelfLink und dann decodeShelfLink |
| alle vier | Der Setup-Smoke-Check in references/checks.md besteht. Öffne ein Volumen, scrolle, lade neu: Dasselbe Volumen ist an derselben Stelle geöffnet. Schließen: Der Fokus ist zurück auf diesem Volumen |
Berichte, was du ausgeführt hast und was nicht. Ein Build, der die Typüberprüfung besteht, hat noch nicht gezeigt, dass er gezogen werden kann.
Aussehen
paper (Standard)