update Markdown skills
This commit is contained in:
@@ -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`.
|
||||
|
||||
Reference in New Issue
Block a user