@cavuno/board 3.2.0 → 4.0.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 (77) hide show
  1. package/CHANGELOG.md +406 -0
  2. package/README.md +6 -2
  3. package/dist/{_spec-nkWAraS0.d.mts → _spec-GVe6WAfT.d.mts} +68 -59
  4. package/dist/{_spec-nkWAraS0.d.ts → _spec-GVe6WAfT.d.ts} +68 -59
  5. package/dist/bin.mjs +7 -2
  6. package/dist/{blog-iV9XEIGf.d.ts → blog-BsmKizpd.d.ts} +1 -1
  7. package/dist/{blog-DGBHmExV.d.mts → blog-Cf64mI8A.d.mts} +1 -1
  8. package/dist/{board-BJbXd_9_.d.mts → board-DUhgiacm.d.ts} +7 -6
  9. package/dist/{board-JhdUWIoq.d.ts → board-GGWJ4coe.d.mts} +7 -6
  10. package/dist/breadcrumbs-DFKr0HzU.d.mts +372 -0
  11. package/dist/breadcrumbs-DiccJhk4.d.ts +372 -0
  12. package/dist/chunk-BGKNXG4H.mjs +146 -0
  13. package/dist/{chunk-NXY3OWGF.mjs → chunk-T2KC6XGV.mjs} +1 -1
  14. package/dist/{chunk-STE77MAT.mjs → chunk-YEAQ3JEI.mjs} +143 -134
  15. package/dist/doctor.js +1 -1
  16. package/dist/doctor.mjs +1 -1
  17. package/dist/filters.d.mts +25 -20
  18. package/dist/filters.d.ts +25 -20
  19. package/dist/filters.js +3 -692
  20. package/dist/filters.mjs +3 -29
  21. package/dist/format.d.mts +57 -231
  22. package/dist/format.d.ts +57 -231
  23. package/dist/format.js +327 -968
  24. package/dist/format.mjs +207 -264
  25. package/dist/go.js +11 -1
  26. package/dist/go.mjs +1 -1
  27. package/dist/index.d.mts +23 -76
  28. package/dist/index.d.ts +23 -76
  29. package/dist/index.js +11 -36
  30. package/dist/index.mjs +11 -36
  31. package/dist/{jobs-BiEvL9d7.d.ts → jobs-BuLpV8iy.d.mts} +4 -4
  32. package/dist/{jobs-lVvKCBCU.d.mts → jobs-QrbXOYB9.d.ts} +4 -4
  33. package/dist/paths.d.mts +89 -1
  34. package/dist/paths.d.ts +89 -1
  35. package/dist/paths.js +62 -14
  36. package/dist/paths.mjs +11 -3
  37. package/dist/route-contract.js +23 -13
  38. package/dist/route-contract.mjs +2 -2
  39. package/dist/{search-CyRvVMUp.d.ts → search-Cr4noJJZ.d.mts} +14 -3
  40. package/dist/{search-CCyFWPi3.d.mts → search-DdsniXQv.d.ts} +14 -3
  41. package/dist/seo.d.mts +30 -29
  42. package/dist/seo.d.ts +30 -29
  43. package/dist/seo.js +197 -718
  44. package/dist/seo.mjs +26 -24
  45. package/dist/server.d.mts +5 -5
  46. package/dist/server.d.ts +5 -5
  47. package/dist/sitemap.d.mts +5 -5
  48. package/dist/sitemap.d.ts +5 -5
  49. package/dist/sitemap.js +25 -14
  50. package/dist/sitemap.mjs +3 -2
  51. package/dist/skills.js +1 -1
  52. package/dist/skills.mjs +1 -1
  53. package/dist/suggest.d.mts +2 -2
  54. package/dist/suggest.d.ts +2 -2
  55. package/dist/suggest.js +17 -3
  56. package/dist/suggest.mjs +17 -3
  57. package/dist/well-known.js +23 -13
  58. package/dist/well-known.mjs +2 -2
  59. package/package.json +2 -4
  60. package/skills/cavuno-board-applications/SKILL.md +4 -1
  61. package/skills/cavuno-board-filters/SKILL.md +11 -12
  62. package/skills/cavuno-board-format/SKILL.md +68 -56
  63. package/skills/cavuno-board-i18n/SKILL.md +102 -21
  64. package/skills/cavuno-board-search-suggestions/SKILL.md +8 -3
  65. package/skills/cavuno-board-seo/BLOG.md +4 -1
  66. package/skills/cavuno-board-seo/JOB_AND_BREADCRUMBS.md +5 -2
  67. package/skills/cavuno-board-seo/LISTING.md +35 -12
  68. package/skills/cavuno-board-seo/SALARY.md +83 -18
  69. package/skills/cavuno-board-seo/SKILL.md +3 -2
  70. package/skills/manifest.json +3 -3
  71. package/dist/breadcrumbs-1hMjcsqA.d.mts +0 -171
  72. package/dist/breadcrumbs-DexU07TT.d.ts +0 -171
  73. package/dist/chunk-IJCJ26SQ.mjs +0 -596
  74. package/dist/chunk-L7ZOYWHW.mjs +0 -98
  75. package/dist/chunk-LUGJWOT7.mjs +0 -96
  76. package/dist/ui-copy-CCkz6CVF.d.mts +0 -230
  77. package/dist/ui-copy-CCkz6CVF.d.ts +0 -230
@@ -1,51 +1,83 @@
1
1
  import {
2
- getSalaryLexicon,
3
- uiCopy
4
- } from "./chunk-IJCJ26SQ.mjs";
2
+ BOARD_PATHS,
3
+ jobsCategoryPath,
4
+ jobsLocationCategoryPath,
5
+ jobsLocationPath
6
+ } from "./chunk-BGKNXG4H.mjs";
5
7
 
6
- // src/seo/salary.ts
7
- var compactCurrencyCache = /* @__PURE__ */ new Map();
8
- function getCompactCurrencyFormatter(language) {
9
- const cached = compactCurrencyCache.get(language);
10
- if (cached) return cached;
11
- const formatter = new Intl.NumberFormat(language, {
12
- style: "currency",
13
- currency: "USD",
14
- notation: "compact",
15
- compactDisplay: "short",
16
- minimumFractionDigits: 0,
17
- maximumFractionDigits: 1
18
- });
19
- compactCurrencyCache.set(language, formatter);
20
- return formatter;
21
- }
22
- function formatUsd(locale, value) {
8
+ // src/format/locale.ts
9
+ function normalizeLocale(language) {
10
+ const trimmed = language.trim();
11
+ if (!trimmed) return null;
23
12
  try {
24
- return getCompactCurrencyFormatter(locale).format(value);
13
+ const candidate = trimmed.replace(/_/g, "-");
14
+ const [canonical] = Intl.getCanonicalLocales(candidate);
15
+ if (!canonical) return null;
16
+ const [supported] = Intl.NumberFormat.supportedLocalesOf([canonical]);
17
+ return supported ?? null;
25
18
  } catch {
26
- if (value >= 1e6) {
27
- const millions = value / 1e6;
28
- return `$${millions % 1 === 0 ? millions : millions.toFixed(1)}M`;
29
- }
30
- if (value >= 1e3) {
31
- const thousands = value / 1e3;
32
- return `$${thousands % 1 === 0 ? thousands : thousands.toFixed(1)}k`;
33
- }
34
- return `$${value}`;
19
+ return null;
35
20
  }
36
21
  }
37
- function formatRange(locale, min, max) {
38
- return `${formatUsd(locale, min)} \u2013 ${formatUsd(locale, max)}`;
39
- }
40
- function formatSeniority(locale, value, overrides) {
41
- if (overrides?.[value]) {
42
- return overrides[value];
22
+
23
+ // src/seo/salary.ts
24
+ var currencyFormatterCache = /* @__PURE__ */ new Map();
25
+ function notationForMagnitude(...values) {
26
+ let anyFinite = false;
27
+ for (const v of values) {
28
+ if (v == null || !Number.isFinite(v)) continue;
29
+ anyFinite = true;
30
+ if (Math.abs(v) >= 1e3) return "compact";
43
31
  }
44
- const lexiconLabel = getSalaryLexicon(locale).seniority[value];
45
- if (lexiconLabel) {
46
- return lexiconLabel;
32
+ return anyFinite ? "standard" : "compact";
33
+ }
34
+ function getCurrencyFormatter(language, currencyCode, notation) {
35
+ const key = `${language}:${currencyCode}:${notation}`;
36
+ const cached = currencyFormatterCache.get(key);
37
+ if (cached !== void 0) return cached;
38
+ let formatter;
39
+ try {
40
+ const options = {
41
+ style: "currency",
42
+ currency: currencyCode,
43
+ notation
44
+ };
45
+ if (notation === "compact") {
46
+ options.compactDisplay = "short";
47
+ options.minimumFractionDigits = 0;
48
+ options.maximumFractionDigits = 1;
49
+ }
50
+ formatter = new Intl.NumberFormat(language, options);
51
+ } catch {
52
+ formatter = null;
47
53
  }
48
- return value.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
54
+ currencyFormatterCache.set(key, formatter);
55
+ return formatter;
56
+ }
57
+ function formatSalaryStat(locale, value, currency, notation) {
58
+ if (!Number.isFinite(value)) return null;
59
+ const currencyCode = currency?.trim().toUpperCase() || null;
60
+ if (!currencyCode) return null;
61
+ const normalized = normalizeLocale(locale);
62
+ if (!normalized) return null;
63
+ const resolved = notation ?? notationForMagnitude(value);
64
+ const formatter = getCurrencyFormatter(normalized, currencyCode, resolved);
65
+ if (!formatter) return null;
66
+ return formatter.format(value);
67
+ }
68
+ function formatSalaryStatRange(locale, min, max, currency, notation) {
69
+ if (!Number.isFinite(min) || !Number.isFinite(max)) return null;
70
+ const currencyCode = currency?.trim().toUpperCase() || null;
71
+ if (!currencyCode) return null;
72
+ const normalized = normalizeLocale(locale);
73
+ if (!normalized) return null;
74
+ const resolved = notation ?? notationForMagnitude(min, max);
75
+ const formatter = getCurrencyFormatter(normalized, currencyCode, resolved);
76
+ if (!formatter) return null;
77
+ const low = Math.min(min, max);
78
+ const high = Math.max(min, max);
79
+ if (low === high) return formatter.format(low);
80
+ return formatter.formatRange(low, high);
49
81
  }
50
82
  var SENIORITY_ORDER = [
51
83
  "entry_level",
@@ -75,7 +107,7 @@ var yearly = {
75
107
  function distribution(name, currency, minValue, maxValue, stats = {}) {
76
108
  return {
77
109
  "@type": "MonetaryAmountDistribution",
78
- name,
110
+ ...name !== void 0 ? { name } : {},
79
111
  currency,
80
112
  minValue,
81
113
  maxValue,
@@ -83,15 +115,19 @@ function distribution(name, currency, minValue, maxValue, stats = {}) {
83
115
  ...yearly
84
116
  };
85
117
  }
86
- function seniorityDistributions(locale, rows, currency, nameFor) {
87
- return rows.map(
88
- (row) => distribution(
89
- nameFor(formatSeniority(locale, row.seniority)),
118
+ function seniorityDistributions(rows, currency, entity, options) {
119
+ return rows.map((row) => {
120
+ const name = options?.seniorityName?.({
121
+ seniority: row.seniority,
122
+ entity
123
+ });
124
+ return distribution(
125
+ name,
90
126
  currency,
91
127
  row.avgSalaryMin,
92
128
  row.avgSalaryMax
93
- )
94
- );
129
+ );
130
+ });
95
131
  }
96
132
  function salaryEnvelope(type, name, extra, estimatedSalary) {
97
133
  return {
@@ -102,17 +138,27 @@ function salaryEnvelope(type, name, extra, estimatedSalary) {
102
138
  estimatedSalary
103
139
  };
104
140
  }
105
- function buildSalaryFaq(locale, label, overall) {
141
+ function buildSalaryFaq(locale, label, overall, currency) {
106
142
  if (!overall) return [];
107
- const range = formatRange(locale, overall.avgMin, overall.avgMax);
143
+ const range = formatSalaryStatRange(
144
+ locale,
145
+ overall.avgMin,
146
+ overall.avgMax,
147
+ currency
148
+ );
108
149
  return [
109
150
  {
110
- q: `What is the average salary for ${label}?`,
111
- a: `The average salary for ${label} is ${range} per year, based on ${overall.jobCount} job ${overall.jobCount === 1 ? "posting" : "postings"} on this board that disclose pay.`
151
+ kind: "average",
152
+ label,
153
+ range,
154
+ avgMin: overall.avgMin,
155
+ avgMax: overall.avgMax,
156
+ currency,
157
+ jobCount: overall.jobCount
112
158
  },
113
159
  {
114
- q: `How is the salary figure for ${label} calculated?`,
115
- a: `These figures are aggregated from current and recent job postings on this board that disclose pay, then averaged across roles. They are estimates, not guarantees of compensation.`
160
+ kind: "methodology",
161
+ label
116
162
  }
117
163
  ];
118
164
  }
@@ -142,11 +188,11 @@ function faqJsonLd(faqs) {
142
188
  }))
143
189
  };
144
190
  }
145
- function titleSalaryJsonLd(locale, d) {
191
+ function titleSalaryJsonLd(d, options) {
146
192
  if (!d.overallSalary) return null;
147
193
  return salaryEnvelope("Occupation", d.categoryName, {}, [
148
194
  distribution(
149
- `${d.categoryName} salary (all levels)`,
195
+ d.categoryName,
150
196
  d.currency,
151
197
  d.overallSalary.avgMin,
152
198
  d.overallSalary.avgMax,
@@ -156,10 +202,10 @@ function titleSalaryJsonLd(locale, d) {
156
202
  }
157
203
  ),
158
204
  ...seniorityDistributions(
159
- locale,
160
205
  d.bySeniority,
161
206
  d.currency,
162
- (label) => `${label} ${d.categoryName}`
207
+ d.categoryName,
208
+ options
163
209
  )
164
210
  ]);
165
211
  }
@@ -167,7 +213,7 @@ function skillSalaryJsonLd(d) {
167
213
  if (!d.overallSalary) return null;
168
214
  return salaryEnvelope("Occupation", d.skillName, {}, [
169
215
  distribution(
170
- `${d.skillName} salary (all levels)`,
216
+ d.skillName,
171
217
  d.currency,
172
218
  d.overallSalary.avgMin,
173
219
  d.overallSalary.avgMax,
@@ -179,42 +225,22 @@ function skillSalaryJsonLd(d) {
179
225
  )
180
226
  ]);
181
227
  }
182
- function locationSalaryJsonLd(d) {
228
+ function locationSalaryJsonLd(d, options) {
229
+ if (!options?.occupationUrl) return null;
183
230
  const isCity = d.adminLevel === "city" || d.adminLevel === "locality";
184
- if (!isCity || !d.overallSalary) return null;
185
- return salaryEnvelope(
186
- "Occupation",
187
- `Average Salary in ${d.placeName}`,
188
- {
189
- occupationLocation: {
190
- "@type": "City",
191
- name: d.placeName,
192
- address: { "@type": "PostalAddress", addressCountry: d.countryCode }
193
- }
194
- },
195
- [
196
- distribution(
197
- `Average salary in ${d.placeName}`,
198
- d.currency,
199
- d.overallSalary.avgMin,
200
- d.overallSalary.avgMax,
201
- {
202
- median: Math.round(
203
- (d.overallSalary.medianMin + d.overallSalary.medianMax) / 2
204
- ),
205
- // Hosted includes these unconditionally, nulls and all.
206
- percentile25: d.overallSalary.p25Min,
207
- percentile75: d.overallSalary.p75Max
208
- }
209
- )
210
- ]
231
+ if (!isCity) return null;
232
+ return itemListJsonLd(
233
+ d.topCategories.map((c) => ({
234
+ name: c.categoryName,
235
+ url: options.occupationUrl(c)
236
+ }))
211
237
  );
212
238
  }
213
- function crossAxisSalaryJsonLd(locale, args) {
239
+ function crossAxisSalaryJsonLd(args, options) {
214
240
  if (!args.overall) return null;
215
241
  return salaryEnvelope(
216
242
  "Occupation",
217
- `${args.name} salary in ${args.placeName}`,
243
+ args.name,
218
244
  {
219
245
  occupationLocation: {
220
246
  "@type": "AdministrativeArea",
@@ -227,7 +253,7 @@ function crossAxisSalaryJsonLd(locale, args) {
227
253
  },
228
254
  [
229
255
  distribution(
230
- `${args.name} salary in ${args.placeName} (all levels)`,
256
+ args.name,
231
257
  args.currency,
232
258
  args.overall.avgMin,
233
259
  args.overall.avgMax,
@@ -237,56 +263,35 @@ function crossAxisSalaryJsonLd(locale, args) {
237
263
  }
238
264
  ),
239
265
  ...seniorityDistributions(
240
- locale,
241
266
  args.bySeniority,
242
267
  args.currency,
243
- (label) => `${label} ${args.name} in ${args.placeName}`
268
+ args.name,
269
+ options
244
270
  )
245
271
  ]
246
272
  );
247
273
  }
248
- function companySalaryJsonLd(locale, d) {
249
- if (!d.overallSalary) return null;
250
- return salaryEnvelope(
251
- "OccupationAggregationByEmployer",
252
- `Jobs at ${d.companyName}`,
253
- {
254
- sampleSize: d.overallSalary.jobCount,
255
- hiringOrganization: { "@type": "Organization", name: d.companyName }
256
- },
257
- [
258
- distribution(
259
- "base",
260
- d.currency,
261
- d.overallSalary.avgMin,
262
- d.overallSalary.avgMax,
263
- {
264
- ...d.boardMedianMin && d.boardMedianMax ? { median: Math.round((d.boardMedianMin + d.boardMedianMax) / 2) } : {},
265
- ...d.boardP25Min ? { percentile25: d.boardP25Min } : {},
266
- ...d.boardP75Max ? { percentile75: d.boardP75Max } : {}
267
- }
268
- ),
269
- ...seniorityDistributions(
270
- locale,
271
- d.bySeniority,
272
- d.currency,
273
- (label) => `${label} salary`
274
- )
275
- ]
274
+ function companySalaryJsonLd(d, options) {
275
+ if (!options?.occupationUrl) return null;
276
+ return itemListJsonLd(
277
+ d.byCategory.map((c) => ({
278
+ name: c.categoryName,
279
+ url: options.occupationUrl(c)
280
+ }))
276
281
  );
277
282
  }
278
- function companyCategorySalaryJsonLd(locale, d) {
283
+ function companyCategorySalaryJsonLd(d, options) {
279
284
  if (!d.overallSalary) return null;
280
285
  return salaryEnvelope(
281
286
  "Occupation",
282
- `${d.categoryName} at ${d.companyName}`,
287
+ d.categoryName,
283
288
  {
284
289
  occupationalCategory: d.categoryName,
285
290
  hiringOrganization: { "@type": "Organization", name: d.companyName }
286
291
  },
287
292
  [
288
293
  distribution(
289
- `${d.categoryName} salary (all levels)`,
294
+ d.categoryName,
290
295
  d.currency,
291
296
  d.overallSalary.avgMin,
292
297
  d.overallSalary.avgMax,
@@ -296,10 +301,10 @@ function companyCategorySalaryJsonLd(locale, d) {
296
301
  }
297
302
  ),
298
303
  ...seniorityDistributions(
299
- locale,
300
304
  d.bySeniority,
301
305
  d.currency,
302
- (label) => `${label} ${d.categoryName}`
306
+ d.categoryName,
307
+ options
303
308
  )
304
309
  ]
305
310
  );
@@ -799,32 +804,36 @@ function resolveBreadcrumbHref(href, origin) {
799
804
  function isAbsoluteUrl(href) {
800
805
  return /^https?:\/\//i.test(href);
801
806
  }
802
- function buildJobBreadcrumbs(job, language, labels) {
803
- const copy = uiCopy(language, labels).breadcrumbs;
807
+ function buildJobBreadcrumbs(job) {
804
808
  const crumbs = [
805
- { name: copy.home, path: "/" },
806
- { name: copy.jobs, path: "/jobs" }
809
+ { kind: "home", path: BOARD_PATHS.home },
810
+ { kind: "jobs", path: BOARD_PATHS.jobs }
807
811
  ];
808
812
  let lastPlaceSlug = null;
809
813
  for (const place of job.placeHierarchy) {
810
814
  lastPlaceSlug = place.slug;
811
- crumbs.push({ name: place.name, path: `/jobs/locations/${place.slug}` });
815
+ crumbs.push({
816
+ kind: "place",
817
+ name: place.name,
818
+ path: jobsLocationPath(place.slug)
819
+ });
812
820
  }
813
821
  const primaryCategory = job.categories[0];
814
822
  if (primaryCategory) {
815
823
  crumbs.push({
824
+ kind: "category",
816
825
  name: primaryCategory.name,
817
- path: lastPlaceSlug ? `/jobs/locations/${lastPlaceSlug}/${primaryCategory.slug}` : `/jobs/${primaryCategory.slug}`
826
+ path: lastPlaceSlug ? jobsLocationCategoryPath(lastPlaceSlug, primaryCategory.slug) : jobsCategoryPath(primaryCategory.slug)
818
827
  });
819
828
  }
820
- crumbs.push({ name: job.title });
829
+ crumbs.push({ kind: "job", name: job.title });
821
830
  return crumbs;
822
831
  }
823
832
 
824
833
  export {
825
- formatUsd,
826
- formatRange,
827
- formatSeniority,
834
+ normalizeLocale,
835
+ formatSalaryStat,
836
+ formatSalaryStatRange,
828
837
  SENIORITY_ORDER,
829
838
  sortBySeniority,
830
839
  buildSalaryFaq,
package/dist/doctor.js CHANGED
@@ -57,7 +57,7 @@ function loadSkillCorpus(baseDir) {
57
57
  ).map((entry) => ({
58
58
  path: entry.name,
59
59
  content: (0, import_node_fs.readFileSync)((0, import_node_path.resolve)(skillDir, entry.name), "utf8")
60
- })).sort((a, b) => a.path.localeCompare(b.path));
60
+ })).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
61
61
  return {
62
62
  ...skill,
63
63
  content: (0, import_node_fs.readFileSync)(skillPath, "utf8"),
package/dist/doctor.mjs CHANGED
@@ -27,7 +27,7 @@ function loadSkillCorpus(baseDir) {
27
27
  ).map((entry) => ({
28
28
  path: entry.name,
29
29
  content: readFileSync(resolve(skillDir, entry.name), "utf8")
30
- })).sort((a, b) => a.path.localeCompare(b.path));
30
+ })).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
31
31
  return {
32
32
  ...skill,
33
33
  content: readFileSync(skillPath, "utf8"),
@@ -1,6 +1,27 @@
1
- import { B as BoardLabelOverrides } from './ui-copy-CCkz6CVF.mjs';
2
- import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-lVvKCBCU.mjs';
3
- import './_spec-nkWAraS0.mjs';
1
+ import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-BuLpV8iy.mjs';
2
+ import './_spec-GVe6WAfT.mjs';
3
+
4
+ /**
5
+ * `@cavuno/board/filters` — the canonical listing-filter vocabulary and URL
6
+ * search-param parsing for jobs-listing routes.
7
+ *
8
+ * One greppable convention across every tenant frontend: the vocabulary
9
+ * arrays mirror the hosted board; seniority URL parsing follows the hosted
10
+ * semantics exactly (trim, lowercase, dedupe — hand-typed URLs are messy).
11
+ *
12
+ * Map parsed filters onto the wire like:
13
+ * ```
14
+ * companySlug: filters.company,
15
+ * seniority: filters.seniority,
16
+ * remoteOption: filters.remoteOption ? [filters.remoteOption] : undefined,
17
+ * ```
18
+ * Company slugs are the URL identity; the API accepts them directly via
19
+ * `companySlug` — never resolve slug→id client-side.
20
+ *
21
+ * Display labels for seniority / sort / remote options are application-owned
22
+ * chrome words — this entry only ships the wire vocabulary and
23
+ * parsers.
24
+ */
4
25
 
5
26
  declare const REMOTE_OPTIONS: readonly RemoteOption[];
6
27
  /**
@@ -12,22 +33,6 @@ declare const EMPLOYMENT_TYPES: readonly EmploymentType[];
12
33
  declare const SENIORITIES: readonly Seniority[];
13
34
  declare const JOB_SORTS: readonly JobSort[];
14
35
  declare const DEFAULT_SORT: JobSort;
15
- /**
16
- * Seniority display labels in the board language (lexicon-backed, en+de),
17
- * ⊕ stored operator overrides (`jobCardLabels.seniority*`) when the
18
- * `labels` bag is passed.
19
- *
20
- * @example
21
- * seniorityLabels('de').executive; // "Führungskraft"
22
- */
23
- declare function seniorityLabels(locale: string, labels?: BoardLabelOverrides): Record<Seniority, string>;
24
- /**
25
- * Sort-dropdown labels in the board language, resolved from the copy
26
- * catalog (`uiCopy(locale).jobCard`) ⊕ stored operator overrides
27
- * (`jobCardLabels.aiRankedLabel` / `sortNewestLabel` / `sortSalaryHighLabel`)
28
- * when the `labels` bag is passed.
29
- */
30
- declare function sortLabels(locale: string, labels?: BoardLabelOverrides): Record<JobSort, string>;
31
36
  interface ListingFilters {
32
37
  q?: string;
33
38
  remoteOption?: RemoteOption;
@@ -66,4 +71,4 @@ declare function parseCompany(raw: unknown): string[] | undefined;
66
71
  */
67
72
  declare function parseListingFilters(search: Record<string, unknown>): ListingFilters;
68
73
 
69
- export { DEFAULT_SORT, EMPLOYMENT_TYPES, JOB_SORTS, type ListingFilters, REMOTE_OPTIONS, SENIORITIES, parseCompany, parseListingFilters, parseSeniority, seniorityLabels, sortLabels };
74
+ export { DEFAULT_SORT, EMPLOYMENT_TYPES, JOB_SORTS, type ListingFilters, REMOTE_OPTIONS, SENIORITIES, parseCompany, parseListingFilters, parseSeniority };
package/dist/filters.d.ts CHANGED
@@ -1,6 +1,27 @@
1
- import { B as BoardLabelOverrides } from './ui-copy-CCkz6CVF.js';
2
- import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-BiEvL9d7.js';
3
- import './_spec-nkWAraS0.js';
1
+ import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-QrbXOYB9.js';
2
+ import './_spec-GVe6WAfT.js';
3
+
4
+ /**
5
+ * `@cavuno/board/filters` — the canonical listing-filter vocabulary and URL
6
+ * search-param parsing for jobs-listing routes.
7
+ *
8
+ * One greppable convention across every tenant frontend: the vocabulary
9
+ * arrays mirror the hosted board; seniority URL parsing follows the hosted
10
+ * semantics exactly (trim, lowercase, dedupe — hand-typed URLs are messy).
11
+ *
12
+ * Map parsed filters onto the wire like:
13
+ * ```
14
+ * companySlug: filters.company,
15
+ * seniority: filters.seniority,
16
+ * remoteOption: filters.remoteOption ? [filters.remoteOption] : undefined,
17
+ * ```
18
+ * Company slugs are the URL identity; the API accepts them directly via
19
+ * `companySlug` — never resolve slug→id client-side.
20
+ *
21
+ * Display labels for seniority / sort / remote options are application-owned
22
+ * chrome words — this entry only ships the wire vocabulary and
23
+ * parsers.
24
+ */
4
25
 
5
26
  declare const REMOTE_OPTIONS: readonly RemoteOption[];
6
27
  /**
@@ -12,22 +33,6 @@ declare const EMPLOYMENT_TYPES: readonly EmploymentType[];
12
33
  declare const SENIORITIES: readonly Seniority[];
13
34
  declare const JOB_SORTS: readonly JobSort[];
14
35
  declare const DEFAULT_SORT: JobSort;
15
- /**
16
- * Seniority display labels in the board language (lexicon-backed, en+de),
17
- * ⊕ stored operator overrides (`jobCardLabels.seniority*`) when the
18
- * `labels` bag is passed.
19
- *
20
- * @example
21
- * seniorityLabels('de').executive; // "Führungskraft"
22
- */
23
- declare function seniorityLabels(locale: string, labels?: BoardLabelOverrides): Record<Seniority, string>;
24
- /**
25
- * Sort-dropdown labels in the board language, resolved from the copy
26
- * catalog (`uiCopy(locale).jobCard`) ⊕ stored operator overrides
27
- * (`jobCardLabels.aiRankedLabel` / `sortNewestLabel` / `sortSalaryHighLabel`)
28
- * when the `labels` bag is passed.
29
- */
30
- declare function sortLabels(locale: string, labels?: BoardLabelOverrides): Record<JobSort, string>;
31
36
  interface ListingFilters {
32
37
  q?: string;
33
38
  remoteOption?: RemoteOption;
@@ -66,4 +71,4 @@ declare function parseCompany(raw: unknown): string[] | undefined;
66
71
  */
67
72
  declare function parseListingFilters(search: Record<string, unknown>): ListingFilters;
68
73
 
69
- export { DEFAULT_SORT, EMPLOYMENT_TYPES, JOB_SORTS, type ListingFilters, REMOTE_OPTIONS, SENIORITIES, parseCompany, parseListingFilters, parseSeniority, seniorityLabels, sortLabels };
74
+ export { DEFAULT_SORT, EMPLOYMENT_TYPES, JOB_SORTS, type ListingFilters, REMOTE_OPTIONS, SENIORITIES, parseCompany, parseListingFilters, parseSeniority };