@cavuno/board 3.2.0 → 4.1.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.
- package/CHANGELOG.md +433 -0
- package/README.md +6 -2
- package/dist/{_spec-nkWAraS0.d.mts → _spec-BZDv2dc5.d.mts} +100 -68
- package/dist/{_spec-nkWAraS0.d.ts → _spec-BZDv2dc5.d.ts} +100 -68
- package/dist/bin.mjs +7 -2
- package/dist/{blog-iV9XEIGf.d.ts → blog-BHx6iH3Z.d.ts} +1 -1
- package/dist/{blog-DGBHmExV.d.mts → blog-DyDvyQrI.d.mts} +1 -1
- package/dist/{board-JhdUWIoq.d.ts → board-BF3Qao6J.d.mts} +7 -6
- package/dist/{board-BJbXd_9_.d.mts → board-BTEZ0uiI.d.ts} +7 -6
- package/dist/breadcrumbs-SGaWth6C.d.ts +372 -0
- package/dist/breadcrumbs-icxRVydh.d.mts +372 -0
- package/dist/chunk-BGKNXG4H.mjs +146 -0
- package/dist/{chunk-NXY3OWGF.mjs → chunk-T2KC6XGV.mjs} +1 -1
- package/dist/{chunk-STE77MAT.mjs → chunk-YEAQ3JEI.mjs} +143 -134
- package/dist/doctor.js +1 -1
- package/dist/doctor.mjs +1 -1
- package/dist/filters.d.mts +25 -20
- package/dist/filters.d.ts +25 -20
- package/dist/filters.js +3 -692
- package/dist/filters.mjs +3 -29
- package/dist/format.d.mts +57 -231
- package/dist/format.d.ts +57 -231
- package/dist/format.js +327 -968
- package/dist/format.mjs +207 -264
- package/dist/go.js +11 -1
- package/dist/go.mjs +1 -1
- package/dist/index.d.mts +67 -76
- package/dist/index.d.ts +67 -76
- package/dist/index.js +45 -36
- package/dist/index.mjs +45 -36
- package/dist/{jobs-BiEvL9d7.d.ts → jobs-BX6d_sdd.d.mts} +4 -4
- package/dist/{jobs-lVvKCBCU.d.mts → jobs-BbLJfz1z.d.ts} +4 -4
- package/dist/paths.d.mts +89 -1
- package/dist/paths.d.ts +89 -1
- package/dist/paths.js +62 -14
- package/dist/paths.mjs +11 -3
- package/dist/route-contract.js +23 -13
- package/dist/route-contract.mjs +2 -2
- package/dist/{search-CCyFWPi3.d.mts → search-7B-mVRNA.d.ts} +14 -3
- package/dist/{search-CyRvVMUp.d.ts → search-OKrdAHv8.d.mts} +14 -3
- package/dist/seo.d.mts +30 -29
- package/dist/seo.d.ts +30 -29
- package/dist/seo.js +197 -718
- package/dist/seo.mjs +26 -24
- package/dist/server.d.mts +5 -5
- package/dist/server.d.ts +5 -5
- package/dist/sitemap.d.mts +5 -5
- package/dist/sitemap.d.ts +5 -5
- package/dist/sitemap.js +25 -14
- package/dist/sitemap.mjs +3 -2
- package/dist/skills.js +1 -1
- package/dist/skills.mjs +1 -1
- package/dist/suggest.d.mts +2 -2
- package/dist/suggest.d.ts +2 -2
- package/dist/suggest.js +17 -3
- package/dist/suggest.mjs +17 -3
- package/dist/well-known.js +23 -13
- package/dist/well-known.mjs +2 -2
- package/package.json +2 -4
- package/skills/cavuno-board-api-client/SKILL.md +42 -0
- package/skills/cavuno-board-applications/SKILL.md +4 -1
- package/skills/cavuno-board-filters/SKILL.md +11 -12
- package/skills/cavuno-board-format/SKILL.md +68 -56
- package/skills/cavuno-board-i18n/SKILL.md +102 -21
- package/skills/cavuno-board-search-suggestions/SKILL.md +8 -3
- package/skills/cavuno-board-seo/BLOG.md +4 -1
- package/skills/cavuno-board-seo/JOB_AND_BREADCRUMBS.md +5 -2
- package/skills/cavuno-board-seo/LISTING.md +35 -12
- package/skills/cavuno-board-seo/SALARY.md +83 -18
- package/skills/cavuno-board-seo/SKILL.md +3 -2
- package/skills/manifest.json +3 -3
- package/dist/breadcrumbs-1hMjcsqA.d.mts +0 -171
- package/dist/breadcrumbs-DexU07TT.d.ts +0 -171
- package/dist/chunk-IJCJ26SQ.mjs +0 -596
- package/dist/chunk-L7ZOYWHW.mjs +0 -98
- package/dist/chunk-LUGJWOT7.mjs +0 -96
- package/dist/ui-copy-CCkz6CVF.d.mts +0 -230
- package/dist/ui-copy-CCkz6CVF.d.ts +0 -230
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,439 @@
|
|
|
3
3
|
This changelog records changes that affect Board API compatibility, exported
|
|
4
4
|
types, runtime behavior, or supported integration patterns.
|
|
5
5
|
|
|
6
|
+
## 4.1.0 — 2026-08-06
|
|
7
|
+
|
|
8
|
+
- **Salary null-clears on job PATCH** (operator `PATCH /v1/jobs/:id` and
|
|
9
|
+
employer `PATCH /v1/boards/:identifier/me/companies/:slug/jobs/:id`):
|
|
10
|
+
`salaryMin`, `salaryMax`, `salaryCurrency`, and `salaryTimeframe` now
|
|
11
|
+
accept an explicit `null` meaning "clear the stored value" — the same
|
|
12
|
+
tri-state `applicationUrl` and `expiresAt` already document (value sets,
|
|
13
|
+
`null` clears, omitted leaves unchanged). Clearing also drops the derived
|
|
14
|
+
USD figures, so salary filters stop matching a job whose salary was
|
|
15
|
+
withdrawn.
|
|
16
|
+
- **Structured remote fields on `PublicJobCard`**: `remoteWorldwide`
|
|
17
|
+
(`boolean | null`) and `remoteWorkPermitCountryCodes` (`string[]`) beside
|
|
18
|
+
the pre-worded `remoteLocationLabel`, so templates can word remote
|
|
19
|
+
regions from their own catalogs instead of matching the board-language
|
|
20
|
+
`"Worldwide"` label string. Derived from the same permit selection the
|
|
21
|
+
label reads — the boolean and the label always agree.
|
|
22
|
+
- **Docs**: `Plan.name` and `Plan.description` are documented as authoring
|
|
23
|
+
defaults — the localized public string lives in the board template,
|
|
24
|
+
composed from `featureSummary`, `billingInterval`, and `talent`.
|
|
25
|
+
- **Marketing consent**: consent is a property of the board
|
|
26
|
+
user. New `board.me.marketingConsent` namespace — `retrieve()` (the
|
|
27
|
+
consent, or `null` when no decision exists), `grant()`, `withdraw()` —
|
|
28
|
+
plus the `MarketingConsent` type. The API records the decision, never
|
|
29
|
+
the prose: disclosure wording is authored by the board's own frontend.
|
|
30
|
+
- **Outbound webhooks**: webhook subscription endpoints and event types
|
|
31
|
+
join the OpenAPI contract and generated types.
|
|
32
|
+
|
|
33
|
+
## 4.0.0 — 2026-08-03
|
|
34
|
+
|
|
35
|
+
- **Lossy seams:** Resolution is fine;
|
|
36
|
+
discarding the input is not. After every call the application must
|
|
37
|
+
still be able to produce a different legitimate rendering.
|
|
38
|
+
- `resolveCustomFieldDisplay` multi-select returns
|
|
39
|
+
`{ kind: 'multi_select', values: string[] }` instead of a
|
|
40
|
+
pre-joined `kind: 'text'` sentence. `Intl.ListFormat` (or chips /
|
|
41
|
+
short style / disjunction) moves to the app — same reason numbers
|
|
42
|
+
stay raw. Leading `language` stays for signature stability; the SDK no longer reads it for multi-select.
|
|
43
|
+
- `buildSalaryFaq` average entries carry `avgMin`, `avgMax`, and
|
|
44
|
+
`currency` alongside the compact `range` convenience. FAQ prose can
|
|
45
|
+
reformat with `notation: 'standard'` without re-threading the detail
|
|
46
|
+
object. `label` is still echoed so FAQ mapping is a pure map over
|
|
47
|
+
entries (the SDK never reads it for formatting).
|
|
48
|
+
- `formatSalaryStat` / `formatSalaryStatRange` accept optional
|
|
49
|
+
`notation` with the same magnitude default as `formatSalaryRange`
|
|
50
|
+
(`|value| ≥ 1000` → compact; smaller → standard). `$90K` vs
|
|
51
|
+
`$90,000` is register, not locale.
|
|
52
|
+
- : remove `ListFormat` from "what the SDK keeps"; add the
|
|
53
|
+
mechanical recoverability test and worked table; document Google
|
|
54
|
+
controlled vocabularies (`EDUCATION_TO_CREDENTIAL`,
|
|
55
|
+
`EMPLOYMENT_TYPE_TO_GOOGLE`) as wire values, not copy; correct that
|
|
56
|
+
timeframe units ship as the wire enum, not unit words from `Intl`.
|
|
57
|
+
|
|
58
|
+
- **Pre-publish blockers (salary format + SEO paths):** Closes the final
|
|
59
|
+
review that blocked publish.
|
|
60
|
+
- `formatSalaryStat` / `formatSalaryStatRange` null-guard currency
|
|
61
|
+
(`currency?.trim()`) so a runtime `null` returns `null` instead of
|
|
62
|
+
`TypeError` — matches `formatSalaryRange` and the skill/changelog
|
|
63
|
+
contract (`string | null`).
|
|
64
|
+
- Non-finite amounts (`NaN`, `±Infinity`) return `null` across
|
|
65
|
+
`formatSalaryStat`, `formatSalaryStatRange`, and `formatSalaryRange`
|
|
66
|
+
(they are `number` at the type level and slip past `== null`).
|
|
67
|
+
- Identical range ends (`min === max`) format as a fixed salary
|
|
68
|
+
(single amount), not ICU's `approximatelySign` form (`~$140K`). A
|
|
69
|
+
posted pin is not a degenerate range.
|
|
70
|
+
- `formatSalaryRange` defaults `notation` by **magnitude**: compact
|
|
71
|
+
only where ICU shortens (`|value| ≥ 1000`); smaller values stay
|
|
72
|
+
`standard` so hourly/day rates keep CLDR minor units (`$22.50`,
|
|
73
|
+
`KWD 22.567`). Explicit `notation` still wins.
|
|
74
|
+
- `listingJsonLd` builds job URLs via `jobDetailPath` / `boardUrl`
|
|
75
|
+
(drops company-less jobs rather than linking `/jobs/{slug}`); omits
|
|
76
|
+
`BreadcrumbList` when the trail has fewer than 2 crumbs.
|
|
77
|
+
`buildJobBreadcrumbs` uses `paths/` helpers instead of hand-built
|
|
78
|
+
strings.
|
|
79
|
+
- `companySalaryJsonLd` / `locationSalaryJsonLd` accept omitted
|
|
80
|
+
`options` and return `null` (siblings already did; these threw).
|
|
81
|
+
|
|
82
|
+
- **i18n audit (format / paths / skills):** Closes the specialist audit
|
|
83
|
+
against ICU/CLDR/W3C practice. Architecture (discriminant seams, no
|
|
84
|
+
composition, `Intl`-over-catalogs) stands; defects were concentrated in
|
|
85
|
+
timezone pinning, CLDR width choice, and structure-vs-support locale
|
|
86
|
+
validation.
|
|
87
|
+
- `formatMonthYear` pins `timeZone: 'UTC'` (and date-only inputs as UTC
|
|
88
|
+
midnight) so full ISO timestamps agree with `formatDate` under every
|
|
89
|
+
host zone — no more `"Mai 2023"` / `"Dec 2022"` for June / Jan UTC.
|
|
90
|
+
- `formatPublishedRelativeDate` uses RTF `style: 'short'` (not
|
|
91
|
+
`narrow`). English reads `"5 days ago"`; French / Russian / Romanian
|
|
92
|
+
no longer degrade to signed numbers (`-5 j`).
|
|
93
|
+
- `normalizeLocale` requires `Intl.NumberFormat.supportedLocalesOf` —
|
|
94
|
+
well-formed but unsupported tags (`xx`, `qqq`, `und`, …) return
|
|
95
|
+
`null` instead of silently using the host default. Contract in
|
|
96
|
+
`locale.ts`, `salary-range.ts`, and `skills/cavuno-board-format` is
|
|
97
|
+
now enforced.
|
|
98
|
+
- Currency fraction digits: compact notation keeps a one-digit cap
|
|
99
|
+
(documented); `notation: 'standard'` (optional override on
|
|
100
|
+
`formatSalaryRange`) lets each currency's CLDR minor units win.
|
|
101
|
+
Default notation is magnitude-selected (see pre-publish blockers
|
|
102
|
+
above), not unconditional compact.
|
|
103
|
+
- `resolveCustomFieldDisplay` emits `kind: 'number'` with the raw
|
|
104
|
+
value (not pre-stringified `kind: 'text'`), so the app formats with
|
|
105
|
+
`Intl.NumberFormat`.
|
|
106
|
+
- Path helpers percent-encode dynamic segments (non-ASCII board-language
|
|
107
|
+
slugs become URIs for sitemap `<loc>`, canonical, email). `:` is kept
|
|
108
|
+
for route-contract templates; already-encoded input is not
|
|
109
|
+
double-encoded.
|
|
110
|
+
- Filter / suggest Set keys NFC-normalize; suggest `minChars` counts
|
|
111
|
+
graphemes (`Intl.Segmenter`), so a single CJK character qualifies.
|
|
112
|
+
- `skills/cavuno-board-i18n` carries the five obligations
|
|
113
|
+
(plural selection, gender, bidi isolation with FSI/PDI, grapheme
|
|
114
|
+
truncation, locale-aware case) and extends the completion gate.
|
|
115
|
+
Salary-range / format skills document bidi isolation at the join
|
|
116
|
+
site rather than returning `{ value, dir }`.
|
|
117
|
+
|
|
118
|
+
- **Rule (word order):** The SDK never decides what goes next to what.
|
|
119
|
+
"No hardcoded strings" is the easy half. Word order is the half that
|
|
120
|
+
survives a grep, because every operand is data and only the
|
|
121
|
+
*arrangement* is English. A template with two data holes and a space
|
|
122
|
+
(or `|`, or `/`) between them is an English sentence with the words
|
|
123
|
+
removed. The SDK formats with `Intl`, returns structure, and leaves
|
|
124
|
+
composition — counters, particles, separators, prefix vs postfix — to
|
|
125
|
+
the application. Invalid locales prefer `null` over English-arranged
|
|
126
|
+
fallbacks; underscore tags (`ja_JP`) normalize to BCP-47 (`ja-JP`) so
|
|
127
|
+
one typo does not drop every formatter into English at once.
|
|
128
|
+
|
|
129
|
+
- **Breaking:** `listingHead` requires a caller-supplied `title` (same
|
|
130
|
+
ownership as `description`). Removed `heading`, `boardName`, `count`,
|
|
131
|
+
and `language` from `ListingHeadOptions` — the SDK no longer composes
|
|
132
|
+
`` `${count} ${heading} | ${boardName}` `` (Japanese wants
|
|
133
|
+
`1,225件の求人`, not `1,225 求人 | …`). Pass the finished title from
|
|
134
|
+
the board's message catalog. Also removes the only unguarded
|
|
135
|
+
`Intl.NumberFormat` construction in the SDK (a malformed board
|
|
136
|
+
language no longer crashes the listing page).
|
|
137
|
+
|
|
138
|
+
- **Breaking:** `formatSalaryRange` returns
|
|
139
|
+
`{ text, timeframe, bound } | null`. `text` is the `Intl`-formatted
|
|
140
|
+
amount/range only; `timeframe` is the **wire enum** (`per_year`,
|
|
141
|
+
`per_hour`, …) — never a unit word from `Intl` and never an owner
|
|
142
|
+
override. Applications map the enum through their own catalog and
|
|
143
|
+
compose amount↔timeframe order (`$90K / year` vs `年収900万〜1200万円`).
|
|
144
|
+
Missing currency, invalid locale, or rejected currency format →
|
|
145
|
+
`null` (no bare number, no English `M`/`k`/`$`).
|
|
146
|
+
|
|
147
|
+
- **Breaking:** Removed `SalaryTimeframeOverrides` from
|
|
148
|
+
`@cavuno/board/format`. Owner-edited unit labels are application-side
|
|
149
|
+
copy keyed off the wire `timeframe` enum the helper returns; the SDK
|
|
150
|
+
no longer accepts an override map.
|
|
151
|
+
|
|
152
|
+
- **Breaking:** `GET/POST …/me/saved-jobs` embeds a slim `job_card` (the
|
|
153
|
+
same shape as jobs list/search), not a full `public_job` with HTML
|
|
154
|
+
`description`. Fetch the job detail by slug when the full body is
|
|
155
|
+
needed. `remoteLocationLabel` is resolved server-side from
|
|
156
|
+
`remoteRules`, so region-limited remote jobs keep their real region
|
|
157
|
+
label instead of a client-side lossy guess.
|
|
158
|
+
|
|
159
|
+
- **Breaking:** Removed `fullJobToCard` from `@cavuno/board/format`. The
|
|
160
|
+
saved-jobs endpoint now serves cards; convert with the same card VM as
|
|
161
|
+
listings (`toJobCardVM(saved.job, …)`). That helper was the last
|
|
162
|
+
user-facing hardcoded English in the SDK (`Remote`, `Worldwide`,
|
|
163
|
+
`(hybrid)`).
|
|
164
|
+
|
|
165
|
+
- **Breaking:** `formatSalaryRange`'s board language is required — no
|
|
166
|
+
silent `en` default. `formatPublishedRelativeDate` under one minute uses
|
|
167
|
+
`Intl.RelativeTimeFormat` (`"now"` / `"jetzt"` / `"maintenant"`), not a
|
|
168
|
+
hardcoded English string. `resolveCustomFieldDisplay` takes the board
|
|
169
|
+
language as its leading parameter (signature stability). Multi-select
|
|
170
|
+
originally joined via `Intl.ListFormat`; that was lossy and now returns
|
|
171
|
+
`kind: 'multi_select'` with `values: string[]` (see Lossy seams above).
|
|
172
|
+
|
|
173
|
+
- **Breaking:** Renamed and currency-aware salary-stat formatters:
|
|
174
|
+
`formatUsd` / `formatRange` → `formatSalaryStat(locale, value, currency)` /
|
|
175
|
+
`formatSalaryStatRange(locale, min, max, currency)`. The `/format`
|
|
176
|
+
aliases `formatSalaryStatUsd` / `formatSalaryStatRange` are replaced by
|
|
177
|
+
the same names (no USD-only alias). `buildSalaryFaq` takes `currency`
|
|
178
|
+
as a fourth argument so FAQ ranges match the board's money. Both formatters
|
|
179
|
+
return `string | null` (`null` when currency is empty or `Intl` rejects
|
|
180
|
+
the locale/currency) instead of inventing English `M`/`k` or a bare `$`.
|
|
181
|
+
|
|
182
|
+
- **Breaking:** `formatSalaryRange` first moved from `string | null` to
|
|
183
|
+
`{ text; bound } | null` so open floors/ceilings are named by the app;
|
|
184
|
+
the timeframe is now a third field (see top of this heading) rather than
|
|
185
|
+
a suffix glued onto `text`.
|
|
186
|
+
|
|
187
|
+
- **Breaking:** Unit, currency, and date *words* derive from `Intl`;
|
|
188
|
+
`skills/` snippets show shape (catalog placeholders), not pasteable
|
|
189
|
+
English copy or join templates. The SDK formats with `Intl` and returns
|
|
190
|
+
structure; it does not supply open-range chrome, nav/button copy, filter
|
|
191
|
+
labels, SEO sentence templates, or amount↔unit arrangement. Dropped the
|
|
192
|
+
private chrome-copy package dependency entirely. Closed ranges use
|
|
193
|
+
`Intl.NumberFormat.prototype.formatRange` (locale owns separator,
|
|
194
|
+
repeated currency/magnitude, and bidi marks). Open-range chrome and
|
|
195
|
+
amount↔timeframe order are application-owned via `bound` + `timeframe`.
|
|
196
|
+
`buildJobBreadcrumbs` no longer takes `language` or `labels` parameters —
|
|
197
|
+
it returns structural `kind`s for chrome crumbs (`home` / `jobs`) instead
|
|
198
|
+
of display names; applications supply the words when mapping to
|
|
199
|
+
`BreadcrumbList`. `seniorityLabels` / `sortLabels` / `formatSeniority`
|
|
200
|
+
are removed. Type exports removed with the chrome surface:
|
|
201
|
+
`AlertsCopy`, `ApplyCopy`, `BlogCopy`, `BoardLabelOverrides`,
|
|
202
|
+
`BreadcrumbsCopy`, `CopyLinkCopy`, `EntityCopy`, `FooterCopy`,
|
|
203
|
+
`JobCardCopy`, `JobDetailCopy`, `JobSearchCopy`, `NavCopy`,
|
|
204
|
+
`PaginationCopy`, `SalaryCopy`, `SalaryFrames`, `SalaryLexicon`,
|
|
205
|
+
`SeniorityKey`, `UiCopy`.
|
|
206
|
+
|
|
207
|
+
- **Breaking:** `listingHead` requires a caller-supplied `description` (and
|
|
208
|
+
now `title` — see top of this heading). It no longer composes
|
|
209
|
+
`Browse … on …` (English on every locale) or case-folds the heading with
|
|
210
|
+
locale-blind `.toLowerCase()`.
|
|
211
|
+
|
|
212
|
+
- **Breaking:** `buildSalaryFaq` returns structured
|
|
213
|
+
`SalaryFaqEntry` values (`kind: 'average' | 'methodology'` plus label,
|
|
214
|
+
`formatRange` range, and `jobCount`) instead of English FAQ prose.
|
|
215
|
+
Map entries to `{ q, a }` in the application (with proper plural rules),
|
|
216
|
+
then pass them to `faqJsonLd`.
|
|
217
|
+
|
|
218
|
+
- **Breaking:** Salary JSON-LD builders
|
|
219
|
+
(`titleSalaryJsonLd`, `skillSalaryJsonLd`, `locationSalaryJsonLd`,
|
|
220
|
+
`crossAxisSalaryJsonLd`, `companySalaryJsonLd`,
|
|
221
|
+
`companyCategorySalaryJsonLd`) use data-only `name` fields (entity
|
|
222
|
+
names). Removed English framing
|
|
223
|
+
(`… salary (all levels)`, `Average Salary in …`, `Jobs at …`,
|
|
224
|
+
`… salary in …`, trailing ` salary`).
|
|
225
|
+
|
|
226
|
+
- **Breaking:** Dropped the dead `locale` parameter from
|
|
227
|
+
`titleSalaryJsonLd`, `crossAxisSalaryJsonLd`, `companySalaryJsonLd`,
|
|
228
|
+
and `companyCategorySalaryJsonLd` — it was discarded (`void locale`)
|
|
229
|
+
and did not change output. Per-seniority distribution names are no
|
|
230
|
+
longer title-cased wire enums (`entry_level` → `Entry level`) and are
|
|
231
|
+
no longer joined as `` `${seniority} ${entity}` `` (English word order).
|
|
232
|
+
Pass optional `seniorityName({ seniority, entity })` that returns the
|
|
233
|
+
finished string in board-language order, or omit it so those
|
|
234
|
+
distributions ship without a `name` (valid structured data;
|
|
235
|
+
wrong-language / wrong-order names are not).
|
|
236
|
+
|
|
237
|
+
- **Breaking:** `formatSalaryRange` never invents USD when
|
|
238
|
+
`salaryCurrency` is null/empty — returns `null`, matching
|
|
239
|
+
`createJobPostingJsonLd` which omits `baseSalary` without a currency.
|
|
240
|
+
Invalid locales and rejected currency formats also return `null` rather
|
|
241
|
+
than English `M`/`k`/`$` or a bare number.
|
|
242
|
+
|
|
243
|
+
- **Breaking:** `formatMonthYear` returns `string | null` — `null` on
|
|
244
|
+
missing/unparseable input (never the English literal `"Invalid Date"`),
|
|
245
|
+
and accepts full ISO timestamps in addition to date-only forms.
|
|
246
|
+
|
|
247
|
+
- **Breaking:** `locationSalaryJsonLd` and `companySalaryJsonLd` no
|
|
248
|
+
longer emit an `Occupation` / `OccupationAggregationByEmployer`
|
|
249
|
+
envelope with the place or employer as `name` (neither is an
|
|
250
|
+
occupation). They emit an `ItemList` of the occupations present on
|
|
251
|
+
that page (`topCategories` / `byCategory`), each linking to its own
|
|
252
|
+
occupation page via required `occupationUrl`. Return `null` when
|
|
253
|
+
there are no per-occupation rows (or when the location is not
|
|
254
|
+
city/locality). The other four builders keep `Occupation`.
|
|
255
|
+
|
|
256
|
+
- **Breaking:** `board.taxonomy.suggestions.list` and
|
|
257
|
+
`GET /v1/boards/{identifier}/suggestions` are removed. Use
|
|
258
|
+
`board.search.suggest({ types: ['category', 'skill'], … })` instead. The
|
|
259
|
+
`taxonomy` namespace itself stays (`categories`, `skills`, `places`,
|
|
260
|
+
`remotePermits`, resolve methods).
|
|
261
|
+
|
|
262
|
+
- `board.search.suggest` accepts
|
|
263
|
+
`types?: ('company' | 'category' | 'skill' | 'market' | 'post' | 'tag')[]`.
|
|
264
|
+
Omit for every kind. `limit` applies **after** type filtering, so
|
|
265
|
+
`{ types: ['skill'], limit: 10 }` returns up to ten skills.
|
|
266
|
+
|
|
267
|
+
- `SuggestionItem` gains a `market` variant (`type: 'market'`, `slug`, `name`,
|
|
268
|
+
`companyCount`) for company-scope typeahead alongside companies.
|
|
269
|
+
|
|
270
|
+
- `SuggestionItem` gains `post` (`type: 'post'`, `id`, `slug`, `title`) and
|
|
271
|
+
`tag` (`type: 'tag'`, `id`, `slug`, `name`) for blog typeahead. Blog posts
|
|
272
|
+
and tags are public, so no extra access control.
|
|
273
|
+
|
|
274
|
+
- `@cavuno/board/paths` adds `suggestionPath(suggestion, { scope, location? })`
|
|
275
|
+
plus `jobsLocationCategoryPath` / `jobsLocationSkillPath`. A jobs-scope
|
|
276
|
+
company suggestion returns `null` — apply a `companySlug` filter; do not
|
|
277
|
+
invent `/jobs/companies/<slug>`. Blog scope routes posts via
|
|
278
|
+
`blogPostPath` and tags via `blogTagPath`.
|
|
279
|
+
|
|
280
|
+
- Added `sdk/reference/search.mdoc` and a docs-contract gate that every
|
|
281
|
+
`BoardSdk` namespace has a matching reference page.
|
|
282
|
+
|
|
283
|
+
- The sitemap no longer advertises `/talent` on boards whose talent directory
|
|
284
|
+
is `employers_only`. That page is gated behind an approved employer session,
|
|
285
|
+
so listing it pointed crawlers at the gate rather than the directory. Only a
|
|
286
|
+
`public` directory is listed.
|
|
287
|
+
|
|
288
|
+
- **Breaking:** `board.context().customFields` is now keyed by model instead of
|
|
289
|
+
a flat job-only array. Today only `job` is present; `company` / `talent` keys
|
|
290
|
+
land when those models ship (absent keys are not empty arrays).
|
|
291
|
+
|
|
292
|
+
Before:
|
|
293
|
+
|
|
294
|
+
```json
|
|
295
|
+
"customFields": [
|
|
296
|
+
{ "key": "security_clearance", "label": "Security clearance", "type": "single_select", "required": false }
|
|
297
|
+
]
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
After:
|
|
301
|
+
|
|
302
|
+
```json
|
|
303
|
+
"customFields": {
|
|
304
|
+
"job": [
|
|
305
|
+
{ "key": "security_clearance", "label": "Security clearance", "type": "single_select", "required": false }
|
|
306
|
+
]
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Resolve job values with `resolveCustomFieldDisplay(context.customFields.job, job.customFieldValues)`
|
|
311
|
+
— the helper still takes a definitions array and did not change.
|
|
312
|
+
|
|
313
|
+
- **Breaking:** `features.talentDirectory` is now the tri-state access mode
|
|
314
|
+
(`off` | `public` | `employers_only`). The top-level
|
|
315
|
+
`talentDirectoryVisibility` field is removed — it duplicated the same gate.
|
|
316
|
+
|
|
317
|
+
Before:
|
|
318
|
+
|
|
319
|
+
```json
|
|
320
|
+
"features": { "talentDirectory": true },
|
|
321
|
+
"talentDirectoryVisibility": "employers_only"
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
After:
|
|
325
|
+
|
|
326
|
+
```json
|
|
327
|
+
"features": { "talentDirectory": "employers_only" }
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Link `/talent` whenever `features.talentDirectory !== 'off'`. Do not treat
|
|
331
|
+
the field as a boolean — the string `'off'` is truthy in JavaScript.
|
|
332
|
+
|
|
333
|
+
- **Breaking:** `board.seo()` no longer returns presentation. Removed
|
|
334
|
+
`icons` (ico/svg/appleTouch/icon192/icon512/iconMaskable512) and
|
|
335
|
+
`manifest.themeColor`. Kept `canonicalBase`, `adsTxt`, `indexNowKey`,
|
|
336
|
+
`googleSiteVerification`, and `manifest.name`. Applications own brand
|
|
337
|
+
assets and theme tokens.
|
|
338
|
+
|
|
339
|
+
Before:
|
|
340
|
+
|
|
341
|
+
```json
|
|
342
|
+
{
|
|
343
|
+
"canonicalBase": "https://jobs.example.com",
|
|
344
|
+
"adsTxt": null,
|
|
345
|
+
"indexNowKey": null,
|
|
346
|
+
"googleSiteVerification": null,
|
|
347
|
+
"icons": { "ico": "…", "svg": null, "appleTouch": null, "icon192": null, "icon512": null, "iconMaskable512": null },
|
|
348
|
+
"manifest": { "name": "Acme Jobs", "themeColor": "#1d4ed8" }
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
After:
|
|
353
|
+
|
|
354
|
+
```json
|
|
355
|
+
{
|
|
356
|
+
"canonicalBase": "https://jobs.example.com",
|
|
357
|
+
"adsTxt": null,
|
|
358
|
+
"indexNowKey": null,
|
|
359
|
+
"googleSiteVerification": null,
|
|
360
|
+
"manifest": { "name": "Acme Jobs" }
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
- **Breaking:** `board.context().footer` is gone. Operator-authored contact
|
|
365
|
+
and social identity move to a top-level `contact` group; editor-authored
|
|
366
|
+
presentation (`description`, `navigationOrder`, `customLinks`) is removed
|
|
367
|
+
— applications own footer layout.
|
|
368
|
+
|
|
369
|
+
Before:
|
|
370
|
+
|
|
371
|
+
```json
|
|
372
|
+
"footer": {
|
|
373
|
+
"description": "Roles from {{board_name}}.",
|
|
374
|
+
"contactEmail": "hello@acme.com",
|
|
375
|
+
"websiteUrl": "https://acme.com/",
|
|
376
|
+
"xUrl": "https://x.com/acmejobs",
|
|
377
|
+
"facebookUrl": null,
|
|
378
|
+
"linkedinUrl": "https://linkedin.com/company/acme",
|
|
379
|
+
"navigationOrder": ["home", "blog"],
|
|
380
|
+
"customLinks": [{ "id": "abc", "label": "Hub", "url": "/hub" }]
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
After:
|
|
385
|
+
|
|
386
|
+
```json
|
|
387
|
+
"contact": {
|
|
388
|
+
"email": "hello@acme.com",
|
|
389
|
+
"websiteUrl": "https://acme.com/",
|
|
390
|
+
"xUrl": "https://x.com/acmejobs",
|
|
391
|
+
"facebookUrl": null,
|
|
392
|
+
"linkedinUrl": "https://linkedin.com/company/acme"
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Brand/social URL sanitization is unchanged (absolute http(s) only).
|
|
397
|
+
`companyLegalName` / `companyAddress` were never on this public context
|
|
398
|
+
payload (they ride the legal/impressum surface); no `company` group was
|
|
399
|
+
invented here.
|
|
400
|
+
|
|
401
|
+
- **Breaking:** Chrome copy leaves the public `@cavuno/board` surface. Removed from `@cavuno/board/format`: `uiCopy`,
|
|
402
|
+
`PUBLIC_LABEL_GROUPS`, `fieldLabel`, `getSalaryLexicon`, `locationLabel`,
|
|
403
|
+
`cardLocationLabel`, `companyIntro`, and the `UiCopy` type re-export.
|
|
404
|
+
Removed from `@cavuno/board/seo`: `formatSeniority`. Removed from
|
|
405
|
+
`@cavuno/board/filters`: `seniorityLabels`, `sortLabels`.
|
|
406
|
+
|
|
407
|
+
Hosted boards import chrome words from a private package. Third-party applications own their copy in code (or their
|
|
408
|
+
message catalogs). The published SDK no longer imports or bundles that
|
|
409
|
+
package.
|
|
410
|
+
|
|
411
|
+
What stays on `@cavuno/board/format`: `formatSalaryRange` (`Intl`-backed
|
|
412
|
+
amounts, wire `timeframe` enum, and a `bound` discriminant for open ranges),
|
|
413
|
+
date helpers, `resolveCustomFieldDisplay`, `normalizeWebsiteUrl`,
|
|
414
|
+
`buildJobBreadcrumbs` (kinds + data names; no `language`/`labels` args),
|
|
415
|
+
`COUNTRY_CODES` / `countryOptions`, and the salary-stat formatters. Filter
|
|
416
|
+
vocabularies (wire enums + parsers only) stay on `@cavuno/board/filters`.
|
|
417
|
+
|
|
418
|
+
- **Breaking:** `board.legal.retrieve`, the legal types (`PublicLegalPage`,
|
|
419
|
+
`LegalPageType`, `LegalEntity`), and `GET /v1/boards/{identifier}/legal/{type}`
|
|
420
|
+
(`getBoardLegal`) are removed. Legal prose is editor-authored
|
|
421
|
+
application content, not a Board API concern.
|
|
422
|
+
|
|
423
|
+
- **Breaking:** `board.context().labels` is removed. Operator label overrides
|
|
424
|
+
no longer ride the public Board API — applications own chrome words.
|
|
425
|
+
Hosted storage for operator overrides is unchanged (hosted chrome still
|
|
426
|
+
reads the bag).
|
|
427
|
+
|
|
428
|
+
Before:
|
|
429
|
+
|
|
430
|
+
```json
|
|
431
|
+
"labels": {
|
|
432
|
+
"jobCardLabels": { "featuredLabel": "Top Job" },
|
|
433
|
+
"navLabels": { "home": "Jobs" }
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
After: field absent.
|
|
438
|
+
|
|
6
439
|
## 3.2.0 — 2026-08-03
|
|
7
440
|
|
|
8
441
|
- Added `formatSalaryStatUsd`, `formatSalaryStatRange`, `normalizeWebsiteUrl`,
|
package/README.md
CHANGED
|
@@ -53,8 +53,12 @@ const { name, language, features } = await board.context();
|
|
|
53
53
|
const page = await board.jobs.list({ limit: 20 });
|
|
54
54
|
const job = await board.jobs.retrieve('senior-chef');
|
|
55
55
|
|
|
56
|
-
// Federated search-dropdown suggestions (companies
|
|
57
|
-
const { items } = await board.search.suggest({
|
|
56
|
+
// Federated search-dropdown suggestions (companies, markets, taxonomy terms).
|
|
57
|
+
const { items } = await board.search.suggest({
|
|
58
|
+
q: 'acme',
|
|
59
|
+
limit: 10,
|
|
60
|
+
types: ['company', 'skill'],
|
|
61
|
+
});
|
|
58
62
|
// Or the headless controller (debounce / abort / stale-drop built in):
|
|
59
63
|
// import { createSuggestController } from '@cavuno/board/suggest';
|
|
60
64
|
// const suggest = createSuggestController(board);
|