@cavuno/board 4.27.0 → 4.29.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 +57 -0
- package/dist/{_spec-B_pBNVUd.d.mts → _spec-CQx5p1cM.d.mts} +117 -20
- package/dist/{_spec-B_pBNVUd.d.ts → _spec-CQx5p1cM.d.ts} +117 -20
- package/dist/{blog-DzGRUsj5.d.ts → blog-BwyZQX_f.d.ts} +1 -1
- package/dist/{blog-fhXRik6k.d.mts → blog-DP06wTDw.d.mts} +1 -1
- package/dist/{board-fVAXZ8T5.d.ts → board-C2Y5fz0m.d.ts} +2 -2
- package/dist/{board-DEiEHcpe.d.mts → board-C7iju96a.d.mts} +2 -2
- package/dist/{breadcrumbs-DNaaP3xa.d.ts → breadcrumbs-BUzF85v3.d.ts} +2 -2
- package/dist/{breadcrumbs-qE3CD--N.d.mts → breadcrumbs-Db6UTihw.d.mts} +2 -2
- package/dist/browser/cavuno-board.global.min.js +1 -1
- package/dist/{chunk-DUTQOLOF.mjs → chunk-EHSB2VAJ.mjs} +4 -0
- package/dist/{chunk-T2KC6XGV.mjs → chunk-IVSECNCD.mjs} +54 -7
- package/dist/{chunk-OPMJ6YH5.mjs → chunk-KDFKVWTZ.mjs} +1 -1
- package/dist/{chunk-BGKNXG4H.mjs → chunk-XXQCESMU.mjs} +4 -0
- package/dist/filters.d.mts +2 -2
- package/dist/filters.d.ts +2 -2
- package/dist/format.d.mts +4 -4
- package/dist/format.d.ts +4 -4
- package/dist/format.mjs +2 -2
- package/dist/go.mjs +1 -1
- package/dist/index.d.mts +99 -15
- package/dist/index.d.ts +99 -15
- package/dist/index.js +44 -4
- package/dist/index.mjs +41 -5
- package/dist/{jobs-BRysn2m0.d.ts → jobs-BA_PnUCd.d.ts} +9 -7
- package/dist/{jobs-R-h3A3kl.d.mts → jobs-BZqtb1gY.d.mts} +9 -7
- package/dist/paths.d.mts +8 -1
- package/dist/paths.d.ts +8 -1
- package/dist/paths.js +5 -1
- package/dist/paths.mjs +5 -3
- package/dist/{roles-DBgzkvEt.d.ts → roles-Bh3WNF9u.d.mts} +3 -1
- package/dist/{roles-DBgzkvEt.d.mts → roles-Bh3WNF9u.d.ts} +3 -1
- package/dist/route-contract.d.mts +10 -5
- package/dist/route-contract.d.ts +10 -5
- package/dist/route-contract.js +53 -5
- package/dist/route-contract.mjs +2 -2
- package/dist/{search-BlB690ma.d.mts → search-Da3TjcZ0.d.mts} +1 -1
- package/dist/{search-BK_zHHNQ.d.ts → search-dsA-Cetf.d.ts} +1 -1
- package/dist/{seo-C7gMl6Y7.d.ts → seo-D-n2g6X3.d.ts} +1 -1
- package/dist/{seo-0mbZ7M_E.d.mts → seo-D3DhnHk8.d.mts} +1 -1
- package/dist/seo-files.d.mts +2 -2
- package/dist/seo-files.d.ts +2 -2
- package/dist/seo.d.mts +6 -6
- package/dist/seo.d.ts +6 -6
- package/dist/seo.mjs +2 -2
- package/dist/server.d.mts +6 -6
- package/dist/server.d.ts +6 -6
- package/dist/server.mjs +1 -1
- package/dist/sitemap.d.mts +6 -6
- package/dist/sitemap.d.ts +6 -6
- package/dist/sitemap.mjs +2 -2
- package/dist/suggest.d.mts +2 -2
- package/dist/suggest.d.ts +2 -2
- package/dist/well-known.d.mts +4 -1
- package/dist/well-known.d.ts +4 -1
- package/dist/well-known.js +30 -1
- package/dist/well-known.mjs +5 -3
- package/package.json +1 -1
- package/skills/cavuno-board-account/SKILL.md +27 -0
- package/skills/cavuno-board-errors/SKILL.md +1 -1
- package/skills/cavuno-board-filters/SKILL.md +90 -1
- package/skills/cavuno-board-format/SKILL.md +52 -0
- package/skills/cavuno-board-post-a-job/SKILL.md +24 -0
- package/skills/cavuno-board-salaries/SKILL.md +7 -0
- package/skills/cavuno-board-talent-access/SKILL.md +7 -0
- package/skills/manifest.json +2 -2
package/package.json
CHANGED
|
@@ -11,6 +11,31 @@ Anonymous methods here are token-based email unsubscribe and email-change confir
|
|
|
11
11
|
|
|
12
12
|
The host application owns forms, file pickers, and cookie plumbing; this SDK surface supplies data operations.
|
|
13
13
|
|
|
14
|
+
## Candidate screens are candidate-only
|
|
15
|
+
|
|
16
|
+
A board user is either a candidate or an employer (`me.role`). Only candidates have a candidate profile. Render candidate screens and fields (the profile form, profile completeness, resume upload, experience, education, skills, and languages) only when `me.role === 'candidate'`. On the account page, give an employer account the employer profile view instead: avatar, `displayName` (the one profile field every account can update), and a link to the employer screens (`board.me.companies.*`). Never show them the empty candidate form.
|
|
17
|
+
|
|
18
|
+
The API is the backstop: a profile write from an account with no candidate profile returns 403 `candidate_profile_required` before anything is saved. `displayName` alone is the one profile field every account can update. Surface this error as a message; never swallow it and report "Saved".
|
|
19
|
+
|
|
20
|
+
```ts snippet
|
|
21
|
+
import { isBoardApiError } from '@cavuno/board';
|
|
22
|
+
|
|
23
|
+
const me = await board.me.retrieve();
|
|
24
|
+
if (me.role !== 'candidate') {
|
|
25
|
+
return renderEmployerProfile(me); // avatar, displayName, link to employer screens
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
await board.me.profile.update({ headline: 'Staff Engineer' });
|
|
30
|
+
showSaved();
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (isBoardApiError(error) && error.code === 'candidate_profile_required') {
|
|
33
|
+
return showError('Only candidate accounts have a candidate profile.');
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
14
39
|
## Account and profile
|
|
15
40
|
|
|
16
41
|
Account deletion is a synchronous, irreversible cascade over the profile, collections, saved jobs, alerts, avatar, and resume. Obtain explicit confirmation before calling it.
|
|
@@ -252,6 +277,8 @@ await board.me.notificationPreferences.unsubscribeWithToken({
|
|
|
252
277
|
|
|
253
278
|
## Completion gate
|
|
254
279
|
|
|
280
|
+
- Candidate screens render only for `me.role === 'candidate'`; an employer account gets the employer profile view (avatar, `displayName`, link to employer screens).
|
|
281
|
+
- A `candidate_profile_required` 403 shows an error and never a "Saved" state.
|
|
255
282
|
- The profile form follows `forms.talent`: order matches, hidden fields are absent, and required fields block save while empty.
|
|
256
283
|
- Profile update is visible after retrieval.
|
|
257
284
|
- Scalar field updates preserve omitted values; collection selection updates preserve every selection the user meant to keep.
|
|
@@ -44,7 +44,7 @@ try {
|
|
|
44
44
|
}
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
The remaining status guards are `isForbidden` for 403 and `isConflict` for 409. Record `code` and `requestId` in support diagnostics. Because v1 may add error codes, unmatched `BoardApiError` instances still need a safe generic state.
|
|
47
|
+
The remaining status guards are `isForbidden` for 403 and `isConflict` for 409. A 403 `candidate_profile_required` means an account that is not a candidate (for example an employer) tried to write candidate profile data: gate candidate screens on `me.role === 'candidate'` and show the error rather than a success state. Record `code` and `requestId` in support diagnostics. Because v1 may add error codes, unmatched `BoardApiError` instances still need a safe generic state.
|
|
48
48
|
|
|
49
49
|
**Complete when:** every expected failure has one typed branch, an invalid resource renders not-found, and unmatched failures retain their original error.
|
|
50
50
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cavuno-board-filters
|
|
3
|
-
description: Listing-filter contracts with @cavuno/board. Use for job filter controls, profile-field filters, collection choices, sort controls, listing URL validation, or taxonomy-backed filter options.
|
|
3
|
+
description: Listing-filter contracts with @cavuno/board. Use for job filter controls, job and company custom-field filters, profile-field filters, collection choices, sort controls, listing URL validation, or taxonomy-backed filter options.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Listing filters
|
|
@@ -150,6 +150,60 @@ const { items } = await board.search.suggest({
|
|
|
150
150
|
The host router owns URL serialization and saved-filter persistence.
|
|
151
151
|
Locations come from `board.taxonomy.places` rather than a static filter export.
|
|
152
152
|
|
|
153
|
+
## Filter jobs by a job custom field
|
|
154
|
+
|
|
155
|
+
The operator's job custom fields arrive as definitions on
|
|
156
|
+
`board.context().customFields.job`. Expose a field as a filter only when the
|
|
157
|
+
operator intends it as one: keep an explicit list of filter keys in your app
|
|
158
|
+
config. Do not render a dropdown for every select field automatically; most
|
|
159
|
+
custom fields are details for the job page, not listing facets.
|
|
160
|
+
|
|
161
|
+
```ts snippet
|
|
162
|
+
const context = await board.context();
|
|
163
|
+
|
|
164
|
+
// The operator picked these; everything else stays a job-page detail.
|
|
165
|
+
const FILTER_FIELD_KEYS = ['work_type'];
|
|
166
|
+
const filterFields = context.customFields.job.filter(
|
|
167
|
+
(field) =>
|
|
168
|
+
FILTER_FIELD_KEYS.includes(field.key) &&
|
|
169
|
+
(field.type === 'single_select' || field.type === 'multi_select'));
|
|
170
|
+
|
|
171
|
+
// Controls: option KEYS are the values, option labels are the text.
|
|
172
|
+
const controls = filterFields.map((field) => ({
|
|
173
|
+
key: field.key,
|
|
174
|
+
label: field.label,
|
|
175
|
+
options: (field.options ?? []).map((option) => ({
|
|
176
|
+
value: option.key,
|
|
177
|
+
label: option.label,
|
|
178
|
+
})),
|
|
179
|
+
}));
|
|
180
|
+
|
|
181
|
+
// URL state holds option keys (`/jobs?work_type=contract`), never labels.
|
|
182
|
+
// Drop anything that is not a current option before it reaches the API.
|
|
183
|
+
const customFields = filterFields.flatMap((field) => {
|
|
184
|
+
const values = searchParams
|
|
185
|
+
.getAll(field.key)
|
|
186
|
+
.filter((value) => field.options?.some((option) => option.key === value));
|
|
187
|
+
return values.length > 0 ? [{ key: field.key, values }] : [];
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const results = await board.jobs.search({
|
|
191
|
+
query: filters.q,
|
|
192
|
+
filters: {
|
|
193
|
+
seniority: filters.seniority,
|
|
194
|
+
customFields: customFields.length > 0 ? customFields : undefined,
|
|
195
|
+
},
|
|
196
|
+
limit: 20,
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Clauses are AND-matched; values inside one clause are alternatives. Each
|
|
201
|
+
request takes at most 10 clauses of 10 values. An unknown key or an option key
|
|
202
|
+
the field no longer offers returns `invalid_filter`, so validate URL input
|
|
203
|
+
against the definitions as above. Boolean and number fields filter the same way
|
|
204
|
+
with `values: [true]` or `values: [3]`. Labels change when the operator renames
|
|
205
|
+
an option; keys do not, so bookmarked URLs keep working.
|
|
206
|
+
|
|
153
207
|
## Build filters from public profile fields
|
|
154
208
|
|
|
155
209
|
`board.profileFields.retrieve('company')` and `retrieve('candidate')` return only public scalar and collection-reference definitions. Use each stored field key for filtering. For a collection reference, page or search its active public choices and submit the returned record `id`; the optional `logoUrl` is display metadata.
|
|
@@ -189,6 +243,36 @@ void talent.data;
|
|
|
189
243
|
|
|
190
244
|
Clauses are AND-matched. Values or record IDs inside one clause are alternatives. Unknown, private, invalid, or archived choices return `invalid_filter`; each array accepts at most 10 clauses and each clause at most 10 choices. Job search supports scalar `customFields` under `filters`, but job collection references are outside this surface.
|
|
191
245
|
|
|
246
|
+
## Filter jobs by job and company custom fields
|
|
247
|
+
|
|
248
|
+
`jobs.search` takes two custom-field filters. `filters.customFields` matches the job's own fields, defined in `board.context().customFields.job`. `filters.companyCustomFields` matches the public profile fields of the job's company, defined in `board.profileFields.retrieve('company')`. Both use the clause shape above; the two lists never share keys, so a job field and a company field may both be called `type`.
|
|
249
|
+
|
|
250
|
+
```ts snippet
|
|
251
|
+
const [context, companyFields] = await Promise.all([
|
|
252
|
+
board.context(),
|
|
253
|
+
board.profileFields.retrieve('company'),
|
|
254
|
+
]);
|
|
255
|
+
const opportunityType = context.customFields.job.find(
|
|
256
|
+
(field) => field.key === 'opportunity_type');
|
|
257
|
+
const employerType = companyFields.definitions.find(
|
|
258
|
+
(field) => field.key === 'employer_type');
|
|
259
|
+
|
|
260
|
+
const results = await board.jobs.search({
|
|
261
|
+
query: filters.q,
|
|
262
|
+
filters: {
|
|
263
|
+
customFields: [{ key: 'opportunity_type', values: ['identified'] }],
|
|
264
|
+
companyCustomFields: [{ key: 'employer_type', values: ['acco'] }],
|
|
265
|
+
},
|
|
266
|
+
limit: 20,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
void opportunityType;
|
|
270
|
+
void employerType;
|
|
271
|
+
void results.data;
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Render each dropdown from the definition's `options` in order: show `label`, submit `key`, and keep the key in the URL so shared links survive a label change. Accept only keys the current definition offers; drop anything else from a hand-typed URL. Only `jobs.search` takes these filters. A company's jobs are re-indexed automatically when its values change. Worked example: [Custom field filters and badges](https://cavuno.com/docs/sdk/cookbook/custom-field-filters-and-badges).
|
|
275
|
+
|
|
192
276
|
## Completion gate
|
|
193
277
|
|
|
194
278
|
Finish only after every applicable check passes:
|
|
@@ -204,8 +288,13 @@ Finish only after every applicable check passes:
|
|
|
204
288
|
- `/jobs?category=engineering,design` sends
|
|
205
289
|
`filters.categories: ['engineering', 'design']` to `jobs.search`.
|
|
206
290
|
- Every paged taxonomy request forwards the previous opaque `nextCursor`.
|
|
291
|
+
- Job custom-field controls exist only for keys the operator chose as
|
|
292
|
+
filters, read their options from `board.context().customFields.job`, and send
|
|
293
|
+
option keys in `filters.customFields`; an unknown option in the URL never
|
|
294
|
+
reaches the API.
|
|
207
295
|
- Company and talent custom controls come from `board.profileFields`; private definitions and archived choices never become filter options.
|
|
208
296
|
- Profile-filter requests use stored scalar values and returned collection entry IDs, not display labels.
|
|
297
|
+
- Job-field dropdowns send `filters.customFields` and company-field dropdowns send `filters.companyCustomFields` to `jobs.search`, both built from the current definitions; a stale option key in the URL is dropped, not sent.
|
|
209
298
|
|
|
210
299
|
## Cavuno SDK reference
|
|
211
300
|
|
|
@@ -96,6 +96,55 @@ the same card view-model as listings — do not convert a full job client-side.
|
|
|
96
96
|
const card = toJobCardVM(saved.job, { language, /* … */ });
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
+
## Badges on job cards
|
|
100
|
+
|
|
101
|
+
Every job card (listings, search, company jobs, similar, recommended, and
|
|
102
|
+
saved jobs) carries `customFieldValues` for the job and
|
|
103
|
+
`company.customFieldValues` for its company. Both hold only select, boolean,
|
|
104
|
+
and number values keyed by field key; select values are option keys. Text
|
|
105
|
+
fields stay on the full job and company, and private company fields never
|
|
106
|
+
appear. Both are `{}` when empty, so a card needs no extra request to show a
|
|
107
|
+
badge.
|
|
108
|
+
|
|
109
|
+
```ts snippet
|
|
110
|
+
import { resolveCustomFieldDisplay } from '@cavuno/board/format';
|
|
111
|
+
|
|
112
|
+
const context = await board.context();
|
|
113
|
+
const companyFields = await board.profileFields.retrieve('company');
|
|
114
|
+
|
|
115
|
+
// Badge only the fields the operator wants on cards (an app-config list).
|
|
116
|
+
const JOB_BADGE_KEYS = ['work_type'];
|
|
117
|
+
const jobBadges = resolveCustomFieldDisplay(
|
|
118
|
+
context.language,
|
|
119
|
+
context.customFields.job.filter((field) => JOB_BADGE_KEYS.includes(field.key)),
|
|
120
|
+
card.customFieldValues).flatMap((entry) => {
|
|
121
|
+
switch (entry.kind) {
|
|
122
|
+
case 'text': // a single_select resolves to its option label
|
|
123
|
+
return [entry.value];
|
|
124
|
+
case 'multi_select':
|
|
125
|
+
return entry.values;
|
|
126
|
+
case 'boolean':
|
|
127
|
+
return entry.value ? [entry.label] : [];
|
|
128
|
+
case 'number':
|
|
129
|
+
return [`${entry.label}: ${entry.value}`];
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Company badge: resolve the stored option key to its public label.
|
|
134
|
+
const employerField = companyFields.definitions.find(
|
|
135
|
+
(field) => field.key === 'employer_category');
|
|
136
|
+
const employerKey = card.company?.customFieldValues.employer_category;
|
|
137
|
+
const employerBadge =
|
|
138
|
+
typeof employerKey === 'string'
|
|
139
|
+
? (employerField?.options?.find((option) => option.key === employerKey)
|
|
140
|
+
?.label ?? null)
|
|
141
|
+
: null;
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Load the definitions once per page (both are cacheable board metadata), not
|
|
145
|
+
once per card. A key with no current definition or option resolves to nothing:
|
|
146
|
+
drop the badge rather than printing the raw key.
|
|
147
|
+
|
|
99
148
|
## Completion gate
|
|
100
149
|
|
|
101
150
|
Finish only after every applicable check passes:
|
|
@@ -107,6 +156,9 @@ Finish only after every applicable check passes:
|
|
|
107
156
|
- Open ranges return `bound: 'from' | 'upTo'`; the application adds open-range
|
|
108
157
|
chrome and composes order itself.
|
|
109
158
|
- Saved jobs arrive as cards; no `fullJobToCard` conversion.
|
|
159
|
+
- Card badges read `card.customFieldValues` / `card.company.customFieldValues`
|
|
160
|
+
and resolve option keys to labels through the definitions; no raw key is
|
|
161
|
+
shown and no card triggers a per-job detail fetch.
|
|
110
162
|
- Salary-stat formatters receive the detail's `currency` (never invent USD).
|
|
111
163
|
- Formatting changes neither salary amounts nor invents chrome words.
|
|
112
164
|
|
|
@@ -87,6 +87,30 @@ The list replaces the job's wording: omit it on `update` to keep what is stored,
|
|
|
87
87
|
|
|
88
88
|
Validate `required` client-side from these entries before submitting, so the poster sees a missing field before the request.
|
|
89
89
|
|
|
90
|
+
## Pick office locations
|
|
91
|
+
|
|
92
|
+
Back the `location` field with `board.locations.search`, a worldwide search over countries, regions, cities, and localities. `session` is required: generate one per location-field interaction, reuse it across keystrokes and the pick-time `board.locations.resolve` call, then generate a new one for the next interaction. Renew it before 50 search requests or after 180 seconds. Pass `jobForm.location.allowedCountries` as `country` to narrow suggestions to the board's allowed countries.
|
|
93
|
+
|
|
94
|
+
```ts snippet
|
|
95
|
+
const session = crypto.randomUUID();
|
|
96
|
+
const { data: locations } = await board.locations.search({
|
|
97
|
+
q: 'berl',
|
|
98
|
+
session,
|
|
99
|
+
country: jobForm.location.allowedCountries?.join(','),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const picked = locations[0];
|
|
103
|
+
if (picked) {
|
|
104
|
+
const location = await board.locations.resolve({
|
|
105
|
+
locationId: picked.id,
|
|
106
|
+
session,
|
|
107
|
+
});
|
|
108
|
+
submission.officeLocations.push({ locationId: location.id });
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Show `fullName` in the input once picked and `contextLabel` as the secondary line in the result list. Resolve the picked `id` with the same session before committing the selection, then send the resolved `id` as `locationId` in the later job submission. `board.taxonomy.places.list({ q })` ids are accepted as `locationId` too. An id the server does not recognise throws `locations_invalid_id`, and `locations_unavailable` means the search is temporarily down, so let the poster retry.
|
|
113
|
+
|
|
90
114
|
## Resolve a logo
|
|
91
115
|
|
|
92
116
|
Both logo paths return a stored `publicUrl` for `create`. The SDK owns multipart encoding.
|
|
@@ -37,6 +37,13 @@ const companies = await board.salaries.companies.list();
|
|
|
37
37
|
region, and city nesting from each `SalaryLocation.parentSlug`; top-level rows
|
|
38
38
|
have `parentSlug: null`.
|
|
39
39
|
|
|
40
|
+
Every place row in a salary response (location index nodes, `topLocations`,
|
|
41
|
+
child, sibling, and other locations) carries `placeName` and `placeLabel`.
|
|
42
|
+
Render `placeLabel` wherever places are listed: it tells a city from the
|
|
43
|
+
region it shares a name with ("New York, NY" under "New York", "Washington,
|
|
44
|
+
DC" beside "Washington"). Keep `placeName` for data you pass on, such as
|
|
45
|
+
JSON-LD.
|
|
46
|
+
|
|
40
47
|
## Retrieve detail and canonicalize slugs
|
|
41
48
|
|
|
42
49
|
Every axis `retrieve(slug)` accepts an inbound English or board-language slug.
|
|
@@ -135,6 +135,13 @@ const named = await board.talent.retrieve('jane-doe');
|
|
|
135
135
|
const opaque = await board.talent.retrieve('bu_candidate');
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
+
The profile route may move to another shape with fixed text around the handle, for example `src/routes/@{$handle}.tsx` for `/@jane`. When you move it:
|
|
139
|
+
|
|
140
|
+
- Keep `export const cavunoPage = 'talentProfile'` on the profile route file, so links Cavuno builds (webhooks, admin links) follow the new address.
|
|
141
|
+
- Keep `/p/$handle` working as a permanent (301) redirect to the same value on the new route.
|
|
142
|
+
- Build every profile link (cards, messages, canonical tag, JSON-LD) through one helper that encodes only the handle, never the fixed text.
|
|
143
|
+
- Never serve a bare `/$handle` at the root: it collides with board pages and is rejected as a profile address.
|
|
144
|
+
|
|
138
145
|
## Completion gate
|
|
139
146
|
|
|
140
147
|
- `retrieve()` drives the Message-vs-upsell CTA and remaining-credit hints.
|
package/skills/manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "4.
|
|
2
|
+
"version": "4.29.0",
|
|
3
3
|
"skills": [
|
|
4
4
|
{
|
|
5
5
|
"name": "cavuno-board-account",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
},
|
|
60
60
|
{
|
|
61
61
|
"name": "cavuno-board-filters",
|
|
62
|
-
"description": "Listing-filter contracts with @cavuno/board. Use for job filter controls, profile-field filters, collection choices, sort controls, listing URL validation, or taxonomy-backed filter options.",
|
|
62
|
+
"description": "Listing-filter contracts with @cavuno/board. Use for job filter controls, job and company custom-field filters, profile-field filters, collection choices, sort controls, listing URL validation, or taxonomy-backed filter options.",
|
|
63
63
|
"path": "skills/cavuno-board-filters/SKILL.md",
|
|
64
64
|
"framework": null,
|
|
65
65
|
"category": "core"
|