refactor(ts-migration): Phase 11 batch 2 — the skills-layer leaves

The eight modules under `src/lib/skills/` that import nothing from their
own layer, plus `uiConfig`, which imports only `surfaces`. Renamed and
annotated; no logic touched.

All eight emit byte-identical minified JavaScript, and the production
bundle still hashes to `74d17e2d…`. Four of them are R100 — not one
character changed beyond the extension.

The annotations are four fixes of the same two kinds:

  - `Object.entries<any>` / `Object.values<any>` at three sites. Passing
    an `any` value to either yields `unknown`, not `any`, because
    inference into the union parameter of their signatures does not
    distribute — so `weightsOf`'s entries arrived unsortable and two
    `reduce` accumulators arrived un-addable. The explicit type argument
    restores what JavaScript had. No cast, no runtime change.
  - `FlowReply` as `assignmentPreview`'s return type. Two of its five
    branches genuinely return no `followUp` — "already fully staffed"
    and "nobody is both qualified and free" are answers with nothing to
    offer next — so `followUp` is optional, which is what `headcountSet`
    has always passed through.

One inert `/** @param {any} */` in `saveFeedback` became a real
annotation. Left as a comment it would have read as if it still did
something.

Measured against `eaa677f`, all unchanged:

  typecheck   20 errors, same files
  lint        exit 0, 0 errors, 289 warnings
  npm test    1684/1691, the same 7 failures verbatim
  build       exit 0, identical bundle hash
  emitted JS  8/8 byte-identical

One number moved and it is a reporting artifact, recorded here so it is
not misread next time: ESLint's file count went 269 -> 261. `src/lib/**`
is in `ignores` for both config blocks and always has been, so no rule
has ever run on these files. A `.js` file there is still walked by
ESLint's default `**/*.js` glob and then ignored, which produces an entry
with zero messages; a `.ts` file matches no `files` pattern, so it is
never walked and produces no entry at all. Confirmed directly: linting
`registry.js` reports nothing, linting `yaml.ts` reports "File ignored
because no matching configuration was supplied." Zero rules applied
before, zero after. The counts that carry signal — 0 errors, 289
warnings — did not move.

No baseline artifact touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
This commit is contained in:
2026-09-18 13:55:59 +05:30
parent eaa677f815
commit e73929f47e
8 changed files with 22 additions and 6 deletions

View File

@@ -228,7 +228,7 @@ export const noDraftReply = () => doc(
/** The weights this position actually carries, never a default dressed as one. */
export function weightsOf(position) {
const criteria = position?.vetting_criteria || {};
const entries = Object.entries(criteria).filter(([, value]) => Number.isFinite(Number(value)));
const entries = Object.entries<any>(criteria).filter(([, value]) => Number.isFinite(Number(value)));
return {
entries,
total: entries.reduce((sum, [, value]) => sum + Number(value), 0),
@@ -298,7 +298,7 @@ export function weightsFromQuestion(question, position) {
}
export const weightsSetReply = (position, next) => {
const total = Object.values(next).reduce((sum, value) => sum + Number(value), 0);
const total = Object.values<any>(next).reduce((sum, value) => sum + Number(value), 0);
return doc(
heading('Vetting weights updated', position.title),

View File

@@ -16,8 +16,7 @@ import { toast } from '@/components/ds';
*/
export function reportSave(message) {
return {
/** @param {any} result */
onSuccess: (result) => {
onSuccess: (result: any) => {
if (result?.persisted === false) {
toast.error(
`${message} — but this browser would not store it, so it will be gone on reload. `

View File

@@ -394,4 +394,4 @@ export const sectionsForPage = (skill, page) => {
/** How many UI sections a definition registers, across every page. */
export const countSections = (skill) =>
Object.values(skill?.ui || {}).reduce((n, page) => n + (page.sections?.length || 0), 0);
Object.values<any>(skill?.ui || {}).reduce((n, page) => n + (page.sections?.length || 0), 0);

View File

@@ -580,7 +580,24 @@ export function headcountFailed(position) {
* position title, so accepting it goes back through the same resolution path a
* typed sentence would — and re-checks the data before writing.
*/
export function assignmentPreview(position, context) {
/**
* A reply from this module: a document, and sometimes chips to offer next.
*
* `followUp` is optional because two of the five branches genuinely omit it —
* "already fully staffed" and "nobody is both qualified and free" are answers
* with nothing to offer after them. Callers that spread it on, like
* `headcountSet`, therefore pass `undefined` through in those cases, which is
* what this has always done at run time.
*
* Both fields are `any`: the blocks are the document format's own structures
* and describing them belongs to that format, not here.
*/
export interface FlowReply {
doc: any;
followUp?: any;
}
export function assignmentPreview(position, context): FlowReply {
const plan = prepareAssignment(position, context);
const demand = demandFor(position, context);