@cavuno/board 4.28.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/{_spec-DQvmsp5o.d.mts → _spec-CQx5p1cM.d.mts} +15 -0
  3. package/dist/{_spec-DQvmsp5o.d.ts → _spec-CQx5p1cM.d.ts} +15 -0
  4. package/dist/{blog-CUkvhn3c.d.ts → blog-BwyZQX_f.d.ts} +1 -1
  5. package/dist/{blog-CvuObn9O.d.mts → blog-DP06wTDw.d.mts} +1 -1
  6. package/dist/{board-CCG1pO6r.d.ts → board-C2Y5fz0m.d.ts} +2 -2
  7. package/dist/{board-D6xOidUX.d.mts → board-C7iju96a.d.mts} +2 -2
  8. package/dist/{breadcrumbs-D2Cwrju4.d.ts → breadcrumbs-BUzF85v3.d.ts} +2 -2
  9. package/dist/{breadcrumbs-BBvJ5gq-.d.mts → breadcrumbs-Db6UTihw.d.mts} +2 -2
  10. package/dist/browser/cavuno-board.global.min.js +1 -1
  11. package/dist/{chunk-T2KC6XGV.mjs → chunk-IVSECNCD.mjs} +54 -7
  12. package/dist/{chunk-OPMJ6YH5.mjs → chunk-KDFKVWTZ.mjs} +1 -1
  13. package/dist/{chunk-BGKNXG4H.mjs → chunk-XXQCESMU.mjs} +4 -0
  14. package/dist/filters.d.mts +2 -2
  15. package/dist/filters.d.ts +2 -2
  16. package/dist/format.d.mts +4 -4
  17. package/dist/format.d.ts +4 -4
  18. package/dist/format.mjs +2 -2
  19. package/dist/go.mjs +1 -1
  20. package/dist/index.d.mts +20 -13
  21. package/dist/index.d.ts +20 -13
  22. package/dist/index.js +1 -1
  23. package/dist/index.mjs +1 -1
  24. package/dist/{jobs-CEIuahbz.d.ts → jobs-BA_PnUCd.d.ts} +1 -1
  25. package/dist/{jobs-DjMcghv6.d.mts → jobs-BZqtb1gY.d.mts} +1 -1
  26. package/dist/paths.d.mts +8 -1
  27. package/dist/paths.d.ts +8 -1
  28. package/dist/paths.js +5 -1
  29. package/dist/paths.mjs +5 -3
  30. package/dist/{roles-DBgzkvEt.d.ts → roles-Bh3WNF9u.d.mts} +3 -1
  31. package/dist/{roles-DBgzkvEt.d.mts → roles-Bh3WNF9u.d.ts} +3 -1
  32. package/dist/route-contract.d.mts +10 -5
  33. package/dist/route-contract.d.ts +10 -5
  34. package/dist/route-contract.js +53 -5
  35. package/dist/route-contract.mjs +2 -2
  36. package/dist/{search-C0LiPXVb.d.mts → search-Da3TjcZ0.d.mts} +1 -1
  37. package/dist/{search-D-EjtZH3.d.ts → search-dsA-Cetf.d.ts} +1 -1
  38. package/dist/{seo-CjNGqZZH.d.ts → seo-D-n2g6X3.d.ts} +1 -1
  39. package/dist/{seo-DCQp8V1c.d.mts → seo-D3DhnHk8.d.mts} +1 -1
  40. package/dist/seo-files.d.mts +2 -2
  41. package/dist/seo-files.d.ts +2 -2
  42. package/dist/seo.d.mts +6 -6
  43. package/dist/seo.d.ts +6 -6
  44. package/dist/seo.mjs +2 -2
  45. package/dist/server.d.mts +6 -6
  46. package/dist/server.d.ts +6 -6
  47. package/dist/sitemap.d.mts +6 -6
  48. package/dist/sitemap.d.ts +6 -6
  49. package/dist/sitemap.mjs +1 -1
  50. package/dist/suggest.d.mts +2 -2
  51. package/dist/suggest.d.ts +2 -2
  52. package/dist/well-known.d.mts +4 -1
  53. package/dist/well-known.d.ts +4 -1
  54. package/dist/well-known.js +30 -1
  55. package/dist/well-known.mjs +5 -3
  56. package/package.json +1 -1
  57. package/skills/cavuno-board-talent-access/SKILL.md +7 -0
  58. package/skills/manifest.json +1 -1
@@ -1,5 +1,5 @@
1
- import { R as RouteEntry, a as RouteRole, C as ClassifyResult, b as CompileResult, V as ValidateManifestError, c as ValidateManifestResult, M as ManifestV1 } from './roles-DBgzkvEt.mjs';
2
- export { A as ALL_ROLES, d as Ambiguity, e as CANONICAL_MANIFEST, f as REQUIRED_ROLES, g as ROLE_PARAM_REGISTRY, i as isRouteRole } from './roles-DBgzkvEt.mjs';
1
+ import { R as RouteEntry, a as RouteRole, C as ClassifyResult, b as CompileResult, V as ValidateManifestError, c as ValidateManifestResult, M as ManifestV1 } from './roles-Bh3WNF9u.mjs';
2
+ export { A as ALL_ROLES, d as Ambiguity, e as CANONICAL_MANIFEST, f as REQUIRED_ROLES, g as ROLE_PARAM_REGISTRY, i as isRouteRole } from './roles-Bh3WNF9u.mjs';
3
3
 
4
4
  /**
5
5
  * Classify routes into role assignments. Markers (keyed by `sourcePath`)
@@ -57,10 +57,11 @@ declare function sameParamSet(a: readonly string[], b: readonly string[]): boole
57
57
  * - must start with '/' but NOT '//'; no '//' anywhere
58
58
  * - no '.' or '..' path segments
59
59
  * - no '?', '#', or whitespace
60
- * - charset limited to [A-Za-z0-9\-_/:.]
60
+ * - charset limited to [A-Za-z0-9\-_/:.@]
61
61
  * - params referenced by a template must be in ROLE_PARAM_REGISTRY[role]
62
62
  * - static / empty-registry roles must have zero `:` tokens
63
63
  * - parameterized roles must retain at least one registry param
64
+ * - `talentProfile` must carry fixed text around its param (not `/:handle`)
64
65
  */
65
66
  declare function checkRoleTemplate(role: RouteRole, template: string): ValidateManifestError[];
66
67
  /**
@@ -96,8 +97,10 @@ declare function createBoardLinkResolver(layers: readonly ManifestLayer[]): {
96
97
  *.
97
98
  *
98
99
  * Segment-wise matcher (no RegExp construction from templates). Templates
99
- * are validated at write time but stay defensive here: any segment that
100
- * does not start with ':' is a literal.
100
+ * are validated at write time but stay defensive here: a segment starting
101
+ * with ':' is a whole-segment param, a segment with fixed text before a
102
+ * `:name` (`@:handle`) captures what follows that text, and anything else
103
+ * is a literal.
101
104
  */
102
105
 
103
106
  /**
@@ -183,6 +186,8 @@ declare function parseReactRouterRoutes(contents: string[]): DerivedRoute[];
183
186
  * - `index` → parent path (`src/routes/index.tsx` → `/`)
184
187
  * - dots nest (`jobs.index.tsx` → `/jobs`)
185
188
  * - `$param` → dynamic segment (`jobs.$jobId.tsx` → `/jobs/:jobId`)
189
+ * - `prefix{$param}` → param with fixed text before it (`@{$handle}.tsx` →
190
+ * `/@:handle`); optional `{-$param}` groups are not supported (skipped)
186
191
  * - `__root` is layout only (skipped)
187
192
  * - `-` prefix excludes the file/folder
188
193
  * - `_` prefix is a pathless layout segment (stripped from the URL)
@@ -1,5 +1,5 @@
1
- import { R as RouteEntry, a as RouteRole, C as ClassifyResult, b as CompileResult, V as ValidateManifestError, c as ValidateManifestResult, M as ManifestV1 } from './roles-DBgzkvEt.js';
2
- export { A as ALL_ROLES, d as Ambiguity, e as CANONICAL_MANIFEST, f as REQUIRED_ROLES, g as ROLE_PARAM_REGISTRY, i as isRouteRole } from './roles-DBgzkvEt.js';
1
+ import { R as RouteEntry, a as RouteRole, C as ClassifyResult, b as CompileResult, V as ValidateManifestError, c as ValidateManifestResult, M as ManifestV1 } from './roles-Bh3WNF9u.js';
2
+ export { A as ALL_ROLES, d as Ambiguity, e as CANONICAL_MANIFEST, f as REQUIRED_ROLES, g as ROLE_PARAM_REGISTRY, i as isRouteRole } from './roles-Bh3WNF9u.js';
3
3
 
4
4
  /**
5
5
  * Classify routes into role assignments. Markers (keyed by `sourcePath`)
@@ -57,10 +57,11 @@ declare function sameParamSet(a: readonly string[], b: readonly string[]): boole
57
57
  * - must start with '/' but NOT '//'; no '//' anywhere
58
58
  * - no '.' or '..' path segments
59
59
  * - no '?', '#', or whitespace
60
- * - charset limited to [A-Za-z0-9\-_/:.]
60
+ * - charset limited to [A-Za-z0-9\-_/:.@]
61
61
  * - params referenced by a template must be in ROLE_PARAM_REGISTRY[role]
62
62
  * - static / empty-registry roles must have zero `:` tokens
63
63
  * - parameterized roles must retain at least one registry param
64
+ * - `talentProfile` must carry fixed text around its param (not `/:handle`)
64
65
  */
65
66
  declare function checkRoleTemplate(role: RouteRole, template: string): ValidateManifestError[];
66
67
  /**
@@ -96,8 +97,10 @@ declare function createBoardLinkResolver(layers: readonly ManifestLayer[]): {
96
97
  *.
97
98
  *
98
99
  * Segment-wise matcher (no RegExp construction from templates). Templates
99
- * are validated at write time but stay defensive here: any segment that
100
- * does not start with ':' is a literal.
100
+ * are validated at write time but stay defensive here: a segment starting
101
+ * with ':' is a whole-segment param, a segment with fixed text before a
102
+ * `:name` (`@:handle`) captures what follows that text, and anything else
103
+ * is a literal.
101
104
  */
102
105
 
103
106
  /**
@@ -183,6 +186,8 @@ declare function parseReactRouterRoutes(contents: string[]): DerivedRoute[];
183
186
  * - `index` → parent path (`src/routes/index.tsx` → `/`)
184
187
  * - dots nest (`jobs.index.tsx` → `/jobs`)
185
188
  * - `$param` → dynamic segment (`jobs.$jobId.tsx` → `/jobs/:jobId`)
189
+ * - `prefix{$param}` → param with fixed text before it (`@{$handle}.tsx` →
190
+ * `/@:handle`); optional `{-$param}` groups are not supported (skipped)
186
191
  * - `__root` is layout only (skipped)
187
192
  * - `-` prefix excludes the file/folder
188
193
  * - `_` prefix is a pathless layout segment (stripped from the URL)
@@ -100,6 +100,9 @@ function blogTagPath(tagSlug) {
100
100
  function blogAuthorPath(authorSlug) {
101
101
  return `/blog/author/${encodePathSegment(authorSlug)}`;
102
102
  }
103
+ function talentProfilePath(handle) {
104
+ return `/p/${encodePathSegment(handle)}`;
105
+ }
103
106
  var BOARD_PATHS = {
104
107
  home: "/",
105
108
  jobs: "/jobs",
@@ -141,6 +144,7 @@ var ROLE_PARAM_REGISTRY = {
141
144
  blogPost: ["postSlug"],
142
145
  blogTag: ["tagSlug"],
143
146
  blogAuthor: ["authorSlug"],
147
+ talentProfile: ["handle"],
144
148
  home: [],
145
149
  jobs: [],
146
150
  companies: [],
@@ -188,6 +192,7 @@ var CANONICAL_MANIFEST = {
188
192
  blogPost: blogPostPath(":postSlug"),
189
193
  blogTag: blogTagPath(":tagSlug"),
190
194
  blogAuthor: blogAuthorPath(":authorSlug"),
195
+ talentProfile: talentProfilePath(":handle"),
191
196
  home: BOARD_PATHS.home,
192
197
  jobs: BOARD_PATHS.jobs,
193
198
  companies: BOARD_PATHS.companies,
@@ -304,7 +309,8 @@ function classifyRoutes(routes, markers) {
304
309
  }
305
310
 
306
311
  // src/route-contract/validate.ts
307
- var TEMPLATE_CHARSET_RE = /^[A-Za-z0-9/_.:-]+$/;
312
+ var TEMPLATE_CHARSET_RE = /^[A-Za-z0-9/_.:@-]+$/;
313
+ var FIXED_TEXT_ROLES = /* @__PURE__ */ new Set(["talentProfile"]);
308
314
  function checkRoleTemplate(role, template) {
309
315
  const errors = [];
310
316
  if (typeof template !== "string" || template.length === 0) {
@@ -396,9 +402,19 @@ function checkRoleTemplate(role, template) {
396
402
  role
397
403
  });
398
404
  }
405
+ if (FIXED_TEXT_ROLES.has(role) && isParamsOnly(template)) {
406
+ errors.push({
407
+ code: "invalid_template",
408
+ message: `role '${role}' template must carry fixed text around its params (e.g. '/p/:handle' or '/@:handle'), got ${JSON.stringify(template)}`,
409
+ role
410
+ });
411
+ }
399
412
  }
400
413
  return errors;
401
414
  }
415
+ function isParamsOnly(template) {
416
+ return template.replace(/:[A-Za-z_][A-Za-z0-9_]*/g, "").replace(/\//g, "").length === 0;
417
+ }
402
418
  function validateManifest(value) {
403
419
  const errors = [];
404
420
  if (value === null || typeof value !== "object") {
@@ -895,7 +911,7 @@ function pathToRoute4(filePath) {
895
911
  if (rel === "route" || rel.endsWith("/route") || rel.endsWith(".route")) {
896
912
  return null;
897
913
  }
898
- if (rel.includes("[") || rel.includes("{")) return null;
914
+ if (rel.includes("[")) return null;
899
915
  const rawSegments = rel.split("/").flatMap((part) => part.split(".")).filter((seg) => seg.length > 0);
900
916
  const urlSegments = [];
901
917
  let hasDynamic = false;
@@ -909,6 +925,14 @@ function pathToRoute4(filePath) {
909
925
  hadIndex = true;
910
926
  continue;
911
927
  }
928
+ if (seg.includes("{") || seg.includes("}")) {
929
+ const pattern = tanStackBraceSegmentToPattern(seg);
930
+ if (pattern === null) return null;
931
+ hasDynamic = true;
932
+ hadConcrete = true;
933
+ urlSegments.push(pattern);
934
+ continue;
935
+ }
912
936
  if (seg.startsWith("$")) {
913
937
  hasDynamic = true;
914
938
  hadConcrete = true;
@@ -925,6 +949,15 @@ function pathToRoute4(filePath) {
925
949
  const path = urlSegments.length === 0 ? "/" : `/${urlSegments.join("/")}`;
926
950
  return { path, navigable: !hasDynamic, sourcePath: normalized };
927
951
  }
952
+ function tanStackBraceSegmentToPattern(segment) {
953
+ const match = /^([^{}$:]*)\{\$([A-Za-z_][A-Za-z0-9_]*)\}([^{}$:]*)$/.exec(
954
+ segment
955
+ );
956
+ if (match === null) return null;
957
+ const [, prefix = "", name = "", suffix = ""] = match;
958
+ if (suffix !== "") return null;
959
+ return `${prefix}:${name}`;
960
+ }
928
961
 
929
962
  // src/route-contract/enumerate/index.ts
930
963
  function enumerateDerived(paths) {
@@ -1087,6 +1120,7 @@ var ROLE_INDEX_PATHS = {
1087
1120
  blogTag: BOARD_PATHS.blog,
1088
1121
  blogAuthor: BOARD_PATHS.blog,
1089
1122
  blog: BOARD_PATHS.blog,
1123
+ talentProfile: BOARD_PATHS.talent,
1090
1124
  home: BOARD_PATHS.home,
1091
1125
  jobs: BOARD_PATHS.jobs,
1092
1126
  companies: BOARD_PATHS.companies,
@@ -1106,6 +1140,7 @@ function splitSegments(path) {
1106
1140
  if (withoutTrailing === "") return [];
1107
1141
  return withoutTrailing.split("/");
1108
1142
  }
1143
+ var PREFIXED_PARAM_RE = /^([^:]+):([A-Za-z_][A-Za-z0-9_]*)(.*)$/;
1109
1144
  function matchPathToTemplate(template, pathname) {
1110
1145
  const templateSegs = splitSegments(template);
1111
1146
  const pathSegs = splitSegments(pathname);
@@ -1116,10 +1151,23 @@ function matchPathToTemplate(template, pathname) {
1116
1151
  for (let i = 0; i < templateSegs.length; i++) {
1117
1152
  const tSeg = templateSegs[i];
1118
1153
  const pSeg = pathSegs[i];
1154
+ let name = null;
1155
+ let value = pSeg;
1119
1156
  if (tSeg.startsWith(":") && tSeg.length > 1) {
1120
- if (pSeg.length === 0) return null;
1121
- if (pSeg.includes("/") || pSeg.includes("\\")) return null;
1122
- params[tSeg.slice(1)] = pSeg;
1157
+ name = tSeg.slice(1);
1158
+ } else {
1159
+ const prefixed = PREFIXED_PARAM_RE.exec(tSeg);
1160
+ if (prefixed) {
1161
+ const [, prefix = "", param = "", suffix = ""] = prefixed;
1162
+ if (!pSeg.startsWith(prefix) || !pSeg.endsWith(suffix)) return null;
1163
+ name = param;
1164
+ value = pSeg.slice(prefix.length, pSeg.length - suffix.length);
1165
+ }
1166
+ }
1167
+ if (name !== null) {
1168
+ if (value.length === 0) return null;
1169
+ if (value.includes("/") || value.includes("\\")) return null;
1170
+ params[name] = value;
1123
1171
  continue;
1124
1172
  }
1125
1173
  if (tSeg !== pSeg) return null;
@@ -26,8 +26,8 @@ import {
26
26
  parseTanStackRoutes,
27
27
  sameParamSet,
28
28
  validateManifest
29
- } from "./chunk-T2KC6XGV.mjs";
30
- import "./chunk-BGKNXG4H.mjs";
29
+ } from "./chunk-IVSECNCD.mjs";
30
+ import "./chunk-XXQCESMU.mjs";
31
31
  export {
32
32
  ALL_ROLES,
33
33
  CANONICAL_MANIFEST,
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-DQvmsp5o.mjs';
1
+ import { S as Schemas } from './_spec-CQx5p1cM.mjs';
2
2
 
3
3
  type Awaitable<T> = T | Promise<T>;
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-DQvmsp5o.js';
1
+ import { S as Schemas } from './_spec-CQx5p1cM.js';
2
2
 
3
3
  type Awaitable<T> = T | Promise<T>;
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-DQvmsp5o.js';
1
+ import { S as Schemas } from './_spec-CQx5p1cM.js';
2
2
 
3
3
  /**
4
4
  * The public SEO-infra payload (`board.seo()`) — platform infrastructure a
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-DQvmsp5o.mjs';
1
+ import { S as Schemas } from './_spec-CQx5p1cM.mjs';
2
2
 
3
3
  /**
4
4
  * The public SEO-infra payload (`board.seo()`) — platform infrastructure a
@@ -1,5 +1,5 @@
1
- import { B as BoardSeo } from './seo-DCQp8V1c.mjs';
2
- import './_spec-DQvmsp5o.mjs';
1
+ import { B as BoardSeo } from './seo-D3DhnHk8.mjs';
2
+ import './_spec-CQx5p1cM.mjs';
3
3
 
4
4
  /**
5
5
  * `@cavuno/board/seo-files` — platform-owned crawler/identity file bodies
@@ -1,5 +1,5 @@
1
- import { B as BoardSeo } from './seo-CjNGqZZH.js';
2
- import './_spec-DQvmsp5o.js';
1
+ import { B as BoardSeo } from './seo-D-n2g6X3.js';
2
+ import './_spec-CQx5p1cM.js';
3
3
 
4
4
  /**
5
5
  * `@cavuno/board/seo-files` — platform-owned crawler/identity file bodies
package/dist/seo.d.mts CHANGED
@@ -1,9 +1,9 @@
1
- import { P as PublicBlogPostSummary, B as BlogAuthorEmbed } from './blog-CvuObn9O.mjs';
2
- import { e as JsonLdObject } from './breadcrumbs-BBvJ5gq-.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-BBvJ5gq-.mjs';
4
- import './_spec-DQvmsp5o.mjs';
5
- import './board-D6xOidUX.mjs';
6
- import './jobs-DjMcghv6.mjs';
1
+ import { P as PublicBlogPostSummary, B as BlogAuthorEmbed } from './blog-DP06wTDw.mjs';
2
+ import { e as JsonLdObject } from './breadcrumbs-Db6UTihw.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-Db6UTihw.mjs';
4
+ import './_spec-CQx5p1cM.mjs';
5
+ import './board-C7iju96a.mjs';
6
+ import './jobs-BZqtb1gY.mjs';
7
7
 
8
8
  /**
9
9
  * Blog structured data on the `@cavuno/board` wire types, transcribed from
package/dist/seo.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { P as PublicBlogPostSummary, B as BlogAuthorEmbed } from './blog-CUkvhn3c.js';
2
- import { e as JsonLdObject } from './breadcrumbs-D2Cwrju4.js';
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-D2Cwrju4.js';
4
- import './_spec-DQvmsp5o.js';
5
- import './board-CCG1pO6r.js';
6
- import './jobs-CEIuahbz.js';
1
+ import { P as PublicBlogPostSummary, B as BlogAuthorEmbed } from './blog-BwyZQX_f.js';
2
+ import { e as JsonLdObject } from './breadcrumbs-BUzF85v3.js';
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-BUzF85v3.js';
4
+ import './_spec-CQx5p1cM.js';
5
+ import './board-C2Y5fz0m.js';
6
+ import './jobs-BA_PnUCd.js';
7
7
 
8
8
  /**
9
9
  * Blog structured data on the `@cavuno/board` wire types, transcribed from
package/dist/seo.mjs CHANGED
@@ -17,11 +17,11 @@ import {
17
17
  skillSalaryJsonLd,
18
18
  sortBySeniority,
19
19
  titleSalaryJsonLd
20
- } from "./chunk-OPMJ6YH5.mjs";
20
+ } from "./chunk-KDFKVWTZ.mjs";
21
21
  import {
22
22
  boardUrl,
23
23
  jobDetailPath
24
- } from "./chunk-BGKNXG4H.mjs";
24
+ } from "./chunk-XXQCESMU.mjs";
25
25
 
26
26
  // src/seo/blog.ts
27
27
  function createBlogArticleJsonLd({
package/dist/server.d.mts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { BoardSdk } from './index.mjs';
2
- import './_spec-DQvmsp5o.mjs';
3
- import './search-C0LiPXVb.mjs';
4
- import './board-D6xOidUX.mjs';
5
- import './jobs-DjMcghv6.mjs';
6
- import './seo-DCQp8V1c.mjs';
7
- import './blog-CvuObn9O.mjs';
2
+ import './_spec-CQx5p1cM.mjs';
3
+ import './search-Da3TjcZ0.mjs';
4
+ import './board-C7iju96a.mjs';
5
+ import './jobs-BZqtb1gY.mjs';
6
+ import './seo-D3DhnHk8.mjs';
7
+ import './blog-DP06wTDw.mjs';
8
8
 
9
9
  /**
10
10
  * Session cookie codec — pure (no framework imports, no node imports) so it
package/dist/server.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { BoardSdk } from './index.js';
2
- import './_spec-DQvmsp5o.js';
3
- import './search-D-EjtZH3.js';
4
- import './board-CCG1pO6r.js';
5
- import './jobs-CEIuahbz.js';
6
- import './seo-CjNGqZZH.js';
7
- import './blog-CUkvhn3c.js';
2
+ import './_spec-CQx5p1cM.js';
3
+ import './search-dsA-Cetf.js';
4
+ import './board-C2Y5fz0m.js';
5
+ import './jobs-BA_PnUCd.js';
6
+ import './seo-D-n2g6X3.js';
7
+ import './blog-BwyZQX_f.js';
8
8
 
9
9
  /**
10
10
  * Session cookie codec — pure (no framework imports, no node imports) so it
@@ -1,10 +1,10 @@
1
1
  import { BoardSdk } from './index.mjs';
2
- import './_spec-DQvmsp5o.mjs';
3
- import './search-C0LiPXVb.mjs';
4
- import './board-D6xOidUX.mjs';
5
- import './jobs-DjMcghv6.mjs';
6
- import './seo-DCQp8V1c.mjs';
7
- import './blog-CvuObn9O.mjs';
2
+ import './_spec-CQx5p1cM.mjs';
3
+ import './search-Da3TjcZ0.mjs';
4
+ import './board-C7iju96a.mjs';
5
+ import './jobs-BZqtb1gY.mjs';
6
+ import './seo-D3DhnHk8.mjs';
7
+ import './blog-DP06wTDw.mjs';
8
8
 
9
9
  /**
10
10
  * Sitemap primitives — the pure XML + bucket-filename logic behind a board
package/dist/sitemap.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { BoardSdk } from './index.js';
2
- import './_spec-DQvmsp5o.js';
3
- import './search-D-EjtZH3.js';
4
- import './board-CCG1pO6r.js';
5
- import './jobs-CEIuahbz.js';
6
- import './seo-CjNGqZZH.js';
7
- import './blog-CUkvhn3c.js';
2
+ import './_spec-CQx5p1cM.js';
3
+ import './search-dsA-Cetf.js';
4
+ import './board-C2Y5fz0m.js';
5
+ import './jobs-BA_PnUCd.js';
6
+ import './seo-D-n2g6X3.js';
7
+ import './blog-BwyZQX_f.js';
8
8
 
9
9
  /**
10
10
  * Sitemap primitives — the pure XML + bucket-filename logic behind a board
package/dist/sitemap.mjs CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  salaryLocationPath,
20
20
  salarySkillPath,
21
21
  salaryTitlePath
22
- } from "./chunk-BGKNXG4H.mjs";
22
+ } from "./chunk-XXQCESMU.mjs";
23
23
 
24
24
  // src/sitemap/xml.ts
25
25
  var SITEMAP_BUCKETS = [
@@ -1,5 +1,5 @@
1
- import { h as SuggestionItem, b as SearchSuggestQuery, F as FetchOptions, g as SuggestResult } from './search-C0LiPXVb.mjs';
2
- import './_spec-DQvmsp5o.mjs';
1
+ import { h as SuggestionItem, b as SearchSuggestQuery, F as FetchOptions, g as SuggestResult } from './search-Da3TjcZ0.mjs';
2
+ import './_spec-CQx5p1cM.mjs';
3
3
 
4
4
  /**
5
5
  * `@cavuno/board/suggest` — framework-agnostic headless search-suggest
package/dist/suggest.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { h as SuggestionItem, b as SearchSuggestQuery, F as FetchOptions, g as SuggestResult } from './search-D-EjtZH3.js';
2
- import './_spec-DQvmsp5o.js';
1
+ import { h as SuggestionItem, b as SearchSuggestQuery, F as FetchOptions, g as SuggestResult } from './search-dsA-Cetf.js';
2
+ import './_spec-CQx5p1cM.js';
3
3
 
4
4
  /**
5
5
  * `@cavuno/board/suggest` — framework-agnostic headless search-suggest
@@ -1,4 +1,4 @@
1
- import { R as RouteEntry, a as RouteRole } from './roles-DBgzkvEt.mjs';
1
+ import { R as RouteEntry, a as RouteRole } from './roles-Bh3WNF9u.mjs';
2
2
 
3
3
  /**
4
4
  * Runtime well-known endpoint — Request → Response serving ManifestV1
@@ -108,6 +108,9 @@ declare function routeEntriesFromTanStackRouteTree(routeTree: TanStackRouteNode)
108
108
  * Convert a TanStack path template to URLPattern `:param` / `*` form.
109
109
  * `/jobs/$categorySlug` → `/jobs/:categorySlug`
110
110
  * `/files/$` → `/files/*`
111
+ * `/@{$handle}` → `/@:handle`
112
+ * A brace segment that has no faithful `:param` form (optional `{-$x}`,
113
+ * or a suffix that would extend the param name) is left as written.
111
114
  */
112
115
  declare function tanStackPathToUrlPattern(path: string): string;
113
116
 
@@ -1,4 +1,4 @@
1
- import { R as RouteEntry, a as RouteRole } from './roles-DBgzkvEt.js';
1
+ import { R as RouteEntry, a as RouteRole } from './roles-Bh3WNF9u.js';
2
2
 
3
3
  /**
4
4
  * Runtime well-known endpoint — Request → Response serving ManifestV1
@@ -108,6 +108,9 @@ declare function routeEntriesFromTanStackRouteTree(routeTree: TanStackRouteNode)
108
108
  * Convert a TanStack path template to URLPattern `:param` / `*` form.
109
109
  * `/jobs/$categorySlug` → `/jobs/:categorySlug`
110
110
  * `/files/$` → `/files/*`
111
+ * `/@{$handle}` → `/@:handle`
112
+ * A brace segment that has no faithful `:param` form (optional `{-$x}`,
113
+ * or a suffix that would extend the param name) is left as written.
111
114
  */
112
115
  declare function tanStackPathToUrlPattern(path: string): string;
113
116
 
@@ -81,6 +81,9 @@ function blogTagPath(tagSlug) {
81
81
  function blogAuthorPath(authorSlug) {
82
82
  return `/blog/author/${encodePathSegment(authorSlug)}`;
83
83
  }
84
+ function talentProfilePath(handle) {
85
+ return `/p/${encodePathSegment(handle)}`;
86
+ }
84
87
  var BOARD_PATHS = {
85
88
  home: "/",
86
89
  jobs: "/jobs",
@@ -122,6 +125,7 @@ var ROLE_PARAM_REGISTRY = {
122
125
  blogPost: ["postSlug"],
123
126
  blogTag: ["tagSlug"],
124
127
  blogAuthor: ["authorSlug"],
128
+ talentProfile: ["handle"],
125
129
  home: [],
126
130
  jobs: [],
127
131
  companies: [],
@@ -166,6 +170,7 @@ var CANONICAL_MANIFEST = {
166
170
  blogPost: blogPostPath(":postSlug"),
167
171
  blogTag: blogTagPath(":tagSlug"),
168
172
  blogAuthor: blogAuthorPath(":authorSlug"),
173
+ talentProfile: talentProfilePath(":handle"),
169
174
  home: BOARD_PATHS.home,
170
175
  jobs: BOARD_PATHS.jobs,
171
176
  companies: BOARD_PATHS.companies,
@@ -282,7 +287,8 @@ function classifyRoutes(routes, markers) {
282
287
  }
283
288
 
284
289
  // src/route-contract/validate.ts
285
- var TEMPLATE_CHARSET_RE = /^[A-Za-z0-9/_.:-]+$/;
290
+ var TEMPLATE_CHARSET_RE = /^[A-Za-z0-9/_.:@-]+$/;
291
+ var FIXED_TEXT_ROLES = /* @__PURE__ */ new Set(["talentProfile"]);
286
292
  function checkRoleTemplate(role, template) {
287
293
  const errors = [];
288
294
  if (typeof template !== "string" || template.length === 0) {
@@ -374,9 +380,19 @@ function checkRoleTemplate(role, template) {
374
380
  role
375
381
  });
376
382
  }
383
+ if (FIXED_TEXT_ROLES.has(role) && isParamsOnly(template)) {
384
+ errors.push({
385
+ code: "invalid_template",
386
+ message: `role '${role}' template must carry fixed text around its params (e.g. '/p/:handle' or '/@:handle'), got ${JSON.stringify(template)}`,
387
+ role
388
+ });
389
+ }
377
390
  }
378
391
  return errors;
379
392
  }
393
+ function isParamsOnly(template) {
394
+ return template.replace(/:[A-Za-z_][A-Za-z0-9_]*/g, "").replace(/\//g, "").length === 0;
395
+ }
380
396
 
381
397
  // src/route-contract/compile.ts
382
398
  var REQUIRED_SET = new Set(REQUIRED_ROLES);
@@ -425,6 +441,17 @@ function compileManifest(routes, markers) {
425
441
  };
426
442
  }
427
443
 
444
+ // src/route-contract/enumerate/tanstack.ts
445
+ function tanStackBraceSegmentToPattern(segment) {
446
+ const match = /^([^{}$:]*)\{\$([A-Za-z_][A-Za-z0-9_]*)\}([^{}$:]*)$/.exec(
447
+ segment
448
+ );
449
+ if (match === null) return null;
450
+ const [, prefix = "", name = "", suffix = ""] = match;
451
+ if (suffix !== "") return null;
452
+ return `${prefix}:${name}`;
453
+ }
454
+
428
455
  // src/route-contract/resolve-links.ts
429
456
  var REQUIRED_SET2 = new Set(REQUIRED_ROLES);
430
457
 
@@ -449,6 +476,7 @@ var ROLE_INDEX_PATHS = {
449
476
  blogTag: BOARD_PATHS.blog,
450
477
  blogAuthor: BOARD_PATHS.blog,
451
478
  blog: BOARD_PATHS.blog,
479
+ talentProfile: BOARD_PATHS.talent,
452
480
  home: BOARD_PATHS.home,
453
481
  jobs: BOARD_PATHS.jobs,
454
482
  companies: BOARD_PATHS.companies,
@@ -555,6 +583,7 @@ function tanStackPathToUrlPattern(path) {
555
583
  const converted = segments.map((seg) => {
556
584
  if (seg === "") return seg;
557
585
  if (seg === "$") return "*";
586
+ if (seg.includes("{")) return tanStackBraceSegmentToPattern(seg) ?? seg;
558
587
  if (seg.startsWith("$") && seg.endsWith("?")) {
559
588
  return `:${seg.slice(1, -1)}`;
560
589
  }
@@ -1,7 +1,8 @@
1
1
  import {
2
- compileManifest
3
- } from "./chunk-T2KC6XGV.mjs";
4
- import "./chunk-BGKNXG4H.mjs";
2
+ compileManifest,
3
+ tanStackBraceSegmentToPattern
4
+ } from "./chunk-IVSECNCD.mjs";
5
+ import "./chunk-XXQCESMU.mjs";
5
6
 
6
7
  // src/well-known/handler.ts
7
8
  var WELL_KNOWN_CACHE_CONTROL = "public, max-age=300";
@@ -95,6 +96,7 @@ function tanStackPathToUrlPattern(path) {
95
96
  const converted = segments.map((seg) => {
96
97
  if (seg === "") return seg;
97
98
  if (seg === "$") return "*";
99
+ if (seg.includes("{")) return tanStackBraceSegmentToPattern(seg) ?? seg;
98
100
  if (seg.startsWith("$") && seg.endsWith("?")) {
99
101
  return `:${seg.slice(1, -1)}`;
100
102
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cavuno/board",
3
- "version": "4.28.0",
3
+ "version": "4.29.0",
4
4
  "description": "Build custom job boards and careers pages with Cavuno’s TypeScript SDK.",
5
5
  "keywords": [
6
6
  "api-client",
@@ -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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.28.0",
2
+ "version": "4.29.0",
3
3
  "skills": [
4
4
  {
5
5
  "name": "cavuno-board-account",