update position page design

This commit is contained in:
2026-08-13 21:41:14 +05:30
parent 6340962907
commit 1c653264b5
124 changed files with 5746 additions and 596 deletions

View File

@@ -91,6 +91,43 @@ function sectionSteps(body, heading) {
.filter(Boolean);
}
/**
* The prose under a `## Heading`, with its bullets and blank lines stripped to
* one line of summary. Used for a level's description, which is a sentence
* rather than a list.
*/
function sectionText(body, heading) {
const section = new RegExp(`##\\s+${heading}\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$)`, 'i').exec(body);
if (!section) return '';
return section[1]
.split(/\r?\n/)
.map((l) => l.replace(/^\s*[-*]\s+/, '').trim())
.filter(Boolean)
.join(' ')
.trim();
}
/**
* The rungs a workforce skill defines, read from its own body.
*
* A definition names its ladder as `## Beginner`, `## Intermediate` and so on,
* each followed by what a person must be able to do at that level. Only the
* headings that are actually present become rungs, so a skill that tops out at
* Advanced has a three-rung ladder rather than a fourth empty one — the ladder
* is what the author wrote, not a fixed shape they are padded into.
*/
const LEVEL_HEADINGS = ['Beginner', 'Intermediate', 'Advanced', 'Expert'];
function sectionLevels(body) {
return LEVEL_HEADINGS
.map((heading) => ({
level: heading.toLowerCase(),
label: heading,
summary: sectionText(body, heading),
}))
.filter((rung) => rung.summary);
}
/**
* The page key a route belongs to — `/admin/positions` → `positions`.
*
@@ -128,12 +165,44 @@ export function parseSkill(raw, { path = 'custom', custom = false } = {}) {
{
const { data, body } = parseFrontmatter(raw);
const pages = Array.isArray(data.pages) ? data.pages : [];
const id = data.id || path.split('/').pop().replace(/\.md$/, '');
const levels = sectionLevels(body);
/**
* Two things wear the same format.
*
* An *assistant* skill teaches Owliver to do something on a page. A
* *workforce* skill defines a capability the workforce can hold, at levels,
* and says which pages may surface it. A definition that names a ladder is
* the second kind — nothing else distinguishes them, so an author declares a
* workforce skill by writing one, not by setting a flag.
*/
const kind = data.kind || (levels.length ? 'workforce' : 'assistant');
return {
id: data.id || path.split('/').pop().replace(/\.md$/, ''),
id,
kind,
name: data.name || 'Untitled skill',
description: data.description || '',
status: data.status === 'inactive' ? 'inactive' : 'active',
pages,
/**
* The capability in the skill graph this definition governs.
*
* Declared as `skill:`, or inferred by dropping a `-training` suffix and
* swapping dashes for underscores — so `server-training.md` governs
* `server` and `customer-service-training.md` governs `customer_service`
* without the author restating it. Only meaningful for workforce skills.
*/
skillId: kind === 'workforce'
? (data.skill || id.replace(/-training$/, '')).replace(/-/g, '_')
: null,
/* The ladder, in order, each rung carrying what it means to hold it. */
levels,
/* The definition as written. Forge edits this; every other page reads the
parsed form, so there is exactly one artefact behind all of them. */
markdown: raw,
source: custom ? 'account' : 'repository',
/* The page label Settings shows, taken from the context the page carries
so the two never disagree. */
actions: Array.isArray(data.actions) ? data.actions : [],
@@ -214,18 +283,42 @@ export function contextIdsForSkill(skill) {
return skill.pages.map((key) => ROUTE_BY_PAGE_KEY[key]?.contextId).filter(Boolean);
}
/**
* Every skill attached to a page — the one answer to "what belongs here".
*
* This is what `pages:` is for. A definition listing `positions` is not
* documenting itself; it is saying that the Positions experience may surface it,
* and this function is the only place that question is answered. Pages call it
* and render what comes back, which is what lets a skill authored tomorrow
* appear on the right pages tonight without any page component being edited.
*
* Nothing downstream may test a page name against a skill id. The moment a page
* asks "is this Server Training?" the attachment has stopped being data.
*/
export function getSkillsForPage(pageId, { disabled = [], customSources = [], kind } = {}) {
if (!pageId) return [];
return allSkills(customSources).filter(
(s) => s.status === 'active'
&& !disabled.includes(s.id)
&& s.pages.includes(pageId)
&& (!kind || s.kind === kind)
);
}
/**
* Skills available on a page.
*
* `disabled` is the account's list of switched-off skill ids, so a skill can be
* turned off from Settings without being deleted from disk.
*
* Assistant skills only: a workforce skill defines a capability, not something
* Owliver can be asked to do, and offering one as a chat action would promise
* behaviour that does not exist.
*/
export function skillsForContext(contextId, disabled = [], customSources = []) {
const pageKey = PAGE_KEY_BY_CONTEXT[contextId];
if (!pageKey) return [];
return allSkills(customSources).filter(
(s) => s.status === 'active' && !disabled.includes(s.id) && s.pages.includes(pageKey)
);
return getSkillsForPage(pageKey, { disabled, customSources, kind: 'assistant' });
}
/**