Skip to content

feat: trove — semantic search for 3D prints by aesthetic#54

Merged
nirokato merged 1 commit into
mainfrom
claude/zen-clarke-07ydp0
Jun 9, 2026
Merged

feat: trove — semantic search for 3D prints by aesthetic#54
nirokato merged 1 commit into
mainfrom
claude/zen-clarke-07ydp0

Conversation

@nirokato

@nirokato nirokato commented Jun 9, 2026

Copy link
Copy Markdown
Owner

What this adds

trove (trove.apps.andymolenda.com) — a static, client-side app where you type any decor style or aesthetic ("industrial eclectic Amsterdam canal house", "warm japandi", "art deco glam") and it semantically ranks a prebuilt Printables index: thumbnail, designer, license, popularity, and a deep link to the original listing. The aesthetic is never hardcoded.

How it works

        (offline / CI)                          (in the browser)
Printables GraphQL ─► tools/trove-ingest ─► data/index.json ─► trove app
                       (metadata only)        (normalized)      ├─ transformers.js (CDN, MiniLM)
                                                                ├─ cosine + lexical rank
                                                                └─ IndexedDB vector cache
  • The browser never calls Printables → no CORS problem, no per-visitor API traffic, no scraping from users. It loads the normalized data/index.json, lazily pulls a small embedding model from a CDN, embeds the corpus once (cached in IndexedDB) + your query, and ranks. If the model can't load it falls back to keyword matching.
  • The ingest is the only Printables-facing piece — a zero-dependency Node script that fetches metadata only and links out (never rehosts model files), rate-limited and polite.
  • No backend, no API keys, no build step. Stays within the repo's static + CDN-only conventions.

Included

  • projects/trove/index.html + app.mjs (UI) + search.mjs (engine + pure ranking math) + hand-built sample dataset so it works immediately + tests/run.mjs
  • tools/trove-ingest/ingest.mjs — zero-dep metadata ingest (optional --embed precompute path)
  • .github/workflows/trove-refresh.yml — weekly + manual rebuild of the index, committed to main to re-trigger deploy
  • Wiring: tofu/variables.tf projects set, homepage link, CLAUDE.md (structure, projects table, tech-stack exception, deployment)

Verification

  • ✅ Ranking math: 18/18 unit tests (node projects/trove/tests/run.mjs)
  • ✅ Full app in a headless browser — index load, sample banner, example chips, result-card rendering, filters, and the keyword fallback (top hit for "industrial pipe shelf" → Pipe Bracket Wall Shelf)

Things to watch

  1. GraphQL schema is best-effort. The build sandbox blocks api.printables.com, so I couldn't introspect Printables' unofficial/undocumented API. The query + field mapping are isolated in one spot in ingest.mjs with defensive parsing. The first trove-refresh run (CI has open network) will either populate real data or fail loudly on a schema mismatch that's a one-file fix. Until then the app serves the labeled sample dataset.
  2. The refresh workflow pushes directly to main. If main is branch-protected against direct pushes, switch it to a PR flow (noted in a comment in the workflow).

Populating real data (after merge)

workflow_dispatch only appears once the workflow is on the default branch, so after this merges: Actions → "Refresh trove index" → Run workflow. That runs the ingest on a CI runner, commits the real index to main, and re-triggers trove's deploy.

Squash-merge friendly.

https://claude.ai/code/session_011Yfgw8tiyFJsxCzFtnkavu


Generated by Claude Code

Add `trove`: a static, client-side app that ranks a prebuilt Printables
metadata index against any free-text decor style ("industrial eclectic",
"japandi", "art deco") using in-browser MiniLM embeddings (transformers.js
via CDN, cached in IndexedDB), with a keyword fallback when the model can't
load. The aesthetic is never hardcoded.

- projects/trove: index.html + app.mjs (UI) + search.mjs (engine + pure,
  unit-tested ranking math) + hand-built sample dataset + node tests
- tools/trove-ingest: zero-dependency Node script that pulls Printables
  metadata into the normalized index (optional --embed precompute path)
- .github/workflows/trove-refresh.yml: weekly + manual rebuild of the index,
  committed to main to re-trigger deploy
- wiring: tofu/variables.tf projects set, homepage link, CLAUDE.md
  (structure, projects table, tech-stack exception, deployment)

Browser flow never calls Printables (CORS-free); ingest fetches metadata only
and links out, never rehosting model files. Ranking math unit-tested; DOM +
keyword fallback verified via headless browser.

https://claude.ai/code/session_011Yfgw8tiyFJsxCzFtnkavu
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

OpenTofu Plan

Plan output
data.cloudflare_zone.zone: Reading...
cloudflare_pages_project.preview: Refreshing state... [id=andy-apps-preview]
cloudflare_pages_project.project["weft"]: Refreshing state... [id=andy-apps-weft]
cloudflare_pages_project.project["homepage"]: Refreshing state... [id=andy-apps-homepage]
cloudflare_pages_project.project["clock"]: Refreshing state... [id=andy-apps-clock]
cloudflare_pages_project.project["peer-drop"]: Refreshing state... [id=andy-apps-peer-drop]
data.cloudflare_zone.zone: Read complete after 0s [id=a7f7a62f2c2b914baa9b82b9e41a29c5]
cloudflare_pages_domain.domain["weft"]: Refreshing state... [id=df0069d6-a9c4-411d-b52b-88227318b108]
cloudflare_pages_domain.domain["peer-drop"]: Refreshing state... [id=69db182b-cd41-43ba-8c0c-bcfc92d974e9]
cloudflare_record.cname["homepage"]: Refreshing state... [id=c2155ce7863f7e7689dc3f05367746e2]
cloudflare_record.cname["weft"]: Refreshing state... [id=e2411dbb4de2318f0f00d1d47b584e4d]
cloudflare_pages_domain.domain["homepage"]: Refreshing state... [id=2c55d7af-683f-4463-aa98-5bc7b1e707cd]
cloudflare_pages_domain.domain["clock"]: Refreshing state... [id=74b72e0f-818d-420e-859a-f0437bd35828]
cloudflare_record.cname["peer-drop"]: Refreshing state... [id=569777844ae8c626fc232d14b0837470]
cloudflare_record.cname["clock"]: Refreshing state... [id=d0e16b835decb7bb06f5feb0736801e3]

OpenTofu used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

OpenTofu will perform the following actions:

  # cloudflare_pages_domain.domain["trove"] will be created
  + resource "cloudflare_pages_domain" "domain" {
      + account_id   = "6050946b66172190524579962c958e5a"
      + domain       = "trove.apps.andymolenda.com"
      + id           = (known after apply)
      + project_name = "andy-apps-trove"
      + status       = (known after apply)
    }

  # cloudflare_pages_project.project["trove"] will be created
  + resource "cloudflare_pages_project" "project" {
      + account_id        = "6050946b66172190524579962c958e5a"
      + created_on        = (known after apply)
      + domains           = (known after apply)
      + id                = (known after apply)
      + name              = "andy-apps-trove"
      + production_branch = "main"
      + subdomain         = (known after apply)
    }

  # cloudflare_record.cname["trove"] will be created
  + resource "cloudflare_record" "cname" {
      + allow_overwrite = true
      + content         = (known after apply)
      + created_on      = (known after apply)
      + hostname        = (known after apply)
      + id              = (known after apply)
      + metadata        = (known after apply)
      + modified_on     = (known after apply)
      + name            = "trove.apps"
      + proxiable       = (known after apply)
      + proxied         = true
      + ttl             = (known after apply)
      + type            = "CNAME"
      + value           = (known after apply)
      + zone_id         = "a7f7a62f2c2b914baa9b82b9e41a29c5"
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  ~ project_urls = {
      + trove     = "https://trove.apps.andymolenda.com"
        # (4 unchanged attributes hidden)
    }

─────────────────────────────────────────────────────────────────────────────

Saved the plan to: tfplan

To perform exactly these actions, run the following command to apply:
    tofu apply "tfplan"

@nirokato nirokato merged commit 486c8a0 into main Jun 9, 2026
3 checks passed
@nirokato nirokato deleted the claude/zen-clarke-07ydp0 branch June 9, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants