# Discovery and public pages

> How does someone who is not signed in find, hear and save a Toy, and how do search engines and social cards see it? This document covers the public routes the API serves, card prev

Source: https://docs.tune.toys/contributors/discovery/

How does someone who is not signed in find, hear and save a Toy, and how do search engines and social cards see it? This document covers the public routes the API serves, card previews and image derivatives, the public catalog search, HTML metadata and indexing, and the listening/saving flow.

**Status:** implemented local browser/API slice. Search-engine indexing and third-party social cache refresh are external behaviors, not qualified compatibility claims. Managed hosting remains pending; see [implementation status](https://github.com/joshstovall/TuneToys/blob/main/docs/delivery/implementation-status.md).

## Public routes

| Route                                                          | Serves                                                                                                                     | Indexing                                                                    |
| -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `/`                                                            | Product homepage                                                                                                           | Indexable                                                                   |
| `/explore`                                                     | Paginated public catalog with GET search form                                                                              | Landing indexable; search results noindex                                   |
| `/explore/instruments`, `/explore/effects`                     | Category landing pages                                                                                                     | Indexable                                                                   |
| `/sounds`, `/explore/chains`, `/creators`                      | Sounds grid, published chains, creators grid ([sounds](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/sounds.md))                                                         | First unfiltered page indexable; filters, search and later pages noindex    |
| `/sounds/:id`                                                  | One sound: preview, resolved fields, links to its landing pages                                                            | Public indexable; unlisted noindex                                          |
| `/sounds/{genre,instrument,mood}/:value`, `/sounds/tempo/:bpm` | One-facet landing pages over the media vocabularies and 60–200 bpm in steps of 5                                           | Page 1 indexable with at least 4 sounds; unknown values 404                 |
| `/templates`, `/templates/:slug`                               | Official template index and three template pages from `@tunetoys/discovery`                                                | Indexable                                                                   |
| `/learn/...`                                                   | Original practical guides                                                                                                  | Indexable                                                                   |
| `/creators/:handle`                                            | Creator profile: public work, optional liked work, collections, relationships ([community](https://docs.tune.toys/contributors/community/))                  | Created, Sounds and Collections tabs, page 1, indexable; other tabs noindex |
| `/t/:id`                                                       | Current public Toy alias, the search target                                                                                | Indexable                                                                   |
| `/@:handle`                                                    | Publisher storefront: published revision of theme tokens and typed sections, or the generated default (hero, product grid) | Public storefronts indexable                                                |
| `/@:handle/:slug`                                              | Product page: header, trailer and carousels, Overview/Versions/Reviews/Discussion/Docs anchored sections                   | Public listings of public Toys indexable; unlisted noindex                  |
| `/@:handle/:slug/v/:semver`, `/docs/:docSlug`                  | One version's changelog and platforms; one manual, guide or FAQ page                                                       | Follows the listing                                                         |
| `/r/:digest`                                                   | Exact pinned release with historical metadata                                                                              | noindex,follow                                                              |
| `/workspace`, `/library/…`, `/settings/…`                      | Editor, Library, Settings (SPA shell)                                                                                      | noindex,nofollow                                                            |

The templates and guides are original editorial metadata over existing admitted examples, not invented user accounts or catalog releases.

```mermaid
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%%
flowchart LR
  HTML["Server head + semantic fallback"] --> APP["React router + shared shell"]
  APP -->|Preview or Play| ENGINE["Lazy shared engine + Frame controls"]
  APP -->|New / Remix / Workspace| EDITOR["Lazy persistent editor session"]
  MODELS["Visibility-filtered page models"] --> HTML
  MODELS -->|Public JSON| APP
  classDef shared fill:#DCEBDD,stroke:#1C1C1B,color:#1C1C1B
  classDef host fill:#DED8F0,stroke:#1C1C1B,color:#1C1C1B
  class HTML,APP,MODELS shared
  class ENGINE,EDITOR host
```

React renders every public URL inside the same navigation, account and theme shell, including community and notifications. `main.tsx` always mounts the router. The server retains status, redirects, cache/ETag rules, CSP, `<head>` metadata, structured data, sitemaps, images and semantic fallback inside `main[data-fallback]`. Fallback has headings, text, links, images with alt text and working GET forms; the small token-based `fallback.css` is its only stylesheet. React replaces fallback with `createRoot`; this is not hydration.

`pages/marketplace.ts` resolves the published listing/storefront once for HTML and generated `ProductPage`/`StorefrontPage` JSON contracts. `pages/sounds.ts` supplies validated sound landing copy and filters. Their public endpoints are `GET /v1/pages/product`, `/storefront` and `/sounds`; previews retain member checks and private caching. Creator and collection pages use the existing community endpoints. Templates and lessons share the bundled discovery content. After navigation, React updates title, description and canonical; crawler metadata remains server-owned.

The shell loads without an editor document, Frame renderer or audio host. `WorkspaceApp.tsx` loads on the first editor action, then keeps the document and audio session mounted across pages. Public page chunks have a 450 KiB gzip cold-load budget, verified from the build's actual module map. Template and release Preview load the real engine on demand, last at most twelve seconds and respect lower/muted monitor preferences. Switching, cancellation, hiding and leaving dispose the audition; template controls use the shared Frame renderer during playback.

A root visit with browser recovery, a source fragment or an account-project handoff routes to `/workspace`; otherwise `/` shows the app home. Source fragments never reach server metadata. Template creation opens the real [New Toy chooser](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/new-toy.md), preserving current source until acceptance.

## Card previews

`ToyThumbnail.tsx` draws each Toy as an SVG node-and-cable map through `PatchThumbnail.tsx` and `patchThumbnail.ts`. This includes home, Explore, community, storefront Toy cards, Library, recently played, reusable components and the workspace Toy browser. Saved positions, instance widths/colors, catalog colors, typed cables, visual groups and durable identities are retained. Populated Frames do not replace the graph. Local Toys and bundled official templates render directly; published cards use lazy, visibility-checked source reads through the account-scoped release query cache, which is also used by playback. Remote source is admitted before drawing, and missing or mismatched releases show an unavailable state while the Toy link remains usable. Browsing loads no audio engine or media assets and makes no thumbnail-image requests.

`ToyPreview.tsx` retains inert Frame collages for the separate image-capture entry, with the entry Frame first and an SVG map when no visible Frame exists. `toyThumbnail.ts` supplies independent nested-instance source values without DSP.

## Image derivatives

`PreviewCapture.tsx` is a separate production build entry at `preview-render.html`. The API's bounded `PreviewImages` service opens only this trusted bundle in an isolated Chromium context, admits source through `readToy`, serves every request from local files and blocks external networking, cookies, service workers and downloads. It waits for fonts, layout, lazy icon geometry and decoded Frame images, then captures the existing renderer. Only image assets referenced by Frames are read from the authorized release inventory with verified hashes and lengths; audio assets are not loaded and no engine is initialized. Sharp encodes 320/640 px WebP cards and 1200 × 630 PNG OG artwork; it is not a second Frame renderer.

| Route                                                                      | Output                          |
| -------------------------------------------------------------------------- | ------------------------------- |
| `/previews/r/:digest.webp?w=320\|640`                                      | Release card                    |
| `/previews/template/:slug.webp?w=320\|640`                                 | Template card                   |
| `/social/r/:digest.png`, `/social/t/:id.png`, `/social/template/:slug.png` | OG artwork (WebP also accepted) |
| `/social/site.png`, `/social/site.webp`                                    | Site artwork                    |

| Limit or rule                                                                                                                                       |
| --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Aggregate image budget 16 megapixels / 16 MiB; capture flattens a bounded first raster frame to PNG in a render-only source copy                    |
| Disk key covers admitted source, wrapper metadata, output dimensions/format and the fingerprinted renderer build                                    |
| One capture at a time, at most eight queued jobs, weighted Frame-complexity cap, twenty-second deadline; identical concurrent work shares a promise |
| Cache prunes to 128 MiB / 512 images, writes atomically, closes idle Chromium after thirty seconds                                                  |
| Authorization is checked before cache/render access and again after asynchronous work                                                               |
| Toy images use private/no-store caching; only intentionally public images permit indexing; template/site images have short public lifetimes         |
| Previously downloaded copies and external social caches cannot be recalled                                                                          |

Correct content types, lengths and HEAD responses accompany all images. Public HTML embeds real image URLs with alt text, dimensions, responsive sources and lazy loading below the primary image. Derived bytes stay separate from portable source and immutable releases.

Local capture needs the pinned Chromium headless shell (`node node_modules/playwright-core/cli.js install --only-shell chromium`); `TUNETOYS_CHROMIUM_PATH` selects an existing executable, and the container installs the shell during its build. Build the web app before serving the API: a missing capture bundle produces an explicit image-build error, never invented artwork. Preview disk space lives beside the API data by default.

## Public catalog

`GET /v1/catalog` accepts `q` (maximum 200 characters), `category` (`instrument` or `effect`) and `offset`. Every whitespace-separated term must match the published title/description, current creator handle/display name, public tags or category. Search is parameterized literal substring matching over all public releases before 24-item pagination; `%` and `_` have no wildcard meaning. This first search uses SQLite's lowercase behavior and has no fuzzy matching or locale-aware stemming.

Category follows the published entrypoint: an audio input makes a Toy an effect, otherwise an instrument. This is an audio-discovery grouping, not a compatibility profile. Search and filter state live in the Explore URL; requests are debounced, stale responses discarded, and errors, empty results and reset actions are visible.

Creators enter up to twelve public tags of at most 32 characters during publication. `PUT /projects/:id/catalog` updates tags with owner authentication, idempotency and the expected project version. Tag changes advance mutable project/catalog metadata and notify library subscribers without modifying source, release bytes or selected library releases; tag-only edits preserve the current unsaved editor document. Public tags are distinct from private personal library tags. Omitting tags on publication preserves existing metadata. `005-discovery.sql` adds the catalog columns and backfills category; no portable Toy schema or release envelope changes.

## HTML, metadata and indexing

Every public landing page has a descriptive title, summary, canonical URL, Open Graph/Twitter large-image metadata, meaningful alt text and `max-image-preview:large`. URLs use the configured `TUNETOYS_PUBLIC_ORIGIN`, never the request Host. HTML is escaped and structured JSON-LD receives a CSP hash. Home describes the WebSite, organization/confirmed social profiles and browser application; collections, creator profiles, app/template pages, breadcrumbs and guides carry matching structured data. Toy pages are `SoftwareApplication` with their product's paid offers or a free `Offer`, and an `aggregateRating` only from real reviews; sound pages are `AudioObject` with a licence deed URL, millisecond duration and preview `contentUrl`. `og:type` follows the page: `profile` for creators, `music.song` for audio, `article` for guides. Collections are a `CollectionPage` listing their Toys and, when they hold public audio, also a `MusicPlaylist` of `MusicRecording` tracks; guides add `HowTo` steps (`#step-<n>` anchors) beside their `TechArticle`. Share cards are 1200×630 brand cards from `shareImage.ts`: creators get a monogram card with their counts (`/social/creator/:handle.png`), sound landing pages a waveform seeded by the facet (`/social/sounds/:facet/:value.png`); the page URL carries `?v=` from what the card prints, so share caches refresh when it changes. No ratings, prices or eligibility claims are fabricated. Each category page ends its first unfiltered page with a short explanatory section, and template and guide pages link the nodes they use to docs.tune.toys, whose node pages link back to the templates.

Home and Explore self-canonicalize. Each catalog page has its own `?page=N` URL, canonical and Previous/Next links. Unlisted and private pages/images remain excluded; inaccessible resources return real 404s. Trailing slashes redirect to canonical paths. The public sitemap lists landing pages, official templates/guides, creators who publish Toys or sounds, current public Toy aliases, public sounds and collections, and the sound landing pages that pass the indexing threshold, with image references; Toy lastmod comes from its published release time (`sitemap-pages.ts` holds the non-Toy queries). Past 45 000 URLs `/sitemap.xml` becomes a sitemap index of `/sitemaps/<n>.xml` parts. `/robots.txt` advertises the sitemap. One `robotsDirective` sets both the robots meta tag and `X-Robots-Tag`, so they never disagree. Indexed pages are sent `public,max-age=60,stale-while-revalidate=60` (never with a `Set-Cookie`), which bounds how long an unpublished page can outlive its visibility change; all others stay `private,no-store`. GET and HEAD return consistent metadata; public image indexing does not weaken release visibility checks.

Optional `TUNETOYS_GOOGLE_SITE_VERIFICATION` and `TUNETOYS_BING_SITE_VERIFICATION` add property-verification tokens to the homepage; they do not create, connect or submit an external account. Operators verify the domain, submit the sitemap and inspect indexing and field Core Web Vitals in the appropriate account. **Neither search indexing nor a social crawler's rendering is guaranteed by local tests.** Vite's static preview does not generate this dynamic HTML; use `npm run build` and the API static host for public-route, metadata and image qualification.

## Listening, saving and returning

Explore cards audition verified pinned releases through the existing BrowserHost with the admitted engine and verified assets. Effects use their Toy preview audio or the original demo loop; instruments use their own generated audio.

| Audition rule                                                                                                                                                                         |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Starts only on a user action, at up to 20% monitor level (respecting a lower or muted editor setting), and stops after twelve seconds                                                 |
| One catalog audition at a time; switching Toys, cancelling, changing search/type, navigating away, opening editor dialogs, hiding the page or starting workspace playback disposes it |
| Previewing stops existing workspace audio without changing its source or saving/publishing it                                                                                         |
| Errors stay visible with retry; cancelled and failed starts never enter history                                                                                                       |

Library → Recently played remembers up to thirty successfully started releases from Explore auditions and published-page Play. It stores only project identity, exact release digest and playback time on this browser, in separate guest/account namespaces; it does not synchronize history or alter a saved/pinned library release. Storage denial falls back to in-memory history. Current authorization is rechecked before showing titles or replay links. Items can be removed and history cleared. Replay uses `/r/:digest`, so later publication cannot silently change the sound previously heard.

Explore, published playback and Recently played have a one-click Save/Saved toggle; guests are prompted to sign in. Initial bookmark flags are fetched in bounded batches by explicit project identities, independently of the 500-item full-library view. `PUT /library/:projectId/bookmark` changes only the bookmark field inside the existing transactional mutation boundary, retaining private tags, ratings and selected releases. It is a set-to-value command with a retained idempotency key, so retries do not toggle again; a no-change request creates no annotation version/event, and unsaving a missing item creates no row. Actual changes advance durable library versions and sync events, so stale full-annotation writes reject. **Saving does not pin, download or activate a release.**

Published playback gives the shared Frame a compact header and action area. Play, presets and monitor volume stay visible; input selection and explicitly requested MIDI access live in an Audio popover with nested Escape and focus return. Input changes stay disabled during playback. Navigation and secondary Library/Remix actions use menus; the description is expandable. Package verification, pinned engine playback, rights and bookmark semantics are unchanged.

## Product pages and storefronts

Rendered by `StorefrontPage.tsx` and `ProductPage.tsx` over the shared `services/api/src/pages/marketplace.ts` models (`storefrontPages.ts` re-exports the HTTP routes), with domain rules in `listing-model.ts` and the shared [`@tunetoys/markdown`](https://github.com/joshstovall/TuneToys/blob/main/packages/markdown/README.md) renderer.

- **Content source.** A product page renders the listing's published revision (`overrides[field] ?? generated[field]`); without one it renders the generated baseline (`generateListingBaseline`: title, first-sentence tagline, description, category, tags, entry-Frame controls, requirements) from the stable version, else the newest published version, else the latest release. Publisher members may open `?preview=<revisionId>` for drafts and older revisions (`private,no-store`, noindex, preview banner). `?lang=<locale>` applies a non-stale translation per field and the page lists `hreflang` alternates.
- **Visibility.** Listing visibility is separate from release visibility and product state. `private` listings, `draft` products and suspended publishers return 404 to non-members; `unlisted` sends `noindex,follow`; delisted or removed products render a `410` tombstone. Lists (storefront grids, "More by", sitemap) include only published products whose listing is public and whose wrapped Toy is public. A private release never contributes its preview image, Play link, social card or library button.
- **Gallery.** React renders the trailer and carousels; fallback retains their media links and alt text. Carousels are scroll-snap lists with a labelled region and a focusable slide list (arrow keys scroll natively); `PublicGallery.tsx` adds previous/next buttons, a position announcement and Home/End. Uploaded media renders only when the media item is public. YouTube and Vimeo items are poster `<button>`s with a fallback link; the iframe (`youtube-nocookie.com` or `player.vimeo.com?dnt=1`, `sandbox="allow-scripts allow-same-origin allow-presentation"`, `allow="autoplay; picture-in-picture"`) is created only after activation, so no third-party request happens before. Only pages that contain such a poster add `frame-src https://www.youtube-nocookie.com https://player.vimeo.com` to their CSP. GIFs under `prefers-reduced-motion` stay hidden until Play; smooth scrolling is disabled.
- **Sections.** Overview (Markdown body or description, controls with notes, requirements and download platforms from the stable version's included targets, tags, links, publisher card, bundle "Included", "More by"), Versions (published and withdrawn versions with rendered changelogs and alias badges), Reviews and Discussion (direct React `PostFeed` components), Docs (document links; FAQ inline). `/@h/:slug/versions|reviews|discussion|docs` redirect (308) to the anchored section.
- **Metadata.** Canonical `/@handle/:slug`, OG/Twitter image from an uploaded `og` asset, else the generated OG card, else the release social card, JSON-LD `SoftwareApplication` (plus `Product` with `offers` only when an active paid offer exists, `aggregateRating` only with ratings, `operatingSystem` from included targets) and a breadcrumb. Storefronts emit `ProfilePage`.
- **Redirects.** Retired handles (`publisher_handle_history`) and slugs (`product_slug_history`) redirect with 308. `/creators/:handle` redirects to `/@handle` once the publisher has published a storefront; until then it stays the community profile.
- **Caching.** Public and unlisted pages send `public, max-age=60, stale-while-revalidate=600` with a weak ETag over the rendered HTML (`304` on match); previews, private pages and tombstones send `private,no-store`.
- **Storefront theme.** Validated tokens become scoped CSS custom properties on the storefront page container, using the same preview renderer as Settings → Publisher. The shared navigation and focus rings stay outside the theme scope.
- **Cards and sitemap.** The web Explore/Library card image is `/previews/listing/:projectId.webp`: the uploaded (else generated) listing thumbnail when the listing is public, otherwise a `307` to the release preview. The sitemap adds every public product page and its storefront, with image and video entries from [generated listing assets](#generated-listing-assets).

## Generated listing assets

Implemented by WP-04: `services/api/src/listing-assets.ts` (triggers, routes, page helpers) and [`services/worker`](https://github.com/joshstovall/TuneToys/blob/main/services/worker/README.md) (generation). Publishing a product version that the listing shows (the stable alias, else the newest published version), moving the stable alias, or publishing a listing revision only **enqueues** `asset_jobs`; nothing renders in a request. Captures reuse `PreviewImages` and the `PreviewCapture` bundle above (no second Frame renderer); cards reuse the `shareImage.ts` glyph-path typography; the audio preview runs the shared WASM engine offline. Kinds whose tool is missing (Chromium, the built capture bundle, `TUNETOYS_FFMPEG_PATH`) are recorded as skipped with a reason and never replaced by a placeholder.

| Route (GET and HEAD)                       | Content                                     | Cache-Control                                                                                                                                                                        |
| ------------------------------------------ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `/assets/l/:listingId/:kind/:variant.:ext` | Image, motion, audio, PDF, caption bytes    | `public, max-age=31536000, immutable` with `?v=<digest>`; otherwise `public, max-age=300, stale-while-revalidate=3600` (`og`/`social`: `max-age=3600, stale-while-revalidate=86400`) |
| `/@handle/:slug/llms.txt`                  | `text/plain` agent summary                  | `public, max-age=300, stale-while-revalidate=3600`                                                                                                                                   |
| `/@handle/:slug/README.md`                 | `text/markdown` product README              | Same                                                                                                                                                                                 |
| `/@handle/:slug/manifest.json`             | Version, artifact inventory and asset index | `public, max-age=60, stale-while-revalidate=600`                                                                                                                                     |
| `/@handle/:slug/controls.schema.json`      | JSON Schema 2020-12 of the public controls  | `public, max-age=3600`                                                                                                                                                               |
| `/@handle/:slug/structured.jsonld`         | schema.org graph                            | `public, max-age=300`                                                                                                                                                                |

- **Visibility.** Rechecked on every request: a `private` listing, a draft/delisted/removed product, a suspended publisher or a private wrapped Toy serves `404`; unlisted listings and non-public Toys serve by URL with `X-Robots-Tag: noindex`. Members preview any ready asset through `GET /api/v1/products/:productId/listing/assets/:assetId/bytes`.
- **Selection.** An uploaded `thumbnail` or `og` override wins (the binary route answers `307` to the public media bytes); otherwise the ready generated row; otherwise the page falls back to the release preview and social card. A failed or skipped regeneration never replaces a ready asset.
- **Headers.** Strong `ETag` (the SHA-256 digest) with `304` on `If-None-Match`; `Content-Length` on HEAD; text and JSON allow any origin; SVG carries `default-src 'none'; sandbox`.
- **Product page.** Uses the generated OG card and `thumb-640`, adds `<link rel="alternate">` for `llms.txt`, README, manifest and control schema, `og:video` (MP4) and `og:audio` when ready, and `VideoObject`/`AudioObject` JSON-LD on indexable pages.
- **Sitemap.** Each public product `<url>` lists `<image:image>` entries (thumb-1280, OG, desktop/phone screenshots, captioned with alt text) and a `<video:video>` entry when the MP4 and poster are ready.
- **Machine drafts.** Alt text and captions are template-derived and stored with `metadata.unverified = true`; the editor labels them "Draft, unverified".
- Not claimed: social crawler rendering, search indexing of video entries, or external cache refresh.

## Evidence

| Check                                                 | Covers                                                                                                                                                                                                                                                                                                       |
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| API fixtures (`platform`, `seo`, `preview-images`)    | Cross-page search, public vs personal tags, category filtering, literal queries, owner/version/bounds checks, unchanged release bytes, HTML escaping/CSP, canonicals, PNG dimensions, sitemap exclusion, private artwork denial, historical-release metadata, GET/HEAD, visibility changes during image work |
| `services/api/test/listings.test.mjs`                 | Generated page with no creator work, overrides and reset, member preview, publish, click-to-load posters with page-scoped `frame-src`, tab redirects, documents, slug 308, unlisted noindex and sitemap exclusion, private 404, storefront contrast/sections/preview/publish/restore, creator redirect       |
| `apps/web/test/listening.test.mjs`                    | Bounded/scoped persistence, preview cancellation, late-load suppression, failure/retry and timeout                                                                                                                                                                                                           |
| `npm run test:seo-ui --workspace @tunetoys/web`       | Real shared-renderer WebP/PNG exports, byte caches, font/icon readiness, image access after revocation, public pages without editor/WASM startup, layout shift, search, template playback with live controls and editor handoff                                                                              |
| `npm run test:listening-ui --workspace @tunetoys/web` | Against a disposable real API/database after a browser build: real shared-engine signal, single-preview switching, cancellation, retry, timeout, exact replay/history scope, revoked access, saving without pinning, desktop/mobile layout                                                                   |
| Browser checks                                        | New/recovered/snapshot entry, search/reset/filtering, real Remix navigation, desktop/mobile layout                                                                                                                                                                                                           |

Accounts, databases, caches and browser profiles in these suites are disposable; `UI_SCREENSHOT_DIR` retains visual evidence. References: [Google JavaScript SEO](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics), [site names](https://developers.google.com/search/docs/appearance/site-names), [Open Graph](https://ogp.me/), [Sharp](https://sharp.pixelplumbing.com/) and [OpenType.js](https://github.com/opentypejs/opentype.js).

## Implementation

- [services/api](https://github.com/joshstovall/TuneToys/blob/main/services/api/README.md) — `publicSite.ts`, `discovery.ts`, `previewImages.ts`, `shareImage.ts`, `pages/`, `storefrontPages.ts`, `listing-model.ts`, `listing-assets.ts`
- [services/worker](https://github.com/joshstovall/TuneToys/blob/main/services/worker/README.md) — generated listing asset jobs
- [packages/markdown](https://github.com/joshstovall/TuneToys/blob/main/packages/markdown/README.md) — shared Markdown parser and allowlisting renderer
- [packages/discovery](https://github.com/joshstovall/TuneToys/blob/main/packages/discovery/README.md) — official templates and guides
- [apps/web](https://github.com/joshstovall/TuneToys/blob/main/apps/web/README.md) — `main.tsx`, `Catalog.tsx`, `ToyPreview.tsx`, `PreviewCapture.tsx`, `RecentlyPlayed.tsx`, `PublishedToy.tsx`
- [packages/frame-runtime](https://github.com/joshstovall/TuneToys/blob/main/packages/frame-runtime/README.md) — the one renderer behind cards, captures and playback

## Related

- [Publishing, libraries and Player](https://docs.tune.toys/contributors/distribution/)
- [Collaborative community](https://docs.tune.toys/contributors/community/)
- [App shell, Settings and Library](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/settings-library.md)
- [Executable HTTP contract](https://github.com/joshstovall/TuneToys/blob/main/docs/specs/http-api.md#public-discovery-metadata)
- [Embedding, oEmbed and deep links](https://github.com/joshstovall/TuneToys/blob/main/docs/architecture/embedding.md)
- [Trust and service boundaries](https://docs.tune.toys/contributors/security/)
