@cavuno/board 3.1.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 +421 -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-BsmKizpd.d.ts +29 -0
  7. package/dist/blog-Cf64mI8A.d.mts +29 -0
  8. package/dist/{salaries-BahBpxWT.d.ts → board-DUhgiacm.d.ts} +17 -29
  9. package/dist/{salaries-ChSqDBAM.d.mts → board-GGWJ4coe.d.mts} +17 -29
  10. package/dist/breadcrumbs-DFKr0HzU.d.mts +372 -0
  11. package/dist/breadcrumbs-DiccJhk4.d.ts +372 -0
  12. package/dist/chunk-2ETP5UH4.mjs +271 -0
  13. package/dist/chunk-BGKNXG4H.mjs +146 -0
  14. package/dist/chunk-DLSO6XXY.mjs +33 -0
  15. package/dist/chunk-T2KC6XGV.mjs +1060 -0
  16. package/dist/chunk-YEAQ3JEI.mjs +853 -0
  17. package/dist/doctor.js +1 -1
  18. package/dist/doctor.mjs +1 -1
  19. package/dist/filters.d.mts +25 -20
  20. package/dist/filters.d.ts +25 -20
  21. package/dist/filters.js +3 -692
  22. package/dist/filters.mjs +3 -690
  23. package/dist/format.d.mts +57 -230
  24. package/dist/format.d.ts +57 -230
  25. package/dist/format.js +355 -927
  26. package/dist/format.mjs +213 -931
  27. package/dist/go.js +11 -1
  28. package/dist/go.mjs +4 -29
  29. package/dist/index.d.mts +23 -75
  30. package/dist/index.d.ts +23 -75
  31. package/dist/index.js +11 -36
  32. package/dist/index.mjs +32 -313
  33. package/dist/{jobs-lVvKCBCU.d.mts → jobs-BuLpV8iy.d.mts} +4 -4
  34. package/dist/{jobs-BiEvL9d7.d.ts → jobs-QrbXOYB9.d.ts} +4 -4
  35. package/dist/paths.d.mts +89 -1
  36. package/dist/paths.d.ts +89 -1
  37. package/dist/paths.js +62 -14
  38. package/dist/paths.mjs +29 -78
  39. package/dist/route-contract.js +23 -13
  40. package/dist/route-contract.mjs +30 -1079
  41. package/dist/{search-CCyFWPi3.d.mts → search-Cr4noJJZ.d.mts} +14 -3
  42. package/dist/{search-CyRvVMUp.d.ts → search-DdsniXQv.d.ts} +14 -3
  43. package/dist/seo.d.mts +33 -189
  44. package/dist/seo.d.ts +33 -189
  45. package/dist/seo.js +197 -718
  46. package/dist/seo.mjs +44 -1417
  47. package/dist/server.d.mts +5 -5
  48. package/dist/server.d.ts +5 -5
  49. package/dist/server.mjs +5 -31
  50. package/dist/sitemap.d.mts +5 -5
  51. package/dist/sitemap.d.ts +5 -5
  52. package/dist/sitemap.js +25 -14
  53. package/dist/sitemap.mjs +22 -97
  54. package/dist/skills.js +1 -1
  55. package/dist/skills.mjs +1 -1
  56. package/dist/suggest.d.mts +2 -2
  57. package/dist/suggest.d.ts +2 -2
  58. package/dist/suggest.js +17 -3
  59. package/dist/suggest.mjs +17 -3
  60. package/dist/well-known.js +23 -13
  61. package/dist/well-known.mjs +4 -421
  62. package/package.json +5 -4
  63. package/skills/cavuno-board-applications/SKILL.md +4 -1
  64. package/skills/cavuno-board-filters/SKILL.md +11 -12
  65. package/skills/cavuno-board-format/SKILL.md +68 -56
  66. package/skills/cavuno-board-i18n/SKILL.md +102 -21
  67. package/skills/cavuno-board-search-suggestions/SKILL.md +8 -3
  68. package/skills/cavuno-board-seo/BLOG.md +4 -1
  69. package/skills/cavuno-board-seo/JOB_AND_BREADCRUMBS.md +5 -2
  70. package/skills/cavuno-board-seo/LISTING.md +35 -12
  71. package/skills/cavuno-board-seo/SALARY.md +83 -18
  72. package/skills/cavuno-board-seo/SKILL.md +3 -2
  73. package/skills/manifest.json +3 -3
  74. package/dist/board-NE98PlwN.d.ts +0 -16
  75. package/dist/board-muu8BI8P.d.mts +0 -16
  76. package/dist/ui-copy-CCkz6CVF.d.mts +0 -230
  77. package/dist/ui-copy-CCkz6CVF.d.ts +0 -230
package/dist/format.d.mts CHANGED
@@ -1,8 +1,7 @@
1
- import { P as PublicJob, a as PublicJobCard, C as CustomFieldValues } from './jobs-lVvKCBCU.mjs';
2
- import { B as BoardLabelOverrides } from './ui-copy-CCkz6CVF.mjs';
3
- export { A as AlertsCopy, a as ApplyCopy, b as BlogCopy, c as BreadcrumbsCopy, C as CopyLinkCopy, E as EntityCopy, F as FooterCopy, J as JobCardCopy, d as JobDetailCopy, e as JobSearchCopy, N as NavCopy, P as PUBLIC_LABEL_GROUPS, f as PaginationCopy, S as SalaryCopy, U as UiCopy, u as uiCopy } from './ui-copy-CCkz6CVF.mjs';
4
- import { C as CustomFieldDefinition } from './board-muu8BI8P.mjs';
5
- import './_spec-nkWAraS0.mjs';
1
+ export { C as CurrencyDisplay, F as FormattedSalaryRange, J as JobBreadcrumb, N as NumberNotation, S as SalaryTimeframeInput, a as SalaryTimeframeValue, b as buildJobBreadcrumbs, f as formatSalaryRange, c as formatSalaryStat, d as formatSalaryStatRange, n as normalizeWebsiteUrl } from './breadcrumbs-DFKr0HzU.mjs';
2
+ import { C as CustomFieldDefinition } from './board-GGWJ4coe.mjs';
3
+ import { C as CustomFieldValues } from './jobs-BuLpV8iy.mjs';
4
+ import './_spec-GVe6WAfT.mjs';
6
5
 
7
6
  /**
8
7
  * Date display helpers in the board language (required leading parameter,
@@ -10,8 +9,12 @@ import './_spec-nkWAraS0.mjs';
10
9
  * tested against the hosted behavior.
11
10
  *
12
11
  * Job cards and the job-detail header render the RELATIVE form
13
- * (`formatPublishedRelativeDate` — "5d ago"); the absolute medium date
12
+ * (`formatPublishedRelativeDate` — "5 days ago"); the absolute medium date
14
13
  * (`formatDate`) is what hosted uses for blog metadata and detail facts.
14
+ *
15
+ * Invalid locales return `null` rather than English unit abbreviations
16
+ * (`5d` / `5mo`) or a sign-stripped digit string. Past vs future is kept
17
+ * (negative → past) so RTF can own the word.
15
18
  */
16
19
  /**
17
20
  * Absolute medium date in the board language, UTC-pinned — e.g.
@@ -21,229 +24,28 @@ import './_spec-nkWAraS0.mjs';
21
24
  */
22
25
  declare function formatDate(locale: string, value: string | number | null): string | null;
23
26
  /**
24
- * Relative published label — "5d ago" (en) / "vor 5 T." (de) — the form the
25
- * hosted board renders on job cards and the job-detail header. Under one
26
- * minute renders "now". `referenceNowMs` exists for deterministic tests.
27
+ * Relative published label — "5 days ago" (en) / "vor 5 Tagen" (de) — the
28
+ * form the hosted board renders on job cards and the job-detail header.
29
+ * Under one minute uses `Intl.RelativeTimeFormat` with `numeric: 'auto'` so
30
+ * the board language owns "now" / "jetzt" / "maintenant". `referenceNowMs`
31
+ * exists for deterministic tests.
27
32
  */
28
33
  declare function formatPublishedRelativeDate(locale: string, value: string | number | null, referenceNowMs?: number): string | null;
29
34
  /**
30
35
  * Month-year label, e.g. "Jun 2023" — transcribed from the hosted board's
31
- * `formatMonthYear`. The `T00:00:00` suffix
32
- * prevents timezone-related off-by-one-month issues on date-only strings.
33
- */
34
- declare function formatMonthYear(locale: string, dateStr: string): string;
35
-
36
- /**
37
- * Adapt a full `PublicJob` (e.g. an embedded saved job) to the slim
38
- * `PublicJobCard` shape a list card renders — computing the card's
39
- * pre-resolved `locationLabel`/`remoteLocationLabel` from the full job's
40
- * fields. Board language is required because the computed location label is
41
- * display text.
42
- */
43
- declare function fullJobToCard(locale: string, job: PublicJob): PublicJobCard;
44
-
45
- /**
46
- * Display label for a job enum value (`remoteOption`, `employmentType`,
47
- * `seniority`) in the board language, ⊕ the board's stored operator
48
- * overrides when the `labels` bag is passed. Unknown values pass through
49
- * unchanged (enums grow additively within v1); unseeded languages fall back
50
- * to the English source vocabulary.
36
+ * `formatMonthYear`.
51
37
  *
52
- * @example
53
- * fieldLabel('en', 'mid_level'); // "Mid-level"
54
- * fieldLabel('de', 'executive'); // "Führungskraft"
55
- * fieldLabel('de', 'full_time'); // "Vollzeit"
56
- * fieldLabel('de', 'full_time', board.labels); // operator override when set
57
- */
58
- declare function fieldLabel(locale: string, value: string | null, labels?: BoardLabelOverrides): string | null;
59
-
60
- /**
61
- * Location display labels for job cards and detail pages, in the board
62
- * language. The wrapper words
63
- * ("Remote", "(hybrid)", "Worldwide") follow the hosted board's current
64
- * English-only rendering; place names arrive already board-language from the
65
- * wire (the API serves canonical, localized labels).
66
- */
67
- /**
68
- * Location label for the full `PublicJob` (detail pages, embedded saved
69
- * jobs): first office location's display name, remote/hybrid wrapping per
70
- * `remoteOption`.
71
- */
72
- declare function locationLabel(locale: string, job: PublicJob): string;
73
- /**
74
- * Location label for a list CARD. The card read-model pre-computes
75
- * `locationLabel` and (for remote jobs) `remoteLocationLabel` server-side —
76
- * the slim card carries no `officeLocations`/`remoteWorldwide` — so those
77
- * wire fields are used directly, matching what the hosted card renders.
78
- */
79
- declare function cardLocationLabel(locale: string, job: Pick<PublicJobCard, 'remoteOption' | 'remoteLocationLabel' | 'locationLabel'>): string;
80
-
81
- /**
82
- * The closed, board-language salary LEXICON — the fixed salary words:
83
- * timeframe labels + range prefixes, seniority labels, and metadata
84
- * `<title>`/`<description>` sentence FRAMES, keyed by the board language
85
- * (`board.context().language`), NOT by any visitor locale.
86
- *
87
- * Transcribed from the hosted board's `salary-lexicon.ts` and tested against it : the two
88
- * copies cannot drift silently. The `de` strings are native-reviewed.
89
- * Unseeded locales fall back to the English source WORDS while number and
90
- * currency formatting stays locale-correct.
91
- */
92
- /** Wire enum for `salaryTimeframe`. */
93
- type SalaryTimeframeValue = 'per_year' | 'per_month' | 'per_week' | 'per_day' | 'per_hour';
94
- type SeniorityKey = 'entry_level' | 'associate' | 'mid_level' | 'senior' | 'lead' | 'principal' | 'director' | 'executive';
95
- /**
96
- * Board-language builders for the salary-page metadata sentence frames. Each
97
- * receives already-formatted parts (entity name, board-language `range`/amount
98
- * strings, counts) and returns one localized sentence. The numbers are
99
- * formatted upstream (`formatSalaryRange`); the FRAME is the words.
100
- */
101
- interface SalaryFrames {
102
- entitySalariesTitle(a: {
103
- entity: string;
104
- range: string;
105
- }): string;
106
- salariesInPlaceTitle(a: {
107
- place: string;
108
- range: string;
109
- }): string;
110
- salariesInPlaceTitleNoRange(a: {
111
- place: string;
112
- }): string;
113
- entitySalariesInPlaceTitle(a: {
114
- entity: string;
115
- place: string;
116
- range: string;
117
- }): string;
118
- companySalariesTitle(a: {
119
- company: string;
120
- range: string | null;
121
- }): string;
122
- companyCategorySalariesTitle(a: {
123
- company: string;
124
- category: string;
125
- range: string | null;
126
- }): string;
127
- professionalsEarn(a: {
128
- entity: string;
129
- range: string;
130
- count: number;
131
- }): string;
132
- rolesPay(a: {
133
- entity: string;
134
- range: string;
135
- count: number;
136
- }): string;
137
- professionalsInLocationEarn(a: {
138
- entity: string;
139
- place: string;
140
- range: string;
141
- count: number;
142
- }): string;
143
- seniorityRange(a: {
144
- lowLabel: string;
145
- lowAmount: string;
146
- highLabel: string;
147
- highAmount: string;
148
- }): string;
149
- seniorityRangeWhile(a: {
150
- lowLabel: string;
151
- lowAmount: string;
152
- highLabel: string;
153
- highAmount: string;
154
- }): string;
155
- compareAcrossTopPaying(a: {
156
- count: number;
157
- }): string;
158
- compareAcrossTop(a: {
159
- count: number;
160
- }): string;
161
- boardWideAverage(a: {
162
- entity: string;
163
- range: string;
164
- }): string;
165
- averageSalaryInCity(a: {
166
- place: string;
167
- range: string;
168
- count: number;
169
- }): string;
170
- browseLocationsInRegion(a: {
171
- count: number;
172
- place: string;
173
- }): string;
174
- companyPays(a: {
175
- company: string;
176
- range: string;
177
- count: number;
178
- }): string;
179
- breakdownAcrossCategories(a: {
180
- count: number;
181
- }): string;
182
- compareWithSimilar(a: {
183
- count: number;
184
- }): string;
185
- salaryInfoFor(a: {
186
- company: string;
187
- board: string;
188
- }): string;
189
- categoryRolesAtCompanyPay(a: {
190
- category: string;
191
- company: string;
192
- range: string;
193
- count: number;
194
- }): string;
195
- categorySalaryDataFor(a: {
196
- category: string;
197
- company: string;
198
- board: string;
199
- }): string;
200
- }
201
- interface SalaryLexicon {
202
- /** Per-timeframe display word, e.g. `per_year` → "Yearly" / "pro Jahr". */
203
- timeframe: Record<SalaryTimeframeValue, string>;
204
- /** Open-ended range prefixes, e.g. "From" / "ab", "Up to" / "bis zu". */
205
- rangePrefix: {
206
- from: string;
207
- upTo: string;
208
- };
209
- /** Seniority-level labels, e.g. `senior` → "Senior". */
210
- seniority: Record<SeniorityKey, string>;
211
- /** Metadata `<title>`/`<description>` sentence builders. */
212
- frames: SalaryFrames;
213
- }
214
- /**
215
- * Resolve the salary lexicon for a board language; unseeded locales fall back
216
- * to the English source.
217
- */
218
- declare function getSalaryLexicon(locale: string | undefined): SalaryLexicon;
219
-
220
- type SalaryTimeframeInput = SalaryTimeframeValue | string | null | undefined;
221
- /** Owner-customized timeframe words (board settings) — from the board's SEO/labels config. */
222
- interface SalaryTimeframeOverrides {
223
- yearlyLabel?: string;
224
- monthlyLabel?: string;
225
- weeklyLabel?: string;
226
- dailyLabel?: string;
227
- hourlyLabel?: string;
228
- }
229
- /**
230
- * Format a per-job salary range in the BOARD language
231
- * (`board.context().language` — required, never defaulted; see ).
232
- * The fixed words (timeframe, "From"/"Up to") come from
233
- * `getSalaryLexicon(language)` with precedence owner-override > lexicon >
234
- * English source. Currency placement is gated: `en` keeps the legacy manual
235
- * symbol-before form (byte-identical to the hosted board); non-`en` uses
236
- * `Intl` `style:'currency'` so the symbol sits per locale.
237
- *
238
- * @example
239
- * formatSalaryRange('en', 90000, 120000, 'per_year', 'USD');
240
- * // "$90K – $120K Yearly"
241
- * formatSalaryRange('de', 90000, null, 'per_year', 'EUR');
242
- * // "ab 90.000 € pro Jahr"
243
- */
244
- declare function formatSalaryRange(language: string | undefined, min: number | null, max: number | null, timeframe: SalaryTimeframeInput, currency?: string | null, timeframeOverrides?: SalaryTimeframeOverrides): string | null;
245
-
246
- declare function companyIntro(summary: string | null, description: string | null): string | null;
38
+ * - Date-only inputs (`YYYY-MM`, `YYYY-MM-DD`) get a UTC midnight pin
39
+ * (`T00:00:00.000Z`) so the UTC formatter below keeps the same calendar
40
+ * month. Local midnight + `timeZone: 'UTC'` would shift western zones
41
+ * back a day (e.g. Jun 1 AEST → May 31 UTC).
42
+ * - Full ISO timestamps are formatted with `timeZone: 'UTC'` (same pin as
43
+ * `formatDate`) so a UTC midnight never renders as the previous month
44
+ * under America/Los_Angeles.
45
+ * - Missing or unparseable input returns `null`, matching `formatDate` —
46
+ * never the English literal `"Invalid Date"`.
47
+ */
48
+ declare function formatMonthYear(locale: string, dateStr: string | null | undefined): string | null;
247
49
 
248
50
  /**
249
51
  * ISO 3166-1 alpha-2 country lexicon — the `country` slice of the remote-
@@ -264,7 +66,9 @@ interface CountryOption {
264
66
  * The complete country option list, display-named in the BOARD language
265
67
  * and sorted by that language's
266
68
  * collation — the shape a country picker renders. Falls back to the bare
267
- * code when the runtime cannot resolve the language or name a region.
69
+ * code when the runtime cannot name a region. When the board language is
70
+ * invalid or unsupported (`normalizeLocale` → `null`), returns ISO codes
71
+ * as names sorted by code — never host-default English names/collation.
268
72
  */
269
73
  declare function countryOptions(language: string): CountryOption[];
270
74
 
@@ -275,9 +79,22 @@ declare function countryOptions(language: string): CountryOption[];
275
79
  * context) in config order, so display order is operator-controlled, orphan
276
80
  * values (deleted definitions) are never inspected, and empty values drop per
277
81
  * field. Select values resolve their stored option KEY to the current label
278
- * (rename-safe); an option with no current match is dropped. Booleans stay raw
279
- * (a discriminated `kind`) — the render layer authors the "Yes"/"No" chrome
280
- * (English; localized copy is the use-intl future release).
82
+ * (rename-safe); an option with no current match is dropped. Booleans and
83
+ * numbers stay raw (discriminated `kind`) — the render layer formats them
84
+ * with the board locale (`Intl.NumberFormat`, Yes/No chrome). Pre-stringifying
85
+ * numbers would ship `kind: 'text'` and foreclose locale-aware formatting.
86
+ *
87
+ * Multi-select stays an array of resolved labels (`kind: 'multi_select'`).
88
+ * Pre-joining with `Intl.ListFormat` would ship `kind: 'text'` and foreclose
89
+ * locale-aware **list** formatting (short style, disjunction, separate chips)
90
+ * for the same reason numbers must not be pre-stringified. The application
91
+ * joins when it wants a sentence:
92
+ * `new Intl.ListFormat(language, { style: 'long', type: 'conjunction' }).format(values)`.
93
+ *
94
+ * `language` remains the required leading parameter ( call-site
95
+ * pattern / signature stability). It is not read here — multi-select no
96
+ * longer joins in the SDK — but callers already pass `board.context().language`
97
+ * and removing it would be a delete from the published surface.
281
98
  */
282
99
  type CustomFieldDisplayEntry = {
283
100
  key: string;
@@ -289,7 +106,17 @@ type CustomFieldDisplayEntry = {
289
106
  label: string;
290
107
  kind: 'boolean';
291
108
  value: boolean;
109
+ } | {
110
+ key: string;
111
+ label: string;
112
+ kind: 'number';
113
+ value: number;
114
+ } | {
115
+ key: string;
116
+ label: string;
117
+ kind: 'multi_select';
118
+ values: string[];
292
119
  };
293
- declare function resolveCustomFieldDisplay(definitions: CustomFieldDefinition[] | undefined, values: CustomFieldValues | undefined): CustomFieldDisplayEntry[];
120
+ declare function resolveCustomFieldDisplay(language: string, definitions: CustomFieldDefinition[] | undefined, values: CustomFieldValues | undefined): CustomFieldDisplayEntry[];
294
121
 
295
- export { BoardLabelOverrides, COUNTRY_CODES, type CountryOption, type CustomFieldDisplayEntry, type IsoCountryCode, type SalaryFrames, type SalaryLexicon, type SalaryTimeframeInput, type SalaryTimeframeOverrides, type SalaryTimeframeValue, type SeniorityKey, cardLocationLabel, companyIntro, countryOptions, fieldLabel, formatDate, formatMonthYear, formatPublishedRelativeDate, formatSalaryRange, fullJobToCard, getSalaryLexicon, locationLabel, resolveCustomFieldDisplay };
122
+ export { COUNTRY_CODES, type CountryOption, type CustomFieldDisplayEntry, type IsoCountryCode, countryOptions, formatDate, formatMonthYear, formatPublishedRelativeDate, resolveCustomFieldDisplay };
package/dist/format.d.ts CHANGED
@@ -1,8 +1,7 @@
1
- import { P as PublicJob, a as PublicJobCard, C as CustomFieldValues } from './jobs-BiEvL9d7.js';
2
- import { B as BoardLabelOverrides } from './ui-copy-CCkz6CVF.js';
3
- export { A as AlertsCopy, a as ApplyCopy, b as BlogCopy, c as BreadcrumbsCopy, C as CopyLinkCopy, E as EntityCopy, F as FooterCopy, J as JobCardCopy, d as JobDetailCopy, e as JobSearchCopy, N as NavCopy, P as PUBLIC_LABEL_GROUPS, f as PaginationCopy, S as SalaryCopy, U as UiCopy, u as uiCopy } from './ui-copy-CCkz6CVF.js';
4
- import { C as CustomFieldDefinition } from './board-NE98PlwN.js';
5
- import './_spec-nkWAraS0.js';
1
+ export { C as CurrencyDisplay, F as FormattedSalaryRange, J as JobBreadcrumb, N as NumberNotation, S as SalaryTimeframeInput, a as SalaryTimeframeValue, b as buildJobBreadcrumbs, f as formatSalaryRange, c as formatSalaryStat, d as formatSalaryStatRange, n as normalizeWebsiteUrl } from './breadcrumbs-DiccJhk4.js';
2
+ import { C as CustomFieldDefinition } from './board-DUhgiacm.js';
3
+ import { C as CustomFieldValues } from './jobs-QrbXOYB9.js';
4
+ import './_spec-GVe6WAfT.js';
6
5
 
7
6
  /**
8
7
  * Date display helpers in the board language (required leading parameter,
@@ -10,8 +9,12 @@ import './_spec-nkWAraS0.js';
10
9
  * tested against the hosted behavior.
11
10
  *
12
11
  * Job cards and the job-detail header render the RELATIVE form
13
- * (`formatPublishedRelativeDate` — "5d ago"); the absolute medium date
12
+ * (`formatPublishedRelativeDate` — "5 days ago"); the absolute medium date
14
13
  * (`formatDate`) is what hosted uses for blog metadata and detail facts.
14
+ *
15
+ * Invalid locales return `null` rather than English unit abbreviations
16
+ * (`5d` / `5mo`) or a sign-stripped digit string. Past vs future is kept
17
+ * (negative → past) so RTF can own the word.
15
18
  */
16
19
  /**
17
20
  * Absolute medium date in the board language, UTC-pinned — e.g.
@@ -21,229 +24,28 @@ import './_spec-nkWAraS0.js';
21
24
  */
22
25
  declare function formatDate(locale: string, value: string | number | null): string | null;
23
26
  /**
24
- * Relative published label — "5d ago" (en) / "vor 5 T." (de) — the form the
25
- * hosted board renders on job cards and the job-detail header. Under one
26
- * minute renders "now". `referenceNowMs` exists for deterministic tests.
27
+ * Relative published label — "5 days ago" (en) / "vor 5 Tagen" (de) — the
28
+ * form the hosted board renders on job cards and the job-detail header.
29
+ * Under one minute uses `Intl.RelativeTimeFormat` with `numeric: 'auto'` so
30
+ * the board language owns "now" / "jetzt" / "maintenant". `referenceNowMs`
31
+ * exists for deterministic tests.
27
32
  */
28
33
  declare function formatPublishedRelativeDate(locale: string, value: string | number | null, referenceNowMs?: number): string | null;
29
34
  /**
30
35
  * Month-year label, e.g. "Jun 2023" — transcribed from the hosted board's
31
- * `formatMonthYear`. The `T00:00:00` suffix
32
- * prevents timezone-related off-by-one-month issues on date-only strings.
33
- */
34
- declare function formatMonthYear(locale: string, dateStr: string): string;
35
-
36
- /**
37
- * Adapt a full `PublicJob` (e.g. an embedded saved job) to the slim
38
- * `PublicJobCard` shape a list card renders — computing the card's
39
- * pre-resolved `locationLabel`/`remoteLocationLabel` from the full job's
40
- * fields. Board language is required because the computed location label is
41
- * display text.
42
- */
43
- declare function fullJobToCard(locale: string, job: PublicJob): PublicJobCard;
44
-
45
- /**
46
- * Display label for a job enum value (`remoteOption`, `employmentType`,
47
- * `seniority`) in the board language, ⊕ the board's stored operator
48
- * overrides when the `labels` bag is passed. Unknown values pass through
49
- * unchanged (enums grow additively within v1); unseeded languages fall back
50
- * to the English source vocabulary.
36
+ * `formatMonthYear`.
51
37
  *
52
- * @example
53
- * fieldLabel('en', 'mid_level'); // "Mid-level"
54
- * fieldLabel('de', 'executive'); // "Führungskraft"
55
- * fieldLabel('de', 'full_time'); // "Vollzeit"
56
- * fieldLabel('de', 'full_time', board.labels); // operator override when set
57
- */
58
- declare function fieldLabel(locale: string, value: string | null, labels?: BoardLabelOverrides): string | null;
59
-
60
- /**
61
- * Location display labels for job cards and detail pages, in the board
62
- * language. The wrapper words
63
- * ("Remote", "(hybrid)", "Worldwide") follow the hosted board's current
64
- * English-only rendering; place names arrive already board-language from the
65
- * wire (the API serves canonical, localized labels).
66
- */
67
- /**
68
- * Location label for the full `PublicJob` (detail pages, embedded saved
69
- * jobs): first office location's display name, remote/hybrid wrapping per
70
- * `remoteOption`.
71
- */
72
- declare function locationLabel(locale: string, job: PublicJob): string;
73
- /**
74
- * Location label for a list CARD. The card read-model pre-computes
75
- * `locationLabel` and (for remote jobs) `remoteLocationLabel` server-side —
76
- * the slim card carries no `officeLocations`/`remoteWorldwide` — so those
77
- * wire fields are used directly, matching what the hosted card renders.
78
- */
79
- declare function cardLocationLabel(locale: string, job: Pick<PublicJobCard, 'remoteOption' | 'remoteLocationLabel' | 'locationLabel'>): string;
80
-
81
- /**
82
- * The closed, board-language salary LEXICON — the fixed salary words:
83
- * timeframe labels + range prefixes, seniority labels, and metadata
84
- * `<title>`/`<description>` sentence FRAMES, keyed by the board language
85
- * (`board.context().language`), NOT by any visitor locale.
86
- *
87
- * Transcribed from the hosted board's `salary-lexicon.ts` and tested against it : the two
88
- * copies cannot drift silently. The `de` strings are native-reviewed.
89
- * Unseeded locales fall back to the English source WORDS while number and
90
- * currency formatting stays locale-correct.
91
- */
92
- /** Wire enum for `salaryTimeframe`. */
93
- type SalaryTimeframeValue = 'per_year' | 'per_month' | 'per_week' | 'per_day' | 'per_hour';
94
- type SeniorityKey = 'entry_level' | 'associate' | 'mid_level' | 'senior' | 'lead' | 'principal' | 'director' | 'executive';
95
- /**
96
- * Board-language builders for the salary-page metadata sentence frames. Each
97
- * receives already-formatted parts (entity name, board-language `range`/amount
98
- * strings, counts) and returns one localized sentence. The numbers are
99
- * formatted upstream (`formatSalaryRange`); the FRAME is the words.
100
- */
101
- interface SalaryFrames {
102
- entitySalariesTitle(a: {
103
- entity: string;
104
- range: string;
105
- }): string;
106
- salariesInPlaceTitle(a: {
107
- place: string;
108
- range: string;
109
- }): string;
110
- salariesInPlaceTitleNoRange(a: {
111
- place: string;
112
- }): string;
113
- entitySalariesInPlaceTitle(a: {
114
- entity: string;
115
- place: string;
116
- range: string;
117
- }): string;
118
- companySalariesTitle(a: {
119
- company: string;
120
- range: string | null;
121
- }): string;
122
- companyCategorySalariesTitle(a: {
123
- company: string;
124
- category: string;
125
- range: string | null;
126
- }): string;
127
- professionalsEarn(a: {
128
- entity: string;
129
- range: string;
130
- count: number;
131
- }): string;
132
- rolesPay(a: {
133
- entity: string;
134
- range: string;
135
- count: number;
136
- }): string;
137
- professionalsInLocationEarn(a: {
138
- entity: string;
139
- place: string;
140
- range: string;
141
- count: number;
142
- }): string;
143
- seniorityRange(a: {
144
- lowLabel: string;
145
- lowAmount: string;
146
- highLabel: string;
147
- highAmount: string;
148
- }): string;
149
- seniorityRangeWhile(a: {
150
- lowLabel: string;
151
- lowAmount: string;
152
- highLabel: string;
153
- highAmount: string;
154
- }): string;
155
- compareAcrossTopPaying(a: {
156
- count: number;
157
- }): string;
158
- compareAcrossTop(a: {
159
- count: number;
160
- }): string;
161
- boardWideAverage(a: {
162
- entity: string;
163
- range: string;
164
- }): string;
165
- averageSalaryInCity(a: {
166
- place: string;
167
- range: string;
168
- count: number;
169
- }): string;
170
- browseLocationsInRegion(a: {
171
- count: number;
172
- place: string;
173
- }): string;
174
- companyPays(a: {
175
- company: string;
176
- range: string;
177
- count: number;
178
- }): string;
179
- breakdownAcrossCategories(a: {
180
- count: number;
181
- }): string;
182
- compareWithSimilar(a: {
183
- count: number;
184
- }): string;
185
- salaryInfoFor(a: {
186
- company: string;
187
- board: string;
188
- }): string;
189
- categoryRolesAtCompanyPay(a: {
190
- category: string;
191
- company: string;
192
- range: string;
193
- count: number;
194
- }): string;
195
- categorySalaryDataFor(a: {
196
- category: string;
197
- company: string;
198
- board: string;
199
- }): string;
200
- }
201
- interface SalaryLexicon {
202
- /** Per-timeframe display word, e.g. `per_year` → "Yearly" / "pro Jahr". */
203
- timeframe: Record<SalaryTimeframeValue, string>;
204
- /** Open-ended range prefixes, e.g. "From" / "ab", "Up to" / "bis zu". */
205
- rangePrefix: {
206
- from: string;
207
- upTo: string;
208
- };
209
- /** Seniority-level labels, e.g. `senior` → "Senior". */
210
- seniority: Record<SeniorityKey, string>;
211
- /** Metadata `<title>`/`<description>` sentence builders. */
212
- frames: SalaryFrames;
213
- }
214
- /**
215
- * Resolve the salary lexicon for a board language; unseeded locales fall back
216
- * to the English source.
217
- */
218
- declare function getSalaryLexicon(locale: string | undefined): SalaryLexicon;
219
-
220
- type SalaryTimeframeInput = SalaryTimeframeValue | string | null | undefined;
221
- /** Owner-customized timeframe words (board settings) — from the board's SEO/labels config. */
222
- interface SalaryTimeframeOverrides {
223
- yearlyLabel?: string;
224
- monthlyLabel?: string;
225
- weeklyLabel?: string;
226
- dailyLabel?: string;
227
- hourlyLabel?: string;
228
- }
229
- /**
230
- * Format a per-job salary range in the BOARD language
231
- * (`board.context().language` — required, never defaulted; see ).
232
- * The fixed words (timeframe, "From"/"Up to") come from
233
- * `getSalaryLexicon(language)` with precedence owner-override > lexicon >
234
- * English source. Currency placement is gated: `en` keeps the legacy manual
235
- * symbol-before form (byte-identical to the hosted board); non-`en` uses
236
- * `Intl` `style:'currency'` so the symbol sits per locale.
237
- *
238
- * @example
239
- * formatSalaryRange('en', 90000, 120000, 'per_year', 'USD');
240
- * // "$90K – $120K Yearly"
241
- * formatSalaryRange('de', 90000, null, 'per_year', 'EUR');
242
- * // "ab 90.000 € pro Jahr"
243
- */
244
- declare function formatSalaryRange(language: string | undefined, min: number | null, max: number | null, timeframe: SalaryTimeframeInput, currency?: string | null, timeframeOverrides?: SalaryTimeframeOverrides): string | null;
245
-
246
- declare function companyIntro(summary: string | null, description: string | null): string | null;
38
+ * - Date-only inputs (`YYYY-MM`, `YYYY-MM-DD`) get a UTC midnight pin
39
+ * (`T00:00:00.000Z`) so the UTC formatter below keeps the same calendar
40
+ * month. Local midnight + `timeZone: 'UTC'` would shift western zones
41
+ * back a day (e.g. Jun 1 AEST → May 31 UTC).
42
+ * - Full ISO timestamps are formatted with `timeZone: 'UTC'` (same pin as
43
+ * `formatDate`) so a UTC midnight never renders as the previous month
44
+ * under America/Los_Angeles.
45
+ * - Missing or unparseable input returns `null`, matching `formatDate` —
46
+ * never the English literal `"Invalid Date"`.
47
+ */
48
+ declare function formatMonthYear(locale: string, dateStr: string | null | undefined): string | null;
247
49
 
248
50
  /**
249
51
  * ISO 3166-1 alpha-2 country lexicon — the `country` slice of the remote-
@@ -264,7 +66,9 @@ interface CountryOption {
264
66
  * The complete country option list, display-named in the BOARD language
265
67
  * and sorted by that language's
266
68
  * collation — the shape a country picker renders. Falls back to the bare
267
- * code when the runtime cannot resolve the language or name a region.
69
+ * code when the runtime cannot name a region. When the board language is
70
+ * invalid or unsupported (`normalizeLocale` → `null`), returns ISO codes
71
+ * as names sorted by code — never host-default English names/collation.
268
72
  */
269
73
  declare function countryOptions(language: string): CountryOption[];
270
74
 
@@ -275,9 +79,22 @@ declare function countryOptions(language: string): CountryOption[];
275
79
  * context) in config order, so display order is operator-controlled, orphan
276
80
  * values (deleted definitions) are never inspected, and empty values drop per
277
81
  * field. Select values resolve their stored option KEY to the current label
278
- * (rename-safe); an option with no current match is dropped. Booleans stay raw
279
- * (a discriminated `kind`) — the render layer authors the "Yes"/"No" chrome
280
- * (English; localized copy is the use-intl future release).
82
+ * (rename-safe); an option with no current match is dropped. Booleans and
83
+ * numbers stay raw (discriminated `kind`) — the render layer formats them
84
+ * with the board locale (`Intl.NumberFormat`, Yes/No chrome). Pre-stringifying
85
+ * numbers would ship `kind: 'text'` and foreclose locale-aware formatting.
86
+ *
87
+ * Multi-select stays an array of resolved labels (`kind: 'multi_select'`).
88
+ * Pre-joining with `Intl.ListFormat` would ship `kind: 'text'` and foreclose
89
+ * locale-aware **list** formatting (short style, disjunction, separate chips)
90
+ * for the same reason numbers must not be pre-stringified. The application
91
+ * joins when it wants a sentence:
92
+ * `new Intl.ListFormat(language, { style: 'long', type: 'conjunction' }).format(values)`.
93
+ *
94
+ * `language` remains the required leading parameter ( call-site
95
+ * pattern / signature stability). It is not read here — multi-select no
96
+ * longer joins in the SDK — but callers already pass `board.context().language`
97
+ * and removing it would be a delete from the published surface.
281
98
  */
282
99
  type CustomFieldDisplayEntry = {
283
100
  key: string;
@@ -289,7 +106,17 @@ type CustomFieldDisplayEntry = {
289
106
  label: string;
290
107
  kind: 'boolean';
291
108
  value: boolean;
109
+ } | {
110
+ key: string;
111
+ label: string;
112
+ kind: 'number';
113
+ value: number;
114
+ } | {
115
+ key: string;
116
+ label: string;
117
+ kind: 'multi_select';
118
+ values: string[];
292
119
  };
293
- declare function resolveCustomFieldDisplay(definitions: CustomFieldDefinition[] | undefined, values: CustomFieldValues | undefined): CustomFieldDisplayEntry[];
120
+ declare function resolveCustomFieldDisplay(language: string, definitions: CustomFieldDefinition[] | undefined, values: CustomFieldValues | undefined): CustomFieldDisplayEntry[];
294
121
 
295
- export { BoardLabelOverrides, COUNTRY_CODES, type CountryOption, type CustomFieldDisplayEntry, type IsoCountryCode, type SalaryFrames, type SalaryLexicon, type SalaryTimeframeInput, type SalaryTimeframeOverrides, type SalaryTimeframeValue, type SeniorityKey, cardLocationLabel, companyIntro, countryOptions, fieldLabel, formatDate, formatMonthYear, formatPublishedRelativeDate, formatSalaryRange, fullJobToCard, getSalaryLexicon, locationLabel, resolveCustomFieldDisplay };
122
+ export { COUNTRY_CODES, type CountryOption, type CustomFieldDisplayEntry, type IsoCountryCode, countryOptions, formatDate, formatMonthYear, formatPublishedRelativeDate, resolveCustomFieldDisplay };