Phase 5. Twelve modules under `src/lib` and `src/lib/admin`. All twelve emit
byte-identical JavaScript; eight needed no annotation at all.
Where the generated entity types fit, they are used. `positionModel` is typed
against `JobPosting` — and that is where TypeScript earned its keep. Annotating
the label functions made `experienceLabel`'s `years === ''` guard a comparison
the compiler called impossible, because the registry types
`min_experience_years` as `number`, which is correct for a record the API has
returned. The guard is not dead: the same functions are handed drafts, and an
untouched numeric form input yields `''` — which is why `toPositionPayload`
coerces all five numerics with `Number(...)`.
So the module now has two types rather than one. `PositionRecord` is a saved
posting with the registry's column types; `PositionDraft` widens the five
numerics to `number | string` and is taken by `toPositionPayload` alone. The
one comparison the split cannot express keeps its guard and carries a cast with
the reason written next to it. Deleting a live guard to satisfy a type would be
the type rewriting the code.
`workforce` keeps its records as `any`: 574 lines of demand and availability
arithmetic over profiles, postings, assignments and staff read largely through
jsonb columns the registry does not describe. What IS described is the module's
own contract — the `WorkforceContext` option bag and the `Availability` result,
whose two shapes differ by whether a worker's commitments are known.
Two of my own type declarations were too narrow and were caught by the
set-difference rather than by inspection. `activitySignals`' accumulator seeds
`{ email, name, count, privileged }` and I had named only the two counters;
`WorkforceContext` omitted `profiles` and `courses`, which `PositionDetail`
passes in a single call with three more. The bag now carries an index signature,
because that is what the call site assumes: callers hand the whole thing over
and each function picks what it needs.
`skillGraph` gains a `SkillLevel` interface with an optional `earned`, set in a
second pass that stops at the first incomplete rung — so the levels above the
gap never receive it, and optional is the honest description.
Verified: tsc 40 -> 37, zero introduced; all twelve emitted outputs
byte-identical; npm test 1684/1691 with the same seven failures; lint 0 errors;
build succeeds with the API origin inlined; baseline artifacts untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
29 lines
1.6 KiB
TypeScript
29 lines
1.6 KiB
TypeScript
/**
|
|
* The administrator's permission scopes.
|
|
*
|
|
* Lives outside the page because two things now read it: the Profile page
|
|
* renders it, and Owliver answers "what are my permissions?" from it. One list,
|
|
* so the panel can never describe a scope the page does not show.
|
|
*/
|
|
export const PERMISSIONS = [
|
|
{ scope: 'Positions', level: 'Full access', detail: 'Create, edit, pause and close roles' },
|
|
{ scope: 'Candidates', level: 'Full access', detail: 'Screen, advance, decline and hire' },
|
|
{ scope: 'Talent Pool', level: 'Full access', detail: 'Search, contact and shortlist' },
|
|
{ scope: 'Analytics', level: 'Read only', detail: 'View reports and export' },
|
|
{ scope: 'Audit log', level: 'Read only', detail: 'View all platform activity' },
|
|
{ scope: 'Billing', level: 'No access', detail: 'Restricted to account owners' },
|
|
];
|
|
|
|
export const LEVEL_TONE = { 'Full access': 'success', 'Read only': 'info', 'No access': 'neutral' };
|
|
|
|
/** What the Profile page can actually do, for the assistant to point at. */
|
|
export const PROFILE_ACTIONS = [
|
|
{ label: 'Edit profile', detail: 'Change your display name' },
|
|
{ label: 'Change password', detail: 'Rotate your administrator password' },
|
|
{ label: 'Manage sessions', detail: 'Review devices signed in to this account' },
|
|
{ label: 'Two-factor authentication', detail: 'Required for privileged actions' },
|
|
{ label: 'Owliver workspace', detail: 'Open the contextual panel by default on supported pages' },
|
|
{ label: 'Compact density', detail: 'Tighter row heights across Admin tables' },
|
|
{ label: 'Email digest', detail: 'A daily summary of what needs attention' },
|
|
];
|