update Markdown skills

This commit is contained in:
2026-08-19 17:36:27 +05:30
parent aa2fb41871
commit d3f7f439f6
30 changed files with 2341 additions and 221 deletions

View File

@@ -27,6 +27,7 @@ export const SKILL_SURFACES = [
label: 'Control Center',
route: '/admin',
placements: ['after-header', 'before-footer'],
provides: {},
},
{
id: 'positions',
@@ -53,6 +54,26 @@ export const SKILL_SURFACES = [
'after-candidates',
'before-footer',
],
/**
* What each of those placements actually hands a section, read off the
* `<SkillSurface>` call sites rather than assumed from the surface.
*
* The distinction is the whole point: the two list placements render above
* and below the grid with no position in context, while every other one
* renders inside a card, a drawer or the position page and passes the
* record. A section reading `position.*` is answerable at one and not at
* the other, and until this was written down both validated identically.
*/
provides: {
'after-position-list-summary': [],
'after-position-list': [],
'after-header': ['positionId'],
'after-position-card': ['positionId'],
'after-position-summary': ['positionId'],
'before-candidates': ['positionId'],
'after-candidates': ['positionId'],
'before-footer': ['positionId'],
},
},
{
/* The authoring form, which is a surface in its own right: what a skill has
@@ -69,12 +90,24 @@ export const SKILL_SURFACES = [
'after-vetting-weights',
'before-footer',
],
/* The draft in the form is the position, so every placement here supplies
one — which is what lets `position.vetting` be read and written while the
role is still being specified. */
provides: {
'after-header': ['positionId'],
'after-job-description': ['positionId'],
'after-vetting-weights': ['positionId'],
'before-footer': ['positionId'],
},
},
{
id: 'candidates',
label: 'Candidates',
route: '/admin/candidates',
placements: ['after-header', 'after-candidate-summary', 'before-footer'],
/* Only the summary placement renders against one person — it is mounted on
the candidate profile, not on the list. */
provides: { 'after-candidate-summary': ['candidateId'] },
},
{
id: 'hired-history',
@@ -82,12 +115,15 @@ export const SKILL_SURFACES = [
route: '/admin/hired',
aliases: ['hired'],
placements: ['after-header', 'before-footer'],
/* `before-footer` is inside the record drawer; `after-header` is the page. */
provides: { 'before-footer': ['candidateId'] },
},
{
id: 'talent-pool',
label: 'Talent Pool',
route: '/admin/talent-pool',
placements: ['after-header', 'before-footer'],
provides: {},
},
{
id: 'krow-forge',
@@ -95,18 +131,21 @@ export const SKILL_SURFACES = [
route: '/admin/university',
aliases: ['university', 'forge'],
placements: ['after-header', 'before-footer'],
provides: {},
},
{
id: 'analytics',
label: 'Analytics',
route: '/admin/analytics',
placements: ['after-header', 'before-footer'],
provides: {},
},
{
id: 'activity',
label: 'Activity',
route: '/admin/activity',
placements: ['after-header', 'before-footer'],
provides: {},
},
/* Not in the eight product surfaces, but skills already attach to it and the
account page reads them. Kept so nothing that works today stops working. */
@@ -115,12 +154,14 @@ export const SKILL_SURFACES = [
label: 'Profile',
route: '/admin/profile',
placements: ['after-header', 'before-footer'],
provides: {},
},
{
id: 'candidates-analysis',
label: 'Candidate Analysis',
route: '/admin/candidates-analysis',
placements: ['after-header', 'before-footer'],
provides: {},
},
];
@@ -133,12 +174,111 @@ for (const surface of SKILL_SURFACES) {
/** Every name a definition may use for a surface, for error messages. */
export const SUPPORTED_SKILL_PAGES = SKILL_SURFACES.map((s) => s.id);
/** The surface a declared page name refers to, or null. */
export const surfaceFor = (page) => BY_KEY.get(String(page || '').trim()) || null;
/**
* The surface a declared page name refers to, or null.
*
* Matched on the normalized name, not the literal one. A definition writing
* `Positions` or `Talent Pool` — which is how the page is spelled everywhere in
* the product — used to resolve to nothing, and a page name that resolves to
* nothing takes the whole `ui:` block with it. The keys stay canonical; only
* what an author may type to reach them widens.
*/
const normalizeKey = (page) => String(page ?? '')
.trim()
.toLowerCase()
.replace(/[\s_]+/g, '-');
export const surfaceFor = (page) => BY_KEY.get(normalizeKey(page)) || null;
/**
* Placement names a definition may use, beyond the canonical ones.
*
* A placement is a position in a page's layout, and the canonical names are
* written from the page's point of view — `after-position-card`. Authors write
* them from the definition's: `grid-card` is where it goes, `panel` is what it
* looks like. An alias only ever resolves to a placement the surface really
* offers, so the vocabulary stays exactly as closed as it was; what changes is
* how many ways there are to name a member of it.
*/
const PLACEMENT_ALIASES = {
'grid-card': 'after-position-card',
card: 'after-position-card',
panel: 'after-header',
top: 'after-header',
header: 'after-header',
footer: 'before-footer',
bottom: 'before-footer',
list: 'after-position-list',
summary: 'after-position-summary',
};
/**
* The canonical placement a declared name refers to on this surface, or null.
*
* An alias that points at a placement this surface does not offer resolves to
* null rather than to some other surface's placement — `panel` means
* `after-header` where there is one and nothing where there is not.
*/
export function placementFor(page, placement) {
const surface = surfaceFor(page);
if (!surface) return null;
const declared = normalizeKey(placement);
if (!declared) return null;
if (surface.placements.includes(declared)) return declared;
const aliased = PLACEMENT_ALIASES[declared];
return aliased && surface.placements.includes(aliased) ? aliased : null;
}
/** The canonical id for a declared page name — `hired` → `hired-history`. */
export const canonicalPage = (page) => surfaceFor(page)?.id || null;
/**
* What a placement hands a section, as the page actually mounts it.
*
* A source declares the record it needs — a position, a candidate, or nothing —
* and until this existed nothing compared that need against the pages a
* definition named. So a section reading `position.activity` could be attached
* to Analytics, validate cleanly, register, draw its title, and then report
* "This section needs a position to read" forever. That is the whole of the
* "the skill saved but does nothing" report, and it is an authoring mistake the
* product can catch rather than one the reader has to discover.
*
* Read per *placement*, never per surface: the Positions grid renders one
* section inside each card, with a position, and two more above and below the
* grid, with none. Both are `positions`.
*
* With no placement named, a definition is answered for the surface's default —
* `surface.placements[0]`, which is what `normalizeSection` fills in.
*/
export function placementProvides(page, placement = null) {
const surface = surfaceFor(page);
if (!surface) return [];
const at = String(placement || '').trim() || surface.placements[0];
return surface.provides?.[at] || [];
}
/**
* Everything the declared pages can supply at this placement, deduplicated.
*
* One page supplying a position is enough — a definition naming several pages
* is saying it belongs on all of them, and refusing it because one cannot
* answer would refuse the definition for the pages that can. The load-time
* diagnostic is where the partial case is reported.
*/
export function contextSuppliedBy(pages = [], placement = null) {
return [...new Set(
(Array.isArray(pages) ? pages : [pages])
.flatMap((page) => placementProvides(page, placement))
)];
}
/** "a position" / "a candidate", for a message an author can act on. */
export const contextLabel = (need) => (
need === 'positionId' ? 'a position' : need === 'candidateId' ? 'a candidate' : 'nothing'
);
/**
* The surface an Admin route belongs to — `/admin/positions/new` →
* `create-position`.