@instruments/materialgraph 0.32.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/association-types.d.ts +11 -11
  2. package/associations-client.d.ts +7 -7
  3. package/associations-client.js +5 -5
  4. package/brand-types.d.ts +37 -41
  5. package/brand-types.js +3 -5
  6. package/brands-client.d.ts +22 -9
  7. package/brands-client.js +20 -6
  8. package/by-spec-types.d.ts +2 -3
  9. package/catalog-discovery-types.d.ts +3 -3
  10. package/catalogue-client.d.ts +2 -50
  11. package/catalogue-client.js +2 -10
  12. package/catalogue-types.d.ts +1 -1
  13. package/categories-client.d.ts +3 -5
  14. package/categories-client.js +1 -1
  15. package/category-types.d.ts +17 -30
  16. package/citation-types.d.ts +2 -2
  17. package/client-vocabulary.d.ts +2 -2
  18. package/client-vocabulary.js +4 -1
  19. package/client.d.ts +75 -102
  20. package/client.js +83 -100
  21. package/color-profiles-types.d.ts +70 -0
  22. package/colors-client.d.ts +76 -0
  23. package/colors-client.js +52 -0
  24. package/find-companions-types.d.ts +8 -8
  25. package/{find-variants-by-colour-types.d.ts → find-variants-by-color-types.d.ts} +70 -64
  26. package/find-variants-by-vector-types.d.ts +4 -1
  27. package/get-variant-types.d.ts +14 -2
  28. package/http-transport.d.ts +1 -1
  29. package/http-transport.js +1 -2
  30. package/identifiers.d.ts +2 -2
  31. package/identifiers.js +1 -1
  32. package/index.d.ts +5 -13
  33. package/index.js +1 -3
  34. package/list-variant-facets-types.d.ts +4 -4
  35. package/list-variants-types.d.ts +108 -19
  36. package/material-asset-types.d.ts +3 -3
  37. package/material-block-types.d.ts +9 -9
  38. package/material-types.d.ts +10 -8
  39. package/operations.d.ts +2 -2
  40. package/operations.js +14 -24
  41. package/package.json +2 -2
  42. package/{resolve-colour-types.d.ts → resolve-color-types.d.ts} +18 -18
  43. package/response-schemas.generated.d.ts +2 -2
  44. package/response-schemas.generated.js +40 -68
  45. package/response-schemas.js +1 -1
  46. package/samplize-catalogue-types.d.ts +4 -4
  47. package/samplize-colorvec.js +6 -6
  48. package/{corpus-scope-types.d.ts → search-catalogue-subset-types.d.ts} +3 -3
  49. package/search-variants-types.d.ts +91 -0
  50. package/types.d.ts +31 -283
  51. package/webmcp.generated.js +4 -9
  52. package/attribution-types.d.ts +0 -51
  53. package/attribution-types.js +0 -13
  54. package/by-colour-types.d.ts +0 -320
  55. package/colour-region-types.d.ts +0 -18
  56. package/colours-client.d.ts +0 -70
  57. package/colours-client.js +0 -38
  58. package/material-query-plan-types.d.ts +0 -70
  59. package/palette-search-types.d.ts +0 -171
  60. package/palette-search-types.js +0 -1
  61. package/query-understanding-types.d.ts +0 -254
  62. package/query-understanding-types.js +0 -1
  63. package/resolve-colour-types.js +0 -1
  64. package/search-card-types.d.ts +0 -86
  65. package/search-card-types.js +0 -1
  66. package/search-index-plan-types.d.ts +0 -74
  67. package/search-index-plan-types.js +0 -1
  68. package/search-refinement-types.d.ts +0 -103
  69. package/search-refinement-types.js +0 -2
  70. package/search-request-types.d.ts +0 -170
  71. package/search-request-types.js +0 -1
  72. package/search-response-receipt-types.d.ts +0 -24
  73. package/search-response-receipt-types.js +0 -1
  74. package/search-response-types.d.ts +0 -11
  75. package/search-response-types.js +0 -1
  76. package/search-result-validation.d.ts +0 -9
  77. package/search-result-validation.js +0 -83
  78. package/spec-types.d.ts +0 -109
  79. package/spec-types.js +0 -54
  80. package/specifications.d.ts +0 -34
  81. package/specifications.js +0 -56
  82. package/validation.d.ts +0 -43
  83. package/validation.js +0 -117
  84. /package/{by-colour-types.js → color-profiles-types.js} +0 -0
  85. /package/{colour-region-types.js → find-variants-by-color-types.js} +0 -0
  86. /package/{corpus-scope-types.js → resolve-color-types.js} +0 -0
  87. /package/{find-variants-by-colour-types.js → search-catalogue-subset-types.js} +0 -0
  88. /package/{material-query-plan-types.js → search-variants-types.js} +0 -0
package/client.js CHANGED
@@ -3,8 +3,8 @@ import { listBrands } from "./brands-client.js";
3
3
  import { CatalogueClient } from "./catalogue-client.js";
4
4
  import { CategoriesClient } from "./categories-client.js";
5
5
  import { DEFAULT_BASE_URL } from "./client-config.js";
6
- import { listAttributes as listAttributesCall, listEnumValues as listEnumValuesCall, resolveColour as resolveColourCall, } from "./client-vocabulary.js";
7
- import { ColoursClient } from "./colours-client.js";
6
+ import { listAttributes as listAttributesCall, listEnumValues as listEnumValuesCall, resolveColor as resolveColorCall, } from "./client-vocabulary.js";
7
+ import { ColorsClient } from "./colors-client.js";
8
8
  import { MaterialGraphError } from "./errors.js";
9
9
  import { HttpTransport } from "./http-transport.js";
10
10
  import { listVariantAssets as listVariantAssetsCall, materialBlocks as materialBlocksCall, materialDocuments as materialDocumentsCall, } from "./material-resources-client.js";
@@ -23,25 +23,27 @@ export * from "./client-config.js";
23
23
  *
24
24
  * | The question | The lens |
25
25
  * | --- | --- |
26
- * | "find me something like this / matching these words" | {@link MaterialGraphClient.search} |
26
+ * | "find me something matching these words" | {@link MaterialGraphClient.searchVariants} |
27
+ * | "find me something that looks or feels like this" | {@link MaterialGraphClient.findVariantsByVector} |
27
28
  * | "tell me about THIS variant" | {@link MaterialGraphClient.getVariant} |
28
29
  * | "which materials, from anyone, meet this hard spec?" | {@link MaterialGraphClient.listVariants} |
29
- * | "what's near this colour / this palette?" | {@link MaterialGraphClient.byColour}, `colours.palette()` |
30
- * | "what goes with THIS material?" | {@link MaterialGraphClient.pairings}, {@link MaterialGraphClient.listVariants} (same product, other colourways) |
31
- * | "what does the manufacturer say is AVAILABLE on this?" | {@link MaterialGraphClient.associatedFinishes}, {@link MaterialGraphClient.associatedProducts} |
30
+ * | "what's near a color, or a palette of colors?" | {@link MaterialGraphClient.findVariantsByColor} (`mode: "palette"`) |
31
+ * | "what colors does this category come in?" | `colors.profiles()` |
32
+ * | "what goes with THIS material?" | {@link MaterialGraphClient.pairings}, {@link MaterialGraphClient.listVariants} (same product, other colorways) |
33
+ * | "what does the brand say is AVAILABLE on this?" | {@link MaterialGraphClient.associatedFinishes}, {@link MaterialGraphClient.associatedProducts} |
32
34
  * | "what can I even ask for?" | {@link MaterialGraphClient.listVariantFacets}, `categories.list()` |
33
35
  * | "what is this word called here?" | {@link MaterialGraphClient.listAttributes} |
34
36
  * | "do the whole job for me, async" | `workflows.start()` + `mg:compose` |
35
37
  *
36
38
  * Three structural rules that fall out of that table:
37
- * - **Colour is a separate lens.** {@link MaterialGraphClient.search} matches
38
- * WORDS. A colour word in `text` finds materials whose text mentions it, not
39
- * materials that look it. Route visual intent to the colour lenses.
39
+ * - **Color is a separate lens.** {@link MaterialGraphClient.searchVariants}
40
+ * matches WORDS. A color word in `query` finds materials whose text mentions
41
+ * it, not materials that look it. Route visual intent to the color lenses.
40
42
  * - **Relationships hang off a material, not off a query.** You cannot ask "what
41
43
  * pairs with beige oak"; you resolve a material first, then ask what pairs
42
44
  * with THAT `variantId`.
43
45
  * - **Declared is not inferred.** `associatedFinishes()` /
44
- * `associatedProducts()` return compatibility the MANUFACTURER stated;
46
+ * `associatedProducts()` return compatibility the BRAND stated;
45
47
  * `pairings()` returns companionship the graph INFERRED. The response shapes
46
48
  * share no field, on purpose — render them as separate sections and never
47
49
  * concatenate them.
@@ -53,10 +55,10 @@ export class MaterialGraphClient extends CatalogueClient {
53
55
  /**
54
56
  * THE QUESTION: "what kinds of thing exist, and what are their keys?"
55
57
  * `categories.list()` pages the tree (roots, one node's children, or a
56
- * name/meaning search), and `categories.get(id)` reads one node in full:
58
+ * name search), and `categories.get(id)` reads one node in full:
57
59
  * its place, the names that find it, and the canonical taxonomy concepts it
58
60
  * relates to. The source of every category id the other lenses accept
59
- * (`search({ category })`, `listVariants({ filters })`, `colours.profiles()`).
61
+ * (`listVariants({ filters })`, `findVariantsByColor({ categoryIds })`).
60
62
  *
61
63
  * A category id is a TREE KEY, never a canonical `product_category` value:
62
64
  * the two look alike and are not interchangeable. Discover ids here, never
@@ -65,17 +67,19 @@ export class MaterialGraphClient extends CatalogueClient {
65
67
  */
66
68
  categories;
67
69
  /**
68
- * THE QUESTION: "what colours exist here?" and "what matches this whole SET
69
- * of colours?" (`palette`).
70
+ * THE QUESTION: "what colors does this category come in?" (`profiles`).
71
+ * `profiles` is an aggregate over a category, not a list of materials, and
72
+ * carries a `basis` naming the snapshot it read.
70
73
  *
71
- * Instead use {@link MaterialGraphClient.byColour} for ONE colour and
72
- * {@link MaterialGraphClient.listVariants} to page through a colour's family.
73
- * Colour is usually derived from product imagery, which is visual likeness,
74
- * not a colour specification. A brand's declared colour, such as a paint's
74
+ * Instead use {@link MaterialGraphClient.findVariantsByColor} (`mode:
75
+ * "palette"`) for the materials near one or several colors and
76
+ * {@link MaterialGraphClient.listVariants} to page through a color's family.
77
+ * Color is usually derived from product imagery, which is visual likeness,
78
+ * not a color specification. A brand's declared color, such as a paint's
75
79
  * published hex, wins where MaterialGraph holds one, and each value's
76
- * source basis says which.
80
+ * value origin says which.
77
81
  */
78
- colours;
82
+ colors;
79
83
  /**
80
84
  * THE QUESTION: "do a whole job for me" — the ASYNC lens. `start()` begins a
81
85
  * Run of one registered workflow, `run()` polls it, `streamRunEvents()` and
@@ -100,9 +104,8 @@ export class MaterialGraphClient extends CatalogueClient {
100
104
  this.categories = new CategoriesClient({
101
105
  get: async (operationId, query, signal, pathParams) => await this.#get(operationId, query, signal, pathParams),
102
106
  });
103
- this.colours = new ColoursClient({
107
+ this.colors = new ColorsClient({
104
108
  get: async (operationId, query, signal) => await this.#get(operationId, query, signal),
105
- post: async (operationId, body, signal) => await this.#post(operationId, body, signal),
106
109
  });
107
110
  this.workflows = new WorkflowsClient({ transport });
108
111
  }
@@ -123,46 +126,6 @@ export class MaterialGraphClient extends CatalogueClient {
123
126
  signal,
124
127
  });
125
128
  }
126
- async search(input, signal) {
127
- return await this.#post("search_materials_post", input, signal);
128
- }
129
- /** Whole materials ask → inspectable meanings and complete safe search requests. */
130
- async planQuery(query, signal) {
131
- return await this.#post("plan_material_query", { query }, signal);
132
- }
133
- async searchGet(params, signal) {
134
- return await this.#get("search_materials_get", { ...params }, signal);
135
- }
136
- /**
137
- * THE QUESTION: "what materials are near THIS colour?" — one colour (or a small
138
- * palette), ranked by OKLab closeness to colour extracted from product imagery.
139
- *
140
- * Instead use `colours.palette()` for N colours at stated proportions (a
141
- * different ranking, not a wrapper),
142
- * {@link MaterialGraphClient.resolveColour} first if you hold a NAME,
143
- * {@link MaterialGraphClient.listVariants} to page through a category or a brand, and
144
- * {@link MaterialGraphClient.search} when colour is one signal among many.
145
- * Report matches as visual likeness, never a stated colour specification.
146
- * `family` gates to ONE colorscope family (a gated page can be short); `brand` gates to named brands — one brand's nearest colour to another's.
147
- */
148
- async byColour(params, signal) {
149
- return await this.#get("search_by_colour", {
150
- a: params.a,
151
- b: params.b,
152
- brand: params.brand,
153
- category: params.category,
154
- colour: params.colour,
155
- corpusScope: params.corpusScope,
156
- family: params.family,
157
- hex: params.hex,
158
- l: params.l,
159
- limit: params.limit,
160
- maxColourDistance: params.maxColourDistance,
161
- order: params.order,
162
- q: params.q,
163
- variantId: params.variantId,
164
- }, signal);
165
- }
166
129
  // ── Materials ──────────────────────────────────────────────────────────────
167
130
  /**
168
131
  * THE QUESTION: "tell me about THIS variant" (GET
@@ -174,10 +137,10 @@ export class MaterialGraphClient extends CatalogueClient {
174
137
  * product, brand, category, image and page. Name `attributes` and `blocks`
175
138
  * to add their values under `specifications`; a null value is unknown, never
176
139
  * false or zero. Instead use {@link MaterialGraphClient.listVariants} for many
177
- * ids, or for the other colourways of the same product (ask `listVariants`
140
+ * ids, or for the other colorways of the same product (ask `listVariants`
178
141
  * with a `product in` filter),
179
142
  * {@link MaterialGraphClient.materialBlocks} to tell "no value" from "not
180
- * checked", {@link MaterialGraphClient.search} when you have no id yet.
143
+ * checked", {@link MaterialGraphClient.searchVariants} when you have no id yet.
181
144
  *
182
145
  * @param variantId Opaque — pass back exactly what you were given.
183
146
  */
@@ -212,11 +175,11 @@ export class MaterialGraphClient extends CatalogueClient {
212
175
  * off one resolved material (GET `/api/v1/materials/{variantId}/pairings`).
213
176
  *
214
177
  * Instead use {@link MaterialGraphClient.listVariants} for the same product in
215
- * another colour, and `search({ similarTo })` for a LIKE-THIS substitute —
178
+ * another color, and {@link MaterialGraphClient.findVariantsByVector} for a LIKE-THIS substitute —
216
179
  * similar and complementary are opposite questions.
217
180
  *
218
181
  * READ `item.source` BEFORE PHRASING: the rail mixes three tiers of evidence —
219
- * `cospec` (real orders), `considered` (board activity), `colour` (palette
182
+ * `cospec` (real orders), `considered` (board activity), `color` (palette
220
183
  * look-alike). Only `cospec` supports "sampled together". `cospecCount` tells
221
184
  * you how much of the rail is real order evidence.
222
185
  */
@@ -225,11 +188,11 @@ export class MaterialGraphClient extends CatalogueClient {
225
188
  variantId,
226
189
  });
227
190
  }
228
- /** "What finishes does the manufacturer declare for THIS product?" — {@link declaredFinishes}. */
191
+ /** "What finishes does the brand declare for THIS product?" - {@link declaredFinishes}. */
229
192
  async associatedFinishes(variantId, options, signal) {
230
193
  return await assoc.declaredFinishes(this.#transport, variantId, options, signal);
231
194
  }
232
- /** "What products does the manufacturer declare THIS finish for?" — {@link declaredProducts}. */
195
+ /** "What products does the brand declare THIS finish for?" - {@link declaredProducts}. */
233
196
  async associatedProducts(variantId, options, signal) {
234
197
  return await assoc.declaredProducts(this.#transport, variantId, options, signal);
235
198
  }
@@ -240,19 +203,21 @@ export class MaterialGraphClient extends CatalogueClient {
240
203
  * THE QUESTION: "eligible variants matching these hard filters, ranked and
241
204
  * cursor-paged" (GET `/api/v1/variants`, MG-2222) — the one read behind
242
205
  * `list_variants`, hydrating ids you already hold (`{terms: [{field:
243
- * "variant", op: "in", values: [...]}]}`, up to 100), the other colourways
206
+ * "variant", op: "in", values: [...]}]}`, up to 100), the other colorways
244
207
  * of a product (`{terms: [{field: "product", op: "in", values:
245
208
  * [productId]}, {field: "variant", op: "not_in", values: [variantId]}]}` —
246
- * `filters` is kind-grouped, `{terms, numbers, presence}`, every condition
247
- * across all three ANDed), or a category/brand listing. An explicit
209
+ * `filters` is kind-grouped, `{terms, numbers, presence, anyField}`, every
210
+ * condition across all four ANDed, 20 at most), or a category/brand listing. An explicit
248
211
  * `variant in` id that does not resolve to an eligible variant is
249
- * reported in `unresolvedVariantIds`, or in `excludedByCorpusScope` when
250
- * it names a real variant outside the materials catalogue, never silently
251
- * dropped from the page.
212
+ * reported in `unresolvedVariantIds` when it names no variant, in
213
+ * `unavailableVariantIds` when it names a catalogue variant this list
214
+ * cannot show in the requested scope (a retired one, for example), or in
215
+ * `excludedBySearchCatalogueSubset` when it names a real variant outside the
216
+ * materials catalogue, never silently dropped from the page.
252
217
  *
253
- * `filters`/`optionalFilters`/`orderBy` are sent as URL-encoded JSON;
254
- * `attributes`/`include` as comma-separated lists — the server validates,
255
- * this method does not duplicate that check.
218
+ * `filters`/`optionalFilters`/`orderBy`/`colors` are sent as URL-encoded
219
+ * JSON; `attributes`/`include`/`words` as comma-separated lists — the server
220
+ * validates, this method does not duplicate that check.
256
221
  */
257
222
  async listVariants(input, signal) {
258
223
  return await this.#get("list_variants", {
@@ -260,7 +225,8 @@ export class MaterialGraphClient extends CatalogueClient {
260
225
  ? input.attributes.join(",")
261
226
  : undefined,
262
227
  collapse: input.collapse,
263
- corpusScope: input.corpusScope,
228
+ colors: input.colors?.length ? JSON.stringify(input.colors) : undefined,
229
+ searchCatalogueSubset: input.searchCatalogueSubset,
264
230
  cursor: input.cursor,
265
231
  filters: input.filters ? JSON.stringify(input.filters) : undefined,
266
232
  include: input.include?.length ? input.include.join(",") : undefined,
@@ -272,6 +238,7 @@ export class MaterialGraphClient extends CatalogueClient {
272
238
  orderBy: input.orderBy?.length
273
239
  ? JSON.stringify(input.orderBy)
274
240
  : undefined,
241
+ words: input.words?.length ? input.words.join(",") : undefined,
275
242
  }, signal);
276
243
  }
277
244
  /**
@@ -279,23 +246,39 @@ export class MaterialGraphClient extends CatalogueClient {
279
246
  * and how many of each" (POST `/api/v1/variants/facets`, MG-2294) — the
280
247
  * options and counts a filter UI shows beside a {@link
281
248
  * MaterialGraphClient.listVariants} page. `filters` is the same tree
282
- * `listVariants()` takes; the server validates both.
249
+ * `listVariants()` takes, less its `orText` and `unstated` additions; the
250
+ * server validates both.
283
251
  */
284
252
  async listVariantFacets(input, signal) {
285
253
  return await this.#post("list_variant_facets", input, signal);
286
254
  }
255
+ /**
256
+ * THE QUESTION: "which variants match these plain words, and how did you
257
+ * read them?" (POST `/api/v1/variants/search`, MG-2223) — `query` is read
258
+ * into the filters {@link MaterialGraphClient.listVariants} takes, run
259
+ * through it, and answered with `understood` (what each phrase became, or
260
+ * why it did nothing) beside the page. `filters`, `optionalFilters` and
261
+ * `name` are exactly what ran, so the three can be sent to `listVariants()`
262
+ * to narrow or widen the same search; page with this method's own cursor,
263
+ * which `listVariants()` refuses. A phrase with several meanings is
264
+ * answered with one and listed in `choices`; send its `handle` with an
265
+ * alternative's `group` and `id` in `selections` to switch it. A query that
266
+ * applies nothing returns no items, never the whole catalogue; a preference
267
+ * alone still ranks it.
268
+ */
269
+ async searchVariants(input, signal) {
270
+ return await this.#post("search_variants", input, signal);
271
+ }
287
272
  /**
288
273
  * THE QUESTION: "which variants hold, resemble, or can be ranked by, this
289
- * colour?" (POST `/api/v1/variants/by-colour`, MG-2217/MG-2230/MG-2247).
290
- * One required `mode`: `contains` (a swatch for every required `colours[]`
291
- * condition), `palette` (whole-palette closeness to `colours[]`, an
274
+ * color?" (POST `/api/v1/variants/by-color`, MG-2217/MG-2230/MG-2247).
275
+ * One required `mode`: `contains` (a swatch for every required `colors[]`
276
+ * condition), `palette` (whole-palette closeness to `colors[]`, an
292
277
  * image's extracted palette, or `targetVariantId`'s own palette) or
293
- * `rerank` (reorder a held `variantIds` set by colour closeness). Use
294
- * {@link MaterialGraphClient.byColour} instead for ONE colour against the
295
- * search index rather than the canonical variant graph.
278
+ * `rerank` (reorder a held `variantIds` set by color closeness).
296
279
  */
297
- async findVariantsByColour(input, signal) {
298
- return await this.#post("find_variants_by_colour", input, signal);
280
+ async findVariantsByColor(input, signal) {
281
+ return await this.#post("find_variants_by_color", input, signal);
299
282
  }
300
283
  /**
301
284
  * THE QUESTION: "what did designers sample together with these?" (POST
@@ -303,7 +286,7 @@ export class MaterialGraphClient extends CatalogueClient {
303
286
  * `materials` (free-text names, one variant each, at most twelve together);
304
287
  * companions come back strongest first, each with its `lift`,
305
288
  * `seedMatchCount`, `relation` and a one-line `reason`. `projectType` reads one
306
- * sector; `include` adds `image` and `colour` fields a lean page leaves out.
289
+ * sector; `include` adds `image` and `color` fields a lean page leaves out.
307
290
  * A sample order shows sampling, never specification. Read `unresolved`
308
291
  * before presenting: a scheme of four that resolved two is a different claim.
309
292
  */
@@ -315,16 +298,17 @@ export class MaterialGraphClient extends CatalogueClient {
315
298
  * `/api/v1/variants/by-vector`, MG-2231). Eligible variants ranked by
316
299
  * semantic or visual similarity to a text description (mood words count),
317
300
  * an image reference, or both fused into one embedding. `filters` is the
318
- * same tree {@link MaterialGraphClient.listVariants} takes. `query.mode`
301
+ * same tree {@link MaterialGraphClient.listVariants} takes, less its `orText`
302
+ * and `unstated` additions. `query.mode`
319
303
  * values other than `"text"` embed through Voyage and spend the shared
320
304
  * per-key `embed` rate budget.
321
305
  */
322
306
  async findVariantsByVector(input, signal) {
323
307
  return await this.#post("find_variants_by_vector", input, signal);
324
308
  }
325
- /** Resolve a colour name to its hex, and the named paint's catalogue variant. */
326
- async resolveColour(name, signal, options) {
327
- return await resolveColourCall(this.#transport, name, signal, options);
309
+ /** Resolve a color name to its hex, and the named paint's catalogue variant. */
310
+ async resolveColor(name, signal, options) {
311
+ return await resolveColorCall(this.#transport, name, signal, options);
328
312
  }
329
313
  // ── Catalog ──────────────────────────────────────────────────────────────────
330
314
  /**
@@ -352,8 +336,8 @@ export class MaterialGraphClient extends CatalogueClient {
352
336
  async listEnumValues(options, signal) {
353
337
  return await listEnumValuesCall(this.#transport, options, signal);
354
338
  }
355
- // Hard-specification search (formerly `bySpec()`, GET
356
- // `/api/v1/search/specifications`) moved to {@link MaterialGraphClient.listVariants}
339
+ // Hard-specification search (formerly `bySpec()`) moved to
340
+ // {@link MaterialGraphClient.listVariants}
357
341
  // (MG-2222): each `attribute:value` criterion is one `filters` condition —
358
342
  // `{field: attributeId, op: "in", values: [...]}` in `filters.terms` for a
359
343
  // term, `{field: attributeId, op: "gte"/"lte"/etc, value}` in
@@ -369,13 +353,12 @@ export class MaterialGraphClient extends CatalogueClient {
369
353
  async whoami(signal) {
370
354
  return await this.#get("whoami", undefined, signal);
371
355
  }
372
- // ── Colour helpers (extract) ─────────────────────────────────────────────────
356
+ // ── Color helpers (extract) ─────────────────────────────────────────────────
373
357
  /**
374
- * THE QUESTION: "what colours are in this image?" — the palette only, no
375
- * materials. The bridge from an inspiration image into the colour lens: extract
376
- * here, then feed the swatches to `colours.palette()` or
377
- * {@link MaterialGraphClient.byColour}. Instead use
378
-
358
+ * THE QUESTION: "what colors are in this image?" — the palette only, no
359
+ * materials. The bridge from an inspiration image into the color lens: extract
360
+ * here, then feed the swatches to
361
+ * {@link MaterialGraphClient.findVariantsByColor} (`mode: "palette"`).
379
362
  */
380
363
  async extractPalette(input, signal) {
381
364
  return await this.#post("extract_image_palette", input, signal);
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Wire types for the category color profile read - GET
3
+ * `/api/v1/colors/profiles`.
4
+ *
5
+ * Kept out of `types.ts` because the shared mirror is at its source-size
6
+ * budget. Same tolerant-parsing contract as
7
+ * the rest of the mirror - response enums are widened so a new server value
8
+ * still type-checks.
9
+ */
10
+ import type { OpenEnum } from "./types.js";
11
+ /**
12
+ * Which taxonomy tier a profile's `categoryKey` names, or `"merged"` where the
13
+ * key is a composite over a caller-defined key set rather than a taxonomy node.
14
+ */
15
+ export type CategoryTier = OpenEnum<"group" | "type" | "merged">;
16
+ /** One representative color bucket of a category. */
17
+ export type CategoryColor = {
18
+ hex: string;
19
+ oklab: {
20
+ l: number;
21
+ a: number;
22
+ b: number;
23
+ };
24
+ variantCount: number;
25
+ /** This bucket's share of the selection's colored variants, 0 to 1. */
26
+ proportion: number;
27
+ };
28
+ export type CategoryColorProfile = {
29
+ categoryKey: string;
30
+ level: CategoryTier;
31
+ name: string;
32
+ path: string;
33
+ /**
34
+ * Under `groupBy: "merged"`, the requested keys in composite-key order, so
35
+ * `categoryKey` never has to be split. Absent on a single-category profile.
36
+ */
37
+ members?: string[];
38
+ variantCount: number;
39
+ avgLightness: number;
40
+ avgChroma: number;
41
+ colors: CategoryColor[];
42
+ };
43
+ /**
44
+ * The snapshot a profile response was computed from. `variantCount` is the
45
+ * size of the whole materialized view across every category, not of the
46
+ * selection; `refreshedAt` is its age.
47
+ */
48
+ export type CategoryColorProfilesBasis = {
49
+ variantCount: number;
50
+ refreshedAt: string | null;
51
+ };
52
+ export type CategoryColorProfilesResponse = {
53
+ profiles: CategoryColorProfile[];
54
+ basis: CategoryColorProfilesBasis;
55
+ };
56
+ /** Request parameters for `colors.profiles()`. */
57
+ export type ColorProfilesParams = {
58
+ /** 1 to 8 category keys, at the tier named by `groupBy`. */
59
+ productTypeIds: readonly string[];
60
+ /**
61
+ * `"type"` (the default) names product types. `"group"` merges each group's
62
+ * leaf product types into one palette; `"merged"` merges EVERY key's leaves
63
+ * into a single combined palette.
64
+ */
65
+ groupBy?: "group" | "type" | "merged";
66
+ /** Representative colors per category, 1 to 64. The server defaults to 8. */
67
+ topN?: number;
68
+ /** `"diverse"` maximises visual distinctness instead of frequency. */
69
+ select?: "frequency" | "diverse";
70
+ };
@@ -0,0 +1,76 @@
1
+ import type { CategoryColorProfilesResponse, ColorProfilesParams } from "./color-profiles-types.js";
2
+ import type { QueryParams } from "./http-transport.js";
3
+ import type { SdkOperationId } from "./operations.js";
4
+ /**
5
+ * The color surface, namespaced as `mg.colors` - `profiles`, the colors a
6
+ * category comes in.
7
+ *
8
+ * A namespace rather than a flat method on an already-large client facade.
9
+ *
10
+ * The namespace holds no transport of its own: the client hands it a bound
11
+ * `get`, so auth, rate-limit accounting and error shaping stay in one place.
12
+ *
13
+ * `profiles` is an aggregate and carries a `basis`: it reads a materialized
14
+ * view refreshed on a schedule, not on write, so check how much of the corpus
15
+ * the answer covers before treating it as complete.
16
+ *
17
+ * ROUTING OUT OF THIS NAMESPACE. Color is a lens, and this is not the only
18
+ * door into it:
19
+ * - One or several colors → `mg.findVariantsByColor({ mode: "palette", colors })`.
20
+ * - A color NAME rather than a hex → `mg.resolveColor(name)` first.
21
+ * - Color WORDS (families, moods, undertones) → `mg.listEnumValues()` for the
22
+ * terms, `mg.listVariantFacets()` for how many variants carry each.
23
+ * - Walking the corpus by lightness/vividness → `mg.listVariants({ orderBy })`.
24
+ * - Color as one signal among words → `mg.searchVariants(...)`.
25
+ *
26
+ * And the honesty rule the whole lens rests on: every color here is DERIVED
27
+ * FROM PRODUCT IMAGERY, not declared by the brand. Report visual
28
+ * likeness, never a stated color specification.
29
+ */
30
+ /** The client's own request primitives, handed down so this file owns no transport. */
31
+ export type ColorsTransport = {
32
+ get<T>(operationId: SdkOperationId, query?: QueryParams, signal?: AbortSignal): Promise<T>;
33
+ };
34
+ export declare class ColorsClient {
35
+ #private;
36
+ constructor(transport: ColorsTransport);
37
+ /**
38
+ * THE QUESTION: "what colors does this CATEGORY come in?" - the
39
+ * representative palette of 1 to 8 categories in ONE call (GET
40
+ * `/api/v1/colors/profiles`).
41
+ *
42
+ * An aggregate over a category, not a list of materials: use
43
+ * `mg.findVariantsByColor()` or `mg.searchVariants()` when you want the
44
+ * materials themselves. Check `basis` -
45
+ * this reads a scheduled materialized view, so it can lag the catalogue.
46
+ *
47
+ * `groupBy: "group"` takes product-type GROUP keys and merges every leaf type
48
+ * under a group into one histogram BEFORE ranking - a group's palette, not
49
+ * the union of its leaves' heads. `select: "diverse"` returns visually spread
50
+ * swatches (greedy max-min over OKLab, 0.02 minimum separation, backfilled by
51
+ * frequency if fewer survive) instead of the most frequent ones; a client
52
+ * cannot recover that spread from a truncated frequency list, which is why it
53
+ * is a server mode rather than a local sort.
54
+ *
55
+ * `groupBy: "merged"` merges across the KEYS as well and returns ONE profile
56
+ * over the union of their leaves - the palette of a caller-defined facet such
57
+ * as "Wood" (`wood_surface` + `wood_flooring`) that the taxonomy holds no
58
+ * single node for, and that no combination of per-key responses reconstructs.
59
+ * Its keys may sit at either tier, `categoryKey` is those keys sorted and
60
+ * joined with `+`, and `members` hands them back so nothing has to split it.
61
+ *
62
+ * A selected category with no color data comes back as a zero-count profile,
63
+ * not a missing key. Category keys come from `mg.categories.list()`.
64
+ *
65
+ * @example
66
+ * const { profiles, basis } = await mg.colors.profiles({
67
+ * productTypeIds: ["wood_surface", "wood_flooring"],
68
+ * groupBy: "merged",
69
+ * select: "diverse",
70
+ * topN: 12,
71
+ * });
72
+ * // profiles[0].members is ["wood_flooring", "wood_surface"]; check
73
+ * // basis.refreshedAt before presenting the palette as current.
74
+ */
75
+ profiles(params: ColorProfilesParams, signal?: AbortSignal): Promise<CategoryColorProfilesResponse>;
76
+ }
@@ -0,0 +1,52 @@
1
+ export class ColorsClient {
2
+ #transport;
3
+ constructor(transport) {
4
+ this.#transport = transport;
5
+ }
6
+ /**
7
+ * THE QUESTION: "what colors does this CATEGORY come in?" - the
8
+ * representative palette of 1 to 8 categories in ONE call (GET
9
+ * `/api/v1/colors/profiles`).
10
+ *
11
+ * An aggregate over a category, not a list of materials: use
12
+ * `mg.findVariantsByColor()` or `mg.searchVariants()` when you want the
13
+ * materials themselves. Check `basis` -
14
+ * this reads a scheduled materialized view, so it can lag the catalogue.
15
+ *
16
+ * `groupBy: "group"` takes product-type GROUP keys and merges every leaf type
17
+ * under a group into one histogram BEFORE ranking - a group's palette, not
18
+ * the union of its leaves' heads. `select: "diverse"` returns visually spread
19
+ * swatches (greedy max-min over OKLab, 0.02 minimum separation, backfilled by
20
+ * frequency if fewer survive) instead of the most frequent ones; a client
21
+ * cannot recover that spread from a truncated frequency list, which is why it
22
+ * is a server mode rather than a local sort.
23
+ *
24
+ * `groupBy: "merged"` merges across the KEYS as well and returns ONE profile
25
+ * over the union of their leaves - the palette of a caller-defined facet such
26
+ * as "Wood" (`wood_surface` + `wood_flooring`) that the taxonomy holds no
27
+ * single node for, and that no combination of per-key responses reconstructs.
28
+ * Its keys may sit at either tier, `categoryKey` is those keys sorted and
29
+ * joined with `+`, and `members` hands them back so nothing has to split it.
30
+ *
31
+ * A selected category with no color data comes back as a zero-count profile,
32
+ * not a missing key. Category keys come from `mg.categories.list()`.
33
+ *
34
+ * @example
35
+ * const { profiles, basis } = await mg.colors.profiles({
36
+ * productTypeIds: ["wood_surface", "wood_flooring"],
37
+ * groupBy: "merged",
38
+ * select: "diverse",
39
+ * topN: 12,
40
+ * });
41
+ * // profiles[0].members is ["wood_flooring", "wood_surface"]; check
42
+ * // basis.refreshedAt before presenting the palette as current.
43
+ */
44
+ async profiles(params, signal) {
45
+ return await this.#transport.get("get_category_color_profiles", {
46
+ groupBy: params.groupBy,
47
+ productTypeIds: params.productTypeIds.join(","),
48
+ select: params.select,
49
+ topN: params.topN,
50
+ }, signal);
51
+ }
52
+ }
@@ -13,8 +13,8 @@
13
13
  export type CompanionProjectType = "Workplace" | "Residential" | "Institution Education" | "Hospitality: Hotel" | "Mixed-Use & Multi-Unit Residential" | "Healthcare" | "Hospitality: Restaurant" | "Government" | "Senior Living" | "Retail" | "Other" | "Sports Entertainment" | "Wellness" | "Medical Dental" | "Aviation" | "Industrial" | "Tech" | "Transportation" | "Single Family" | "Condominium Cooperative" | "Marine" | "Power & Energy" | "University Design Program: Interior Design" | "Non-Billable" | "University Design Program: Interior Architecture" | "University Design Program: Architecture" | "Historic Preservation";
14
14
  /** At least one of `variantIds` and `materials`, at most twelve together. */
15
15
  export type FindCompanionsInput = {
16
- /** Optional row fields: image adds imageUrl, colour adds swatches. */
17
- include?: ("image" | "colour")[];
16
+ /** Optional row fields: image adds imageUrl, color adds swatches. */
17
+ include?: ("image" | "color")[];
18
18
  /** Ops and audit switch: leave sample cards in. Default false. */
19
19
  includeSamplingArtifacts?: boolean;
20
20
  /** Companions to return, default 20, at most 100. */
@@ -28,8 +28,8 @@ export type FindCompanionsInput = {
28
28
  };
29
29
  export type FindCompanionsRow = {
30
30
  brand: string;
31
- /** Palette likeness to the seeds. Present with `include: ["colour"]`; never reorders. */
32
- colourFit?: number | null;
31
+ /** Palette likeness to the seeds. Present with `include: ["color"]`; never reorders. */
32
+ colorFit?: number | null;
33
33
  /** Present with `include: ["image"]`. */
34
34
  imageUrl?: string | null;
35
35
  /** Mean lift over the seeds it pairs with; above 1 is above average. */
@@ -37,11 +37,11 @@ export type FindCompanionsRow = {
37
37
  product: string;
38
38
  /** Why it was suggested, in one line. */
39
39
  reason: string;
40
- /** `companion` is another brand; `brand_sibling` and `colourway` share the seed's. */
41
- relation: "companion" | "brand_sibling" | "colourway";
40
+ /** `companion` is another brand; `brand_sibling` and `colorway` share the seed's. */
41
+ relation: "companion" | "brand_sibling" | "colorway";
42
42
  /** How many seeds it was sampled with. */
43
43
  seedMatchCount: number;
44
- /** Present with `include: ["colour"]`. */
44
+ /** Present with `include: ["color"]`. */
45
45
  swatches?: {
46
46
  hex: string;
47
47
  proportion: number;
@@ -64,7 +64,7 @@ export type FindCompanionsOutput = {
64
64
  variantName: string | null;
65
65
  }[];
66
66
  /** Seed ids outside the materials catalogue; present only when at least one was. */
67
- excludedByCorpusScope?: string[];
67
+ excludedBySearchCatalogueSubset?: string[];
68
68
  /** References that matched no variant. */
69
69
  unresolved: string[];
70
70
  };