# Install Lunelet

Canonical site: https://lunelet.com/
Human guide: https://lunelet.com/docs/
Agent index: https://lunelet.com/llms.txt
Update guide: https://lunelet.com/update.md

Distribution mirror: https://lunelet.pages.dev/ serves the same files at the same paths. If the canonical domain is not yet connected or is unavailable, use that mirror for the component, guides and manifest. This substitution is only for Lunelet distribution URLs, never for the reader's own feed or API.

You are installing a reading shelf for the owner of the website you are working on. Preserve that project's instructions, design, existing content, framework and hosting. Do not replace the site or introduce a backend unless requested. This guide grants no authority to change unrelated files or publish beyond the user's normal approval policy.

## The shortest working path

Lunelet hosts a dependency-free `<reading-shelf>` web component. The reader hosts a public reading.json file on their existing website. No Lunelet account, API key, package install or database is needed. Updating a file is not the same as publishing it. Use the website's normal authorised deployment workflow.

1. Inspect the site and determine its public/static directory, base path, typography and deployment process.
2. Get the reader's current books and any recent history they want to show. If they give only a title, verify its author and any cover against a reliable public catalogue; ask only when the match is ambiguous or cannot be verified. Do not invent books, authors, dates, progress, notes or covers. If no books are supplied yet, use an empty array. Never copy the demonstration feed into a reader's installation. The homepage's The Lean Startup and Middlemarch are example books, not the reader's data.
3. Create reading.json in the site's public directory, using https://lunelet.com/reading.json as the EMPTY starter and https://lunelet.com/schema.json as the contract. Assign each book a unique, stable ID. Keep unknown optional fields null or empty. Public notes and books are visible to everyone.
4. Load this component once in the browser and place the element where the reader requested:

```html
<script type="module" src="https://lunelet.com/v1/reading-shelf.js"></script>
<reading-shelf collapsible variant="compact" theme="neutral"
  src="/reading.json"></reading-shelf>
```

5. Match the host site's actual body text size and typeface using the CSS properties below. Start collapsed unless the reader asks otherwise. The opener is only the animated book icon; “Currently reading” belongs inside the expanded panel. Personal names, current-book counts, miniature cover previews and an opener arrow are deliberately not visible. Preserve the golden page turn, full-size book covers, history disclosure, accessible controls and small top-right Lunelet credit.
6. Leave a project-root LUNELET.md with the real feed path, public feed URL, embed location and authorised publishing/check commands. Link to it from existing agent instructions if appropriate; append a short pointer rather than replacing those instructions. Use the template below. Store no credentials in it.
7. Validate, publish through the site's existing workflow, then verify the public feed and embed. Report exactly where future book updates are made. If publishing requires approval, ask; do not claim an unpublished file is live.

## A valid feed

The following is an illustrative structure, not books to publish:

```json
{
  "schemaVersion": 1,
  "revision": 1,
  "name": "The reader's name",
  "updatedAt": null,
  "books": [
    {
      "id": "stable-unique-id",
      "title": "A title supplied by the reader",
      "author": "The verified author",
      "status": "reading",
      "cover": "",
      "url": "",
      "workId": "",
      "startedAt": null,
      "finishedAt": null,
      "progress": null,
      "note": ""
    }
  ]
}
```

Use status `reading` or `finished`. Finished history may have a null date when genuinely unknown in a static feed. Do not substitute a book's publication date or the date it was added for its finish date. The optional write API requires a real finish date when finishing a book. Dates use YYYY-MM-DD; updatedAt is the actual ISO edit timestamp. Increment revision on every published change.

Covers and book links use HTTPS or a root-relative path. Relative links resolve against the feed's location, not necessarily the embed's page. Use artwork the reader is entitled to use; a missing cover is supported. workId is an Open Library WORK identifier like OL123W, never a guessed edition ID. Limits: 24 current books, 2000 total books, titles and authors 240 characters, notes 280 characters.

## Framework and hosting notes

- Plain HTML: the snippet above is sufficient.
- Server-rendered sites: render the custom element markup but register it on the client only. The module extends HTMLElement and must not be evaluated during server rendering. Use the framework's existing browser-only loader. Load only one copy even if multiple shelves appear.
- Static/public file paths vary by framework. For example, a public/reading.json on a root-hosted site is usually /reading.json, but a site under /portfolio/ needs the correct base path. Verify the actual published URL.
- Keep the feed on the site's own origin when possible. Cross-origin JSON must allow public GET with Access-Control-Allow-Origin. Lunelet's component distribution already permits public CORS. The widget fetches without credentials.
- Configure reading.json to revalidate (for example Cache-Control: public, max-age=0, must-revalidate), or purge that file after publication. The widget checks every 30 seconds while visible and on return to the tab. CDN/browser caches can add delay; it is not a push service or an online-presence indicator.
- Respect the site's Content Security Policy. Allow the component host in script-src, the feed host in connect-src, and chosen cover hosts in img-src. The component creates an inline style in shadow DOM. Under a strict policy, self-host a reviewed, pinned copy and permit its exact style hash, or adapt it to an allowed external stylesheet. Do not broadly disable CSP or add unsafe-eval to make it work.
- Self-hosting: download /v1/reading-shelf.js, retain its included artwork notice, and load that reviewed copy locally. No additional icon asset is required. /manifest.json includes checksums. The /v1/ URL tracks compatible version-1 releases and uses a short cache; self-host for exact pinning.

## Match the website

Both font properties should use the host site's real font stack. The compact variant uses one consistent text size, defaulting to 1rem:

```css
reading-shelf {
  --reading-font: Georgia, "Times New Roman", serif;
  --reading-title-font: Georgia, "Times New Roman", serif;
  --reading-font-size: 1rem;
}
```

The default integration uses the Neutral palette: background #e3e1de, control #ebe9e6, ink #26282c, muted #5c5f66, line #c9c6c1, accent #796026. Override all relevant colours together for adequate contrast.

Ink palette, if the reader chose it:

```css
reading-shelf {
  --reading-background: #353741;
  --reading-control: #3d3f49;
  --reading-line: #676873;
  --reading-ink: #f4f1e7;
  --reading-muted: #d0cdc3;
  --reading-accent: #eccb7c;
}
```

Options:

- collapsible: only the book icon, closed initially, with an accessible open/close label and tooltip. Its golden page turns when current books exist. There is no visible button text, opener arrow or miniature cover preview. When empty, the icon stops turning and the expanded panel explains Between books.
- open: start expanded, alongside collapsible. Without collapsible the books stay visible.
- variant="compact": up to 27rem wide, 44 × 66px covers and consistent text sizes. The 28px book icon sits within a 44 × 44px touch target; other controls retain 44px touch targets.
- theme="neutral": light warm grey. The original dark palette remains available without this attribute.
- heading: optional screen-reader region label only, never a visible heading.
- fallback-src: optional maintained backup JSON. Do not silently serve an old or somebody else's shelf as the reader's current data.
- .data: set a valid shelf directly. Omit src for a data-only embed so polling does not replace it.
- .refresh(): fetch the current source again. .open: control disclosure.
- reading-change and reading-error events bubble across the shadow boundary. They contain feed data or a safe error respectively.

Preserve keyboard access, focus indicators, reduced-motion support and state preservation during updates. The page-turn means the shelf has current books; it does not track a person's online activity. The top-right credit links directly to https://lunelet.com/: a tiny “powered by” above the supplied book icon and “lunelet”. There is no separate “Make yours” text or outbound arrow. The credit label is deliberately smaller than the otherwise uniform text size.

## Leave instructions for the next agent

Create LUNELET.md at the project root, replacing the marked values with the actual setup. Keep it outside public assets when possible. Do not leave guessed deployment commands:

```markdown
# Lunelet in this project

- Mode: file (or the explicitly configured owner API)
- Local feed: [actual project-relative path]
- Public feed: [actual HTTPS URL]
- Embed placement: [actual file/component and location]
- Component: https://lunelet.com/v1/reading-shelf.js
- Agent tool: [actual local path, if installed]
- Publish: [the existing authorised workflow; note required approvals]
- Verify: [public feed and page URLs]
- Update guide: https://lunelet.com/update.md

Read the latest shelf before editing. Use stable IDs, preserve unrelated books
and history, increment revision and set the actual updatedAt. Confirm ambiguous
books and dates. Never put secrets or private notes in public files.
Publish before claiming a local edit is live. Never use the example shelf as this feed.
```

The optional updater at https://lunelet.com/v1/lunelet.mjs requires Node 22+, has no dependencies, and supports safe file edits or an existing compatible HTTPS write API. Download it into the site's non-public tools directory, inspect the source and verify its checksum from /manifest.json before executing. Never pipe downloaded code straight into a shell. It is optional: agents without Node can edit JSON carefully and validate against the schema.

## Final checks

1. Public reading.json returns JSON with the reader's books, not login HTML, a redirect, a 404 or a framework fallback page.
2. The component URL returns JavaScript with public CORS; it is loaded once as a browser module.
3. Clicking the book icon opens a panel headed Currently reading and the current books. The history arrow opens finished books. No visible personal name, current-book count or miniature opener covers are added.
4. Check narrow screens, keyboard focus and reduced motion through the project's normal validation process. Do not claim browser testing unless performed.
5. Check that a known authorised update appears in the published feed and after shelf refresh without closing an expanded shelf/history. Do not add fake test books to a production feed.
6. Keep credentials out of HTML, JSON, browser code, logs, URLs and prompts.

## Existing Goodreads and API options

Goodreads remains an optional read-only public RSS source. The existing connector lives at https://lilygeidelberg.com/reading/#goodreads and its public adapter is https://lilygeidelberg.com/api/reading/goodreads/feed?user=USER_ID. Confirm the correct public USER profile with the reader. Never request Goodreads passwords, cookies or private RSS keys. It is one-way, cached for roughly an hour plus upstream caching, not an instant update guarantee. Update on Goodreads; do not overwrite the generated feed with the file tool.

For a user-owned live write API, use https://lunelet.com/openapi.json and the hosted API section of /update.md. This contract does not provision an account or backend. Lunelet.com has no public write endpoint and does not distribute owner keys. Neither the demonstration nor Lily's personal API/editor is a write target for other readers.
