@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
package/dist/paths.d.mts CHANGED
@@ -9,11 +9,27 @@
9
9
  * zero-dependency — safe to import in server runtimes, on the edge,
10
10
  * and in the browser.
11
11
  *
12
+ * Dynamic path segments are percent-encoded (URI, not bare IRI) so
13
+ * non-Latin board-language slugs (`SuggestionPathInput.canonicalSlug`)
14
+ * are legal in sitemap `<loc>`, canonical/`og:url`, and email links.
15
+ * Separators (`/`) are never encoded; already-encoded input is not
16
+ * double-encoded.
17
+ *
12
18
  * These paths mirror the hosted board's indexed URLs exactly (migration
13
19
  * parity). Do NOT change a pattern here without updating the hosted board
14
20
  * and the sitemap golden tests in lockstep — the structure is a locked
15
21
  * cross-surface contract.
16
22
  */
23
+ /**
24
+ * Percent-encode one path segment for use in a URL path.
25
+ * Round-trips already-encoded input so `%E6…` is not double-encoded.
26
+ * Does not touch path separators — callers encode segments only.
27
+ *
28
+ * `:` is left unencoded so route-contract / well-known templates built via
29
+ * `jobDetailPath(':companySlug', ':jobSlug')` stay RFC-6570-style. Real
30
+ * board slugs are alphanumeric-plus-hyphen; non-ASCII is still encoded.
31
+ */
32
+ declare function encodePathSegment(segment: string): string;
17
33
  /** Job detail — the canonical, indexed job URL. Requires BOTH slugs. */
18
34
  declare function jobDetailPath(companySlug: string, jobSlug: string): string;
19
35
  /** Category (keyword) job listing. */
@@ -22,10 +38,81 @@ declare function jobsCategoryPath(categorySlug: string): string;
22
38
  declare function jobsSkillPath(skillSlug: string): string;
23
39
  /** Location job listing. */
24
40
  declare function jobsLocationPath(placeSlug: string): string;
41
+ /**
42
+ * Location + category combination job listing
43
+ * (`/jobs/locations/<place>/<category>`).
44
+ */
45
+ declare function jobsLocationCategoryPath(placeSlug: string, categorySlug: string): string;
46
+ /**
47
+ * Location + skill combination job listing
48
+ * (`/jobs/locations/<place>/skills/<skill>`).
49
+ */
50
+ declare function jobsLocationSkillPath(placeSlug: string, skillSlug: string): string;
25
51
  /** Company profile. */
26
52
  declare function companyPath(companySlug: string): string;
27
53
  /** Company market (sector) listing. */
28
54
  declare function companyMarketPath(marketSlug: string): string;
55
+ /**
56
+ * Suggestion kinds `suggestionPath` understands. Mirrors the wire
57
+ * `SuggestionItem` discriminators (and `termType` for terms) without
58
+ * importing generated OpenAPI types into the paths entry.
59
+ */
60
+ type SuggestionPathInput = {
61
+ type: 'company';
62
+ slug: string;
63
+ } | {
64
+ type: 'market';
65
+ slug: string;
66
+ } | {
67
+ type: 'post';
68
+ slug: string;
69
+ } | {
70
+ type: 'tag';
71
+ slug: string;
72
+ } | {
73
+ type: 'term';
74
+ termType: 'category' | 'skill';
75
+ /** Board-language URL slug — use `canonicalSlug` from the wire item. */
76
+ canonicalSlug: string;
77
+ };
78
+ /** Search scopes that own different destinations for the same suggestion. */
79
+ type SuggestionPathScope = 'jobs' | 'companies' | 'blog';
80
+ type SuggestionPathOptions = {
81
+ scope: SuggestionPathScope;
82
+ /**
83
+ * Active place slug. When set on the `jobs` scope with a category or
84
+ * skill term, routes into the location combination pages.
85
+ */
86
+ location?: string;
87
+ };
88
+ /**
89
+ * Resolve a suggestion to a board-relative path for the given search scope
90
+ * The API never returns an `href` because the same
91
+ * `CompanySuggestion` means "navigate to the company page" under companies
92
+ * scope and "apply a `companySlug` filter" under jobs scope — only the app
93
+ * knows which.
94
+ *
95
+ * Returns `null` when the selection is not a navigation (jobs-scope company
96
+ * → filter, or a kind that has no path under the active scope). Compose from
97
+ * the existing `*Path` helpers; never string-build at the call site.
98
+ *
99
+ * | scope | suggestion | result |
100
+ * | --- | --- | --- |
101
+ * | jobs | skill | `/jobs/skills/<slug>` |
102
+ * | jobs | category | `/jobs/<slug>` |
103
+ * | jobs | skill + location | `/jobs/locations/<loc>/skills/<slug>` |
104
+ * | jobs | category + location | `/jobs/locations/<loc>/<slug>` |
105
+ * | jobs | company | `null` (apply `companySlug` filter) |
106
+ * | jobs | market | `null` |
107
+ * | jobs | post / tag | `null` |
108
+ * | companies | company | `/companies/<slug>` |
109
+ * | companies | market | `/companies/markets/<slug>` |
110
+ * | companies | term / post / tag | `null` |
111
+ * | blog | post | `/blog/<slug>` |
112
+ * | blog | tag | `/blog/tag/<slug>` |
113
+ * | blog | other | `null` |
114
+ */
115
+ declare function suggestionPath(suggestion: SuggestionPathInput, options: SuggestionPathOptions): string | null;
29
116
  /** A company's salary overview. */
30
117
  declare function companySalaryPath(companySlug: string): string;
31
118
  /** Salary page for a job title. */
@@ -90,7 +177,8 @@ declare function goAlertsConfirmPath(): string;
90
177
  /**
91
178
  * Prefix a board-relative path with the board origin. A trailing slash on
92
179
  * the origin is tolerated (stripped) so callers need not normalise first.
180
+ * Paths from `*Path` helpers already percent-encode dynamic segments.
93
181
  */
94
182
  declare function boardUrl(origin: string, path: string): string;
95
183
 
96
- export { BOARD_PATHS, blogAuthorPath, blogPostPath, blogTagPath, boardUrl, companyMarketPath, companyPath, companySalaryPath, goAlertsConfirmPath, goAlertsManagePath, goJobPath, jobDetailPath, jobsCategoryPath, jobsLocationPath, jobsSkillPath, salaryLocationPath, salarySkillPath, salaryTitlePath };
184
+ export { BOARD_PATHS, type SuggestionPathInput, type SuggestionPathOptions, type SuggestionPathScope, blogAuthorPath, blogPostPath, blogTagPath, boardUrl, companyMarketPath, companyPath, companySalaryPath, encodePathSegment, goAlertsConfirmPath, goAlertsManagePath, goJobPath, jobDetailPath, jobsCategoryPath, jobsLocationCategoryPath, jobsLocationPath, jobsLocationSkillPath, jobsSkillPath, salaryLocationPath, salarySkillPath, salaryTitlePath, suggestionPath };
package/dist/paths.d.ts CHANGED
@@ -9,11 +9,27 @@
9
9
  * zero-dependency — safe to import in server runtimes, on the edge,
10
10
  * and in the browser.
11
11
  *
12
+ * Dynamic path segments are percent-encoded (URI, not bare IRI) so
13
+ * non-Latin board-language slugs (`SuggestionPathInput.canonicalSlug`)
14
+ * are legal in sitemap `<loc>`, canonical/`og:url`, and email links.
15
+ * Separators (`/`) are never encoded; already-encoded input is not
16
+ * double-encoded.
17
+ *
12
18
  * These paths mirror the hosted board's indexed URLs exactly (migration
13
19
  * parity). Do NOT change a pattern here without updating the hosted board
14
20
  * and the sitemap golden tests in lockstep — the structure is a locked
15
21
  * cross-surface contract.
16
22
  */
23
+ /**
24
+ * Percent-encode one path segment for use in a URL path.
25
+ * Round-trips already-encoded input so `%E6…` is not double-encoded.
26
+ * Does not touch path separators — callers encode segments only.
27
+ *
28
+ * `:` is left unencoded so route-contract / well-known templates built via
29
+ * `jobDetailPath(':companySlug', ':jobSlug')` stay RFC-6570-style. Real
30
+ * board slugs are alphanumeric-plus-hyphen; non-ASCII is still encoded.
31
+ */
32
+ declare function encodePathSegment(segment: string): string;
17
33
  /** Job detail — the canonical, indexed job URL. Requires BOTH slugs. */
18
34
  declare function jobDetailPath(companySlug: string, jobSlug: string): string;
19
35
  /** Category (keyword) job listing. */
@@ -22,10 +38,81 @@ declare function jobsCategoryPath(categorySlug: string): string;
22
38
  declare function jobsSkillPath(skillSlug: string): string;
23
39
  /** Location job listing. */
24
40
  declare function jobsLocationPath(placeSlug: string): string;
41
+ /**
42
+ * Location + category combination job listing
43
+ * (`/jobs/locations/<place>/<category>`).
44
+ */
45
+ declare function jobsLocationCategoryPath(placeSlug: string, categorySlug: string): string;
46
+ /**
47
+ * Location + skill combination job listing
48
+ * (`/jobs/locations/<place>/skills/<skill>`).
49
+ */
50
+ declare function jobsLocationSkillPath(placeSlug: string, skillSlug: string): string;
25
51
  /** Company profile. */
26
52
  declare function companyPath(companySlug: string): string;
27
53
  /** Company market (sector) listing. */
28
54
  declare function companyMarketPath(marketSlug: string): string;
55
+ /**
56
+ * Suggestion kinds `suggestionPath` understands. Mirrors the wire
57
+ * `SuggestionItem` discriminators (and `termType` for terms) without
58
+ * importing generated OpenAPI types into the paths entry.
59
+ */
60
+ type SuggestionPathInput = {
61
+ type: 'company';
62
+ slug: string;
63
+ } | {
64
+ type: 'market';
65
+ slug: string;
66
+ } | {
67
+ type: 'post';
68
+ slug: string;
69
+ } | {
70
+ type: 'tag';
71
+ slug: string;
72
+ } | {
73
+ type: 'term';
74
+ termType: 'category' | 'skill';
75
+ /** Board-language URL slug — use `canonicalSlug` from the wire item. */
76
+ canonicalSlug: string;
77
+ };
78
+ /** Search scopes that own different destinations for the same suggestion. */
79
+ type SuggestionPathScope = 'jobs' | 'companies' | 'blog';
80
+ type SuggestionPathOptions = {
81
+ scope: SuggestionPathScope;
82
+ /**
83
+ * Active place slug. When set on the `jobs` scope with a category or
84
+ * skill term, routes into the location combination pages.
85
+ */
86
+ location?: string;
87
+ };
88
+ /**
89
+ * Resolve a suggestion to a board-relative path for the given search scope
90
+ * The API never returns an `href` because the same
91
+ * `CompanySuggestion` means "navigate to the company page" under companies
92
+ * scope and "apply a `companySlug` filter" under jobs scope — only the app
93
+ * knows which.
94
+ *
95
+ * Returns `null` when the selection is not a navigation (jobs-scope company
96
+ * → filter, or a kind that has no path under the active scope). Compose from
97
+ * the existing `*Path` helpers; never string-build at the call site.
98
+ *
99
+ * | scope | suggestion | result |
100
+ * | --- | --- | --- |
101
+ * | jobs | skill | `/jobs/skills/<slug>` |
102
+ * | jobs | category | `/jobs/<slug>` |
103
+ * | jobs | skill + location | `/jobs/locations/<loc>/skills/<slug>` |
104
+ * | jobs | category + location | `/jobs/locations/<loc>/<slug>` |
105
+ * | jobs | company | `null` (apply `companySlug` filter) |
106
+ * | jobs | market | `null` |
107
+ * | jobs | post / tag | `null` |
108
+ * | companies | company | `/companies/<slug>` |
109
+ * | companies | market | `/companies/markets/<slug>` |
110
+ * | companies | term / post / tag | `null` |
111
+ * | blog | post | `/blog/<slug>` |
112
+ * | blog | tag | `/blog/tag/<slug>` |
113
+ * | blog | other | `null` |
114
+ */
115
+ declare function suggestionPath(suggestion: SuggestionPathInput, options: SuggestionPathOptions): string | null;
29
116
  /** A company's salary overview. */
30
117
  declare function companySalaryPath(companySlug: string): string;
31
118
  /** Salary page for a job title. */
@@ -90,7 +177,8 @@ declare function goAlertsConfirmPath(): string;
90
177
  /**
91
178
  * Prefix a board-relative path with the board origin. A trailing slash on
92
179
  * the origin is tolerated (stripped) so callers need not normalise first.
180
+ * Paths from `*Path` helpers already percent-encode dynamic segments.
93
181
  */
94
182
  declare function boardUrl(origin: string, path: string): string;
95
183
 
96
- export { BOARD_PATHS, blogAuthorPath, blogPostPath, blogTagPath, boardUrl, companyMarketPath, companyPath, companySalaryPath, goAlertsConfirmPath, goAlertsManagePath, goJobPath, jobDetailPath, jobsCategoryPath, jobsLocationPath, jobsSkillPath, salaryLocationPath, salarySkillPath, salaryTitlePath };
184
+ export { BOARD_PATHS, type SuggestionPathInput, type SuggestionPathOptions, type SuggestionPathScope, blogAuthorPath, blogPostPath, blogTagPath, boardUrl, companyMarketPath, companyPath, companySalaryPath, encodePathSegment, goAlertsConfirmPath, goAlertsManagePath, goJobPath, jobDetailPath, jobsCategoryPath, jobsLocationCategoryPath, jobsLocationPath, jobsLocationSkillPath, jobsSkillPath, salaryLocationPath, salarySkillPath, salaryTitlePath, suggestionPath };
package/dist/paths.js CHANGED
@@ -28,56 +28,104 @@ __export(paths_exports, {
28
28
  companyMarketPath: () => companyMarketPath,
29
29
  companyPath: () => companyPath,
30
30
  companySalaryPath: () => companySalaryPath,
31
+ encodePathSegment: () => encodePathSegment,
31
32
  goAlertsConfirmPath: () => goAlertsConfirmPath,
32
33
  goAlertsManagePath: () => goAlertsManagePath,
33
34
  goJobPath: () => goJobPath,
34
35
  jobDetailPath: () => jobDetailPath,
35
36
  jobsCategoryPath: () => jobsCategoryPath,
37
+ jobsLocationCategoryPath: () => jobsLocationCategoryPath,
36
38
  jobsLocationPath: () => jobsLocationPath,
39
+ jobsLocationSkillPath: () => jobsLocationSkillPath,
37
40
  jobsSkillPath: () => jobsSkillPath,
38
41
  salaryLocationPath: () => salaryLocationPath,
39
42
  salarySkillPath: () => salarySkillPath,
40
- salaryTitlePath: () => salaryTitlePath
43
+ salaryTitlePath: () => salaryTitlePath,
44
+ suggestionPath: () => suggestionPath
41
45
  });
42
46
  module.exports = __toCommonJS(paths_exports);
47
+ function encodePathSegment(segment) {
48
+ try {
49
+ return encodeURIComponent(decodeURIComponent(segment)).replace(
50
+ /%3A/gi,
51
+ ":"
52
+ );
53
+ } catch {
54
+ return encodeURIComponent(segment).replace(/%3A/gi, ":");
55
+ }
56
+ }
43
57
  function jobDetailPath(companySlug, jobSlug) {
44
- return `/companies/${companySlug}/jobs/${jobSlug}`;
58
+ return `/companies/${encodePathSegment(companySlug)}/jobs/${encodePathSegment(jobSlug)}`;
45
59
  }
46
60
  function jobsCategoryPath(categorySlug) {
47
- return `/jobs/${categorySlug}`;
61
+ return `/jobs/${encodePathSegment(categorySlug)}`;
48
62
  }
49
63
  function jobsSkillPath(skillSlug) {
50
- return `/jobs/skills/${skillSlug}`;
64
+ return `/jobs/skills/${encodePathSegment(skillSlug)}`;
51
65
  }
52
66
  function jobsLocationPath(placeSlug) {
53
- return `/jobs/locations/${placeSlug}`;
67
+ return `/jobs/locations/${encodePathSegment(placeSlug)}`;
68
+ }
69
+ function jobsLocationCategoryPath(placeSlug, categorySlug) {
70
+ return `${jobsLocationPath(placeSlug)}/${encodePathSegment(categorySlug)}`;
71
+ }
72
+ function jobsLocationSkillPath(placeSlug, skillSlug) {
73
+ return `${jobsLocationPath(placeSlug)}/skills/${encodePathSegment(skillSlug)}`;
54
74
  }
55
75
  function companyPath(companySlug) {
56
- return `/companies/${companySlug}`;
76
+ return `/companies/${encodePathSegment(companySlug)}`;
57
77
  }
58
78
  function companyMarketPath(marketSlug) {
59
- return `/companies/markets/${marketSlug}`;
79
+ return `/companies/markets/${encodePathSegment(marketSlug)}`;
80
+ }
81
+ function suggestionPath(suggestion, options) {
82
+ const location = options.location?.trim() || void 0;
83
+ if (options.scope === "blog") {
84
+ if (suggestion.type === "post") {
85
+ return blogPostPath(suggestion.slug);
86
+ }
87
+ if (suggestion.type === "tag") {
88
+ return blogTagPath(suggestion.slug);
89
+ }
90
+ return null;
91
+ }
92
+ if (options.scope === "jobs") {
93
+ if (suggestion.type === "company" || suggestion.type === "market" || suggestion.type === "post" || suggestion.type === "tag") {
94
+ return null;
95
+ }
96
+ if (suggestion.termType === "skill") {
97
+ return location ? jobsLocationSkillPath(location, suggestion.canonicalSlug) : jobsSkillPath(suggestion.canonicalSlug);
98
+ }
99
+ return location ? jobsLocationCategoryPath(location, suggestion.canonicalSlug) : jobsCategoryPath(suggestion.canonicalSlug);
100
+ }
101
+ if (suggestion.type === "company") {
102
+ return companyPath(suggestion.slug);
103
+ }
104
+ if (suggestion.type === "market") {
105
+ return companyMarketPath(suggestion.slug);
106
+ }
107
+ return null;
60
108
  }
61
109
  function companySalaryPath(companySlug) {
62
- return `/companies/${companySlug}/salaries`;
110
+ return `/companies/${encodePathSegment(companySlug)}/salaries`;
63
111
  }
64
112
  function salaryTitlePath(titleSlug) {
65
- return `/salaries/titles/${titleSlug}`;
113
+ return `/salaries/titles/${encodePathSegment(titleSlug)}`;
66
114
  }
67
115
  function salarySkillPath(skillSlug) {
68
- return `/salaries/skills/${skillSlug}`;
116
+ return `/salaries/skills/${encodePathSegment(skillSlug)}`;
69
117
  }
70
118
  function salaryLocationPath(placeSlug) {
71
- return `/salaries/locations/${placeSlug}`;
119
+ return `/salaries/locations/${encodePathSegment(placeSlug)}`;
72
120
  }
73
121
  function blogPostPath(postSlug) {
74
- return `/blog/${postSlug}`;
122
+ return `/blog/${encodePathSegment(postSlug)}`;
75
123
  }
76
124
  function blogTagPath(tagSlug) {
77
- return `/blog/tag/${tagSlug}`;
125
+ return `/blog/tag/${encodePathSegment(tagSlug)}`;
78
126
  }
79
127
  function blogAuthorPath(authorSlug) {
80
- return `/blog/author/${authorSlug}`;
128
+ return `/blog/author/${encodePathSegment(authorSlug)}`;
81
129
  }
82
130
  var BOARD_PATHS = {
83
131
  home: "/",
package/dist/paths.mjs CHANGED
@@ -7,17 +7,21 @@ import {
7
7
  companyMarketPath,
8
8
  companyPath,
9
9
  companySalaryPath,
10
+ encodePathSegment,
10
11
  goAlertsConfirmPath,
11
12
  goAlertsManagePath,
12
13
  goJobPath,
13
14
  jobDetailPath,
14
15
  jobsCategoryPath,
16
+ jobsLocationCategoryPath,
15
17
  jobsLocationPath,
18
+ jobsLocationSkillPath,
16
19
  jobsSkillPath,
17
20
  salaryLocationPath,
18
21
  salarySkillPath,
19
- salaryTitlePath
20
- } from "./chunk-L7ZOYWHW.mjs";
22
+ salaryTitlePath,
23
+ suggestionPath
24
+ } from "./chunk-BGKNXG4H.mjs";
21
25
  export {
22
26
  BOARD_PATHS,
23
27
  blogAuthorPath,
@@ -27,14 +31,18 @@ export {
27
31
  companyMarketPath,
28
32
  companyPath,
29
33
  companySalaryPath,
34
+ encodePathSegment,
30
35
  goAlertsConfirmPath,
31
36
  goAlertsManagePath,
32
37
  goJobPath,
33
38
  jobDetailPath,
34
39
  jobsCategoryPath,
40
+ jobsLocationCategoryPath,
35
41
  jobsLocationPath,
42
+ jobsLocationSkillPath,
36
43
  jobsSkillPath,
37
44
  salaryLocationPath,
38
45
  salarySkillPath,
39
- salaryTitlePath
46
+ salaryTitlePath,
47
+ suggestionPath
40
48
  };
@@ -51,44 +51,54 @@ __export(route_contract_exports, {
51
51
  module.exports = __toCommonJS(route_contract_exports);
52
52
 
53
53
  // src/paths/index.ts
54
+ function encodePathSegment(segment) {
55
+ try {
56
+ return encodeURIComponent(decodeURIComponent(segment)).replace(
57
+ /%3A/gi,
58
+ ":"
59
+ );
60
+ } catch {
61
+ return encodeURIComponent(segment).replace(/%3A/gi, ":");
62
+ }
63
+ }
54
64
  function jobDetailPath(companySlug, jobSlug) {
55
- return `/companies/${companySlug}/jobs/${jobSlug}`;
65
+ return `/companies/${encodePathSegment(companySlug)}/jobs/${encodePathSegment(jobSlug)}`;
56
66
  }
57
67
  function jobsCategoryPath(categorySlug) {
58
- return `/jobs/${categorySlug}`;
68
+ return `/jobs/${encodePathSegment(categorySlug)}`;
59
69
  }
60
70
  function jobsSkillPath(skillSlug) {
61
- return `/jobs/skills/${skillSlug}`;
71
+ return `/jobs/skills/${encodePathSegment(skillSlug)}`;
62
72
  }
63
73
  function jobsLocationPath(placeSlug) {
64
- return `/jobs/locations/${placeSlug}`;
74
+ return `/jobs/locations/${encodePathSegment(placeSlug)}`;
65
75
  }
66
76
  function companyPath(companySlug) {
67
- return `/companies/${companySlug}`;
77
+ return `/companies/${encodePathSegment(companySlug)}`;
68
78
  }
69
79
  function companyMarketPath(marketSlug) {
70
- return `/companies/markets/${marketSlug}`;
80
+ return `/companies/markets/${encodePathSegment(marketSlug)}`;
71
81
  }
72
82
  function companySalaryPath(companySlug) {
73
- return `/companies/${companySlug}/salaries`;
83
+ return `/companies/${encodePathSegment(companySlug)}/salaries`;
74
84
  }
75
85
  function salaryTitlePath(titleSlug) {
76
- return `/salaries/titles/${titleSlug}`;
86
+ return `/salaries/titles/${encodePathSegment(titleSlug)}`;
77
87
  }
78
88
  function salarySkillPath(skillSlug) {
79
- return `/salaries/skills/${skillSlug}`;
89
+ return `/salaries/skills/${encodePathSegment(skillSlug)}`;
80
90
  }
81
91
  function salaryLocationPath(placeSlug) {
82
- return `/salaries/locations/${placeSlug}`;
92
+ return `/salaries/locations/${encodePathSegment(placeSlug)}`;
83
93
  }
84
94
  function blogPostPath(postSlug) {
85
- return `/blog/${postSlug}`;
95
+ return `/blog/${encodePathSegment(postSlug)}`;
86
96
  }
87
97
  function blogTagPath(tagSlug) {
88
- return `/blog/tag/${tagSlug}`;
98
+ return `/blog/tag/${encodePathSegment(tagSlug)}`;
89
99
  }
90
100
  function blogAuthorPath(authorSlug) {
91
- return `/blog/author/${authorSlug}`;
101
+ return `/blog/author/${encodePathSegment(authorSlug)}`;
92
102
  }
93
103
  var BOARD_PATHS = {
94
104
  home: "/",
@@ -26,8 +26,8 @@ import {
26
26
  parseTanStackRoutes,
27
27
  sameParamSet,
28
28
  validateManifest
29
- } from "./chunk-NXY3OWGF.mjs";
30
- import "./chunk-L7ZOYWHW.mjs";
29
+ } from "./chunk-T2KC6XGV.mjs";
30
+ import "./chunk-BGKNXG4H.mjs";
31
31
  export {
32
32
  ALL_ROLES,
33
33
  CANONICAL_MANIFEST,
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-nkWAraS0.js';
1
+ import { S as Schemas } from './_spec-GVe6WAfT.mjs';
2
2
 
3
3
  type Awaitable<T> = T | Promise<T>;
4
4
  /**
@@ -69,13 +69,24 @@ declare class BoardClient {
69
69
  type SuggestResult = Schemas['SuggestResult'];
70
70
  type SuggestionItem = Schemas['SuggestionItem'];
71
71
  type CompanySuggestion = Schemas['CompanySuggestion'];
72
+ type MarketSuggestion = Schemas['MarketSuggestion'];
72
73
  type TermSuggestion = Schemas['TermSuggestion'];
74
+ /** Kinds accepted by `SearchSuggestQuery.types`. */
75
+ type SearchSuggestType = 'company' | 'category' | 'skill' | 'market' | 'post' | 'tag';
73
76
  /** Query for `board.search.suggest()`. */
74
77
  type SearchSuggestQuery = {
75
78
  /** Keyword; under 2 chars the server returns an empty `items` array. */
76
79
  q?: string;
77
- /** Max interleaved suggestions (1–25; default 25). */
80
+ /**
81
+ * Max interleaved suggestions after type filtering (1–25; default 25).
82
+ * With `types: ['skill']`, this is "up to N skills", not N of a mixed pool.
83
+ */
78
84
  limit?: number;
85
+ /**
86
+ * Include only these suggestion kinds. Omit for every kind (company,
87
+ * category, skill, market, post, tag). Repeated query keys on the wire.
88
+ */
89
+ types?: SearchSuggestType[];
79
90
  };
80
91
 
81
- export { type Awaitable as A, type BoardRequest as B, type CompanySuggestion as C, type FetchOptions as F, type Logger as L, REFRESH_TOKEN_KEY as R, type StorageMode as S, type TermSuggestion as T, BoardClient as a, type SearchSuggestQuery as b, ACCESS_TOKEN_KEY as c, BOARD_ACCESS_GRANT_KEY as d, type CustomStorage as e, type SuggestResult as f, type SuggestionItem as g };
92
+ export { type Awaitable as A, type BoardRequest as B, type CompanySuggestion as C, type FetchOptions as F, type Logger as L, type MarketSuggestion as M, REFRESH_TOKEN_KEY as R, type StorageMode as S, type TermSuggestion as T, BoardClient as a, type SearchSuggestQuery as b, ACCESS_TOKEN_KEY as c, BOARD_ACCESS_GRANT_KEY as d, type CustomStorage as e, type SearchSuggestType as f, type SuggestResult as g, type SuggestionItem as h };
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-nkWAraS0.mjs';
1
+ import { S as Schemas } from './_spec-GVe6WAfT.js';
2
2
 
3
3
  type Awaitable<T> = T | Promise<T>;
4
4
  /**
@@ -69,13 +69,24 @@ declare class BoardClient {
69
69
  type SuggestResult = Schemas['SuggestResult'];
70
70
  type SuggestionItem = Schemas['SuggestionItem'];
71
71
  type CompanySuggestion = Schemas['CompanySuggestion'];
72
+ type MarketSuggestion = Schemas['MarketSuggestion'];
72
73
  type TermSuggestion = Schemas['TermSuggestion'];
74
+ /** Kinds accepted by `SearchSuggestQuery.types`. */
75
+ type SearchSuggestType = 'company' | 'category' | 'skill' | 'market' | 'post' | 'tag';
73
76
  /** Query for `board.search.suggest()`. */
74
77
  type SearchSuggestQuery = {
75
78
  /** Keyword; under 2 chars the server returns an empty `items` array. */
76
79
  q?: string;
77
- /** Max interleaved suggestions (1–25; default 25). */
80
+ /**
81
+ * Max interleaved suggestions after type filtering (1–25; default 25).
82
+ * With `types: ['skill']`, this is "up to N skills", not N of a mixed pool.
83
+ */
78
84
  limit?: number;
85
+ /**
86
+ * Include only these suggestion kinds. Omit for every kind (company,
87
+ * category, skill, market, post, tag). Repeated query keys on the wire.
88
+ */
89
+ types?: SearchSuggestType[];
79
90
  };
80
91
 
81
- export { type Awaitable as A, type BoardRequest as B, type CompanySuggestion as C, type FetchOptions as F, type Logger as L, REFRESH_TOKEN_KEY as R, type StorageMode as S, type TermSuggestion as T, BoardClient as a, type SearchSuggestQuery as b, ACCESS_TOKEN_KEY as c, BOARD_ACCESS_GRANT_KEY as d, type CustomStorage as e, type SuggestResult as f, type SuggestionItem as g };
92
+ export { type Awaitable as A, type BoardRequest as B, type CompanySuggestion as C, type FetchOptions as F, type Logger as L, type MarketSuggestion as M, REFRESH_TOKEN_KEY as R, type StorageMode as S, type TermSuggestion as T, BoardClient as a, type SearchSuggestQuery as b, ACCESS_TOKEN_KEY as c, BOARD_ACCESS_GRANT_KEY as d, type CustomStorage as e, type SearchSuggestType as f, type SuggestResult as g, type SuggestionItem as h };
package/dist/seo.d.mts CHANGED
@@ -1,10 +1,9 @@
1
- import { P as PublicBlogPostSummary, B as BlogAuthorEmbed } from './blog-DGBHmExV.mjs';
2
- import { J as JsonLdObject } from './breadcrumbs-1hMjcsqA.mjs';
3
- export { A as ALL_COUNTRY_CODES, B as BreadcrumbItemInput, F as FaqItem, c as JsonLdBoard, S as SENIORITY_ORDER, b as buildJobBreadcrumbs, d as buildSalaryFaq, e as companyCategorySalaryJsonLd, g as companySalaryJsonLd, h as createBreadcrumbJsonLd, i as createJobPostingJsonLd, j as crossAxisSalaryJsonLd, k as faqJsonLd, f as formatRange, l as formatSeniority, a as formatUsd, m as itemListJsonLd, o as locationSalaryJsonLd, n as normalizeWebsiteUrl, s as skillSalaryJsonLd, p as sortBySeniority, t as titleSalaryJsonLd } from './breadcrumbs-1hMjcsqA.mjs';
4
- import './_spec-nkWAraS0.mjs';
5
- import './board-BJbXd_9_.mjs';
6
- import './jobs-lVvKCBCU.mjs';
7
- import './ui-copy-CCkz6CVF.mjs';
1
+ import { P as PublicBlogPostSummary, B as BlogAuthorEmbed } from './blog-Cf64mI8A.mjs';
2
+ import { e as JsonLdObject } from './breadcrumbs-DFKr0HzU.mjs';
3
+ export { A as ALL_COUNTRY_CODES, B as BreadcrumbItemInput, g as FaqItem, J as JobBreadcrumb, h as JsonLdBoard, N as NumberNotation, i as SENIORITY_ORDER, j as SalaryFaqEntry, k as SalaryJsonLdOptions, l as SalaryOccupationListOptions, b as buildJobBreadcrumbs, m as buildSalaryFaq, o as companyCategorySalaryJsonLd, p as companySalaryJsonLd, q as createBreadcrumbJsonLd, r as createJobPostingJsonLd, s as crossAxisSalaryJsonLd, t as faqJsonLd, c as formatSalaryStat, d as formatSalaryStatRange, u as itemListJsonLd, v as locationSalaryJsonLd, n as normalizeWebsiteUrl, w as skillSalaryJsonLd, x as sortBySeniority, y as titleSalaryJsonLd } from './breadcrumbs-DFKr0HzU.mjs';
4
+ import './_spec-GVe6WAfT.mjs';
5
+ import './board-GGWJ4coe.mjs';
6
+ import './jobs-BuLpV8iy.mjs';
8
7
 
9
8
  /**
10
9
  * Blog structured data on the `@cavuno/board` wire types, transcribed from
@@ -59,36 +58,32 @@ declare function createAuthorProfileJsonLd({ author, canonical, description, ori
59
58
  totalPosts: number;
60
59
  }): JsonLdObject | null;
61
60
 
62
- /**
63
- * Framework-neutral `<head>` + structural JSON-LD builders for jobs-listing
64
- * pages. Locale-neutral pass-through: the caller supplies the
65
- * heading/board copy (title/description COPY is board-SEO-config +
66
- * localized — a named exclusion; these builders replicate the hosted
67
- * STRUCTURE: title with result count, canonical, Open Graph, breadcrumb +
68
- * job `ItemList`).
69
- */
70
-
71
61
  interface ListingHeadOptions {
72
- boardName: string;
62
+ /**
63
+ * Fully composed document title — application owns counters, particles,
64
+ * word order, and separators (e.g. `1,225 Jobs | Acme`, `1.225 Jobs | Acme`,
65
+ * `1,225件の求人 | 求人ボード`). The SDK does not join count/heading/board.
66
+ */
67
+ title: string;
73
68
  origin: string;
74
69
  /** Absolute path of THIS page, for canonical / og:url, e.g. `/jobs/robotik`. */
75
70
  path: string;
76
- /** Human heading, e.g. `Robotik jobs in Berlin` or `Jobs`. */
77
- heading: string;
78
- /** Result count — prefixed into the title (`N heading`). */
79
- count?: number;
80
71
  /**
81
- * Board language (BCP-47). When supplied, `count` is grouped for that
82
- * locale in the title and description — `1,225 Jobs` / `1.225 Jobs` —
83
- * matching how listing bodies render the same number. Omit to keep the
84
- * ungrouped digits the hosted structure has always emitted.
72
+ * Meta description — fully owned by the application. Pass a sentence from
73
+ * the board's copy source (page config, message catalog, template). The SDK
74
+ * does not invent or case-fold this string.
85
75
  */
86
- language?: string;
76
+ description: string;
87
77
  }
88
78
  /**
89
- * Meta/link descriptors for a jobs-listing page — title (with result
90
- * count), meta description, Open Graph, and `<link rel=canonical>`.
91
- * Framework-neutral: map `meta`/`links` into your head manager.
79
+ * Meta/link descriptors for a jobs-listing page — caller-supplied title and
80
+ * meta description, Open Graph, and `<link rel=canonical>`. Framework-neutral:
81
+ * map `meta`/`links` into your head manager.
82
+ *
83
+ * **Why `title` and `description` are both accepted composed:** a function
84
+ * that takes a finished description but still composes its own title from
85
+ * count/heading/boardName is incoherent — both are display sentences whose
86
+ * arrangement is locale-dependent (Japanese counters, RTL order, …).
92
87
  */
93
88
  declare function listingHead(options: ListingHeadOptions): {
94
89
  meta: ({
@@ -123,6 +118,12 @@ interface JobLink {
123
118
  * page, mirroring the structural JSON-LD the hosted board emits. The rich
124
119
  * category set (`Occupation`/`FAQPage`/`MonetaryAmountDistribution`) lives
125
120
  * in `./salary` — feed it the salary detail reads.
121
+ *
122
+ * Empty / single-crumb trails omit `BreadcrumbList` (same min-2 rule as
123
+ * `createBreadcrumbJsonLd` — an empty `itemListElement` is invalid structured
124
+ * data). Job URLs go through `jobDetailPath` — `/jobs/{slug}` is always a
125
+ * listing route, never a job detail; company-less jobs are dropped from the
126
+ * ItemList rather than linked to an invalid path.
126
127
  */
127
128
  declare function listingJsonLd(options: {
128
129
  origin: string;