2f9b3baa56e3e975f2da755d49df177a6454f354
71 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 2f9b3baa56 |
chore(ts-migration): remove the migration-only TypeScript configuration
`src` holds 321 TypeScript files and no JavaScript, so four settings that
existed only to let the two coexist have nothing left to act on. Each was
removed on its own and validated before the next.
allowJs / checkJs Removed together, because they are coupled: `checkJs`
without `allowJs` is TS5052, a configuration error
rather than a code one. Verified first that nothing
under `src` imports a `.js` module. The program still
resolves the same 321 files and still reports 0 errors.
This also closes a door. With `allowJs` on, a `.js`
file added under `src` would be compiled and bundled
silently; now it is a resolution failure, which is the
right outcome for a codebase that has finished
migrating.
jsconfig.json Deleted. It had been inert since `tsconfig.json`
appeared — TypeScript ignores a jsconfig wherever a
tsconfig sits beside it — and it still carried the old
`types: []` and the three stale excludes. A second,
unread copy of the options is an invitation to edit the
wrong file.
components.json `"tsx": false` -> `true`, so `npx shadcn add` emits
TSX. No runtime effect; it would have quietly
reintroduced `.jsx` into a repository that has none.
`README.md` documented `npm run typecheck` as `tsc -p ./jsconfig.json`
with `checkJs`, which named a file that no longer exists. Corrected, along
with a stale assertion count in the same table (835, against 1691 today).
No file under `src` changed, so there is nothing for a per-file emitted-JS
comparison to compare; the bundle hash is the check that matters and it is
unmoved.
typecheck 0 errors, 321 files in the program
lint exit 0
npm test 1684/1691, the same 7 failures
owliver unchanged; baselines still pinned, not recaptured
build exit 0, bundle 74d17e2d… identical
Left in place deliberately, reported rather than removed:
`scripts/ssr-resolve.mjs`. Its own comment says it can go once `src` holds
no `.js`, but that is only half the condition — the 203 literal `.js`
paths inside `skill-check.mjs` would all have to be renamed first, and
that is a large diff against the file that guards this migration.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
|
|||
| e8207038dd |
refactor(ts-migration): type src/api and the first of src/lib for noImplicitAny
Phase 12 step 1, in progress. The flag is not enabled yet — these are the
annotations it will require, landed first so the switch itself is a
one-line commit with a number that is already zero.
`noImplicitAny` projects 3496 errors across 242 files. This clears 179 of
them in 7 files: all of `src/api` (107 -> 0) and `workforce`,
`hiringRecords` in `src/lib`.
The leverage is real and worth recording, because it shapes the rest of
the step. Eight annotations on `aiEngine`'s prompt readers cleared 27
errors: where a value is `any`, every callback beneath it — `.map((c) =>
…)`, `.filter((r) => …)` — has no contextual type and errors on its own.
Typing the source fixes the callbacks for free, so this works bottom-up,
sources first.
Types are taken from what already exists wherever possible. The generated
entity types fit `workforce` and `hiringRecords` without a single
cascade: `JobPosting`, `JobApplication`, `Staff`, `AIInterview`,
`WorkerProfile`, `Assignment`, `Course`. `PreferencesUpdateResult` in
`src/types/user.ts` already described `updatePreferences`'s return.
`buildInsights` takes the other builders' outputs, so its parameters are
`ReturnType<typeof byDepartment>` and friends rather than a restatement
that could drift.
Two things are recorded rather than fixed:
`buildHires` probes four fields that are not `staff` columns —
`timeToHire`, `score`, `company` and `department` are absent from
`information_schema` and from the generated `Staff`. They are read as
fallbacks, so at run time they are always `undefined` and the other
branch always wins. `HireSourceRow` writes them down as optional so the
dead fallbacks are visible; removing the reads would be a behaviour
change. `Hire` likewise widens `profile_tier`, because the builder's
default `'skilled'` is lower-case where the column's check constraint
spells it `'Skilled'`.
`aiEngine`'s talent pool stays `any[]`. It is `JSON.parse` output from a
block embedded in a prompt, carrying computed fields like `match_score`
that no entity declares — typing it `WorkerProfile[]` would assert a
shape nothing validates.
One mistake worth keeping. I replaced an inline lookup with a hoisted
`const URGENCY = {…}`, and per-file esbuild said the output was
unchanged: with `--minify-syntax` it inlines a single-use const straight
back. The production bundle disagreed — `43e7f268` against `74d17e2d`.
Reverted to a type assertion, which erases. Hoisting reads as a tidy-up
and is a real change to the emitted code; the two checks disagreeing is
exactly why both are run.
typecheck 0 under the committed config; 3317 under the probe, from 3496
lint exit 0
npm test 1684/1691, the same 7 failures
build exit 0, bundle back to 74d17e2d…
emitted JS 7/7 identical
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
|
|||
| 21133a6064 |
chore(ts-migration): make the tsconfig root set authoritative
Phase 12 prerequisite. `exclude` still carried `src/components/ui`, `src/api` and `src/lib` from `jsconfig.json`; each was meant to come off as its tree was converted, in Phases 4, 5 and 6, and all three were missed. The miss was quiet by construction. Excluding a file only keeps it out of the ROOT set — anything an included file imports is pulled in anyway — so 318 of 321 files were being checked regardless, and the omission cost nothing visible. The three that were not checked are the ones nothing under `src` imports: `api/seed.ts`, `api/attendanceSeed.ts` and `lib/skills/positionFlow.ts`. They are not dead code. `scripts/` loads them, `seed.ts` from six separate places, and they held four real errors that no previous count in this migration has included. All four were one cause. `MODULE_TABLE` is 36 rows of mixed literals, so it infers as an array of the UNION of its column types and destructuring a row gives every field `string | number | string[]` — `SKILL_CATEGORY[skill_id]` then refuses a key that might be an array. It is now written as the tuple it is. The migration plan predicted this class of error for three other tables by name. Doing this before the ratchet rather than after: a strictness flag measured against an incomplete root set produces a number that grows again later for reasons unrelated to the flag, and the whole method here is that each step's error count means one thing. root set 321 files, was 318 typecheck 0 errors, with every file in src now checked lint exit 0 npm test 1684/1691, the same 7 failures build exit 0, identical bundle hash 74d17e2d… seed.ts emitted JS identical `allowJs`, `checkJs` and `jsconfig.json` are deliberately left alone — they come off at final cleanup, after strictness. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8 |
|||
| 78f1b44c2e |
chore(ts-migration): remove the now-redundant @ts-ignore in the agent registry
`import.meta.glob` needed a suppression while `tsc` modelled only the standard `ImportMeta`. Phase 1 set `types: ["vite/client"]`, which declares Vite's additions, and the directive has had nothing to suppress since. Phase 1 scheduled its removal for Phase 11; this is that. Removing it is the point rather than tidying. `@ts-ignore` suppresses whatever the next line produces, so one that no longer applies is a directive that would silently swallow a real error on that line later. Verified redundant before removing: typecheck is still 0, and the emitted JavaScript is identical. `src/vite-env.d.ts` described this suppression as present, so its comment is corrected too. `src/` now contains no `@ts-ignore` or `@ts-expect-error` at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8 |
|||
| 34acd63a80 |
fix(ts-migration): clear the last six pre-existing type errors
These six pre-date the migration. They were unfixable while the files
holding them were JavaScript with `checkJs`, because the fix in each case
is an annotation JSDoc cannot express. `src/` is TypeScript now, so:
- `setWidthState.timer` in `AssistantPanelContext` is a debounce handle
hung on the callback itself, so it survives re-renders without a ref.
TypeScript has no way to describe an expando on a `useCallback`
result, so the binding is `any`.
- `runAction(name, { skill, ...payload } = {})` — the `= {}` default
types the parameter `{}`, so reading `skill` off it looked wrong.
- `Progression` in `Positions` is a local presentational component
whose `className` is omitted at both call sites, like its siblings in
the same file.
- `normalizeSection`'s `page` reads as required because it has no
default, but `owliverConfig` deliberately calls it without one: `page`
only builds a fallback `where` label, and that caller passes `where`
explicitly, so the branch that would read it never runs. Stating the
parameter object makes `page` optional, which is what the function has
always accepted.
`npm run typecheck` is now 0 errors, from 71 when this migration started
and 20 when Phase 11 began. Nothing was suppressed to reach it: there is
no `@ts-ignore` or `@ts-expect-error` added anywhere in this branch.
4/4 byte-identical, suite and bundle unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
|
|||
| 284a7e7671 |
refactor(ts-migration): Phase 11 batch 11 — the data pages
The last nine admin pages. `src/` now contains no `.js` or `.jsx` at all. One error, and it was the same kind of thing the whole phase has been finding: `getBandMembers(min, max)` in `TalentPool` is called with one argument for the top band, and its own body reads `max == null || s < max`. The parameter has always been optional everywhere except the signature. The marker erases. 9/9 byte-identical, and the bundle still hashes to 74d17e2d… Three of these nine — `Analytics`, `Candidates`, `HiredHistory` — are the pages the Owliver baselines cover, and they carry six of the seven pre-existing suite failures. Those failures are unchanged and still match `ecf5e75` line for line, which is the point: if a rename had altered what these pages render, it would have altered how they fail, and it did not. The baselines are untouched, as they have been in every batch. Phase 11 is 122/122 files and eleven batches. Across all of them: type erasure 122/122 byte-identical bundle 74d17e2d… unchanged from `ecf5e75` in every batch npm test 1684/1691 with the same 7 failures, in every batch typecheck 20 errors at the start, 6 now The remaining 6 all pre-date the migration. They are no longer in JavaScript, so they are now fixable; that is the next commit, not this one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8 |
|||
| a0f3f77900 |
refactor(ts-migration): Phase 11 batch 10 — the authoring and settings pages
Nine admin pages: `AgentDetail`, both skill editors, the three Workspace
pages, `SkillDevelopment`, `Settings` and `Profile`. 9/9 byte-identical,
bundle hash unchanged.
Six errors, five of them the same shape this phase has met repeatedly:
`Object.values(skill.ui || {}).flatMap((page) => page.sections)` yields
`unknown` because inference into the union parameter of `values` does not
distribute. Three copies of that line across the two editors and
`WorkspaceSkills`, plus one `Object.entries` and one `new Set` whose
element type reached a React `key`, where `unknown` is not allowed.
The sixth is worth its own line. `WorkspaceSkills.toggleSkill` builds a
preferences patch as `{ disabledSkills }` and then adds `customSkills` to
it conditionally, several lines later, when re-enabling a skill flips an
inactive definition back to active. The literal's inferred type does not
carry a key assigned after the fact, so the later write looked wrong.
The annotation names both keys and marks the conditional one optional,
which is what the function does.
Measured against `b99dc7c`:
typecheck 6 errors, unchanged; no new error anywhere
lint exit 0, 0 errors, 289 warnings
npm test 1684/1691, the same 7 failures verbatim
build exit 0, identical bundle hash 74d17e2d…
type erasure 113/113 byte-identical across Phase 11 so far
Nine files left in Phase 11, all data pages. No baseline artifact
touched — three of those nine are the pages the baselines cover, so the
recapture question arrives with the next batch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
|
|||
| b99dc7c576 |
refactor(ts-migration): Phase 11 batch 9 — the page composition tables
The eight `nodes` files under `src/pages/admin/*/`, which declare what each admin page is made of. Renamed with no annotations needed at all: zero new errors, 8/8 byte-identical, bundle hash unchanged. They came through clean because everything they call was typed first — `makeNode` and the registry in batch 1, the skill sections in batch 2. A descriptor table is only as checkable as the constructor it feeds. `positions/nodes.js` became `.tsx`, joining its seven `nodes.jsx` siblings. That is the exact case `scripts/ssr-resolve.mjs` was written for and says so in its comment: it is the only `.js` under `src/pages`, `skill-check.mjs` loads it by literal path as `.js`, and the resolver tries `.ts` and `.tsx` in turn. The suite loads it and still passes, which is the first time that particular branch has been exercised. Two of these files are also read as source TEXT rather than loaded — `candidates/nodes.jsx` at `skill-check.mjs:6049` and `talent-pool/nodes.jsx` at `:6298`, both through `resolveSourcePath`. That is the second channel, the one that was missed in Phase 0 and found by an ENOContent failure in Phase 4b-ii. Both resolve. Measured against `7a95d95`: typecheck 6 errors, unchanged; no new error anywhere lint exit 0 npm test 1684/1691, the same 7 failures verbatim build exit 0, identical bundle hash 74d17e2d… type erasure 104/104 byte-identical across Phase 11 so far No baseline artifact touched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8 |
|||
| 7a95d95151 |
refactor(ts-migration): Phase 11 batch 8 — the assistant panel components
The thirteen `.jsx` files under `src/components/ai-assistant/`, which
completes that directory: 24/24 files migrated and all 24 linted under
their new extensions.
39 errors, and converting 21 `/** @param {any} props */` hatches to
`: any` cleared 33 of them — the same pattern as batch 6, and the same
reason: without the JSDoc, TypeScript infers every destructured prop as
required, so `ResponseBlocks` and `AssistantMessage` alone produced 31
complaints about call sites that were always correct.
The remaining six were already there before this batch and are
unchanged.
13/13 erase byte-identically. `.jsx` -> `.tsx` is the move that can drop
unused imports through esbuild's loader difference; it did not here, and
the bundle hash is unchanged.
Measured against `3f835ee`:
typecheck 6 errors, unchanged; no new error anywhere
lint exit 0, 0 errors, 289 warnings, 24/24 linted by name
npm test 1684/1691, the same 7 failures verbatim
build exit 0, identical bundle hash 74d17e2d…
type erasure 96/96 byte-identical across Phase 11 so far
No baseline artifact touched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
|
|||
| 3f835eee93 |
refactor(ts-migration): Phase 11 batch 7 — the assistant's non-component modules
The eleven `.js` files under `src/components/ai-assistant/`: the blocks
format, contexts, routing, history, placement, viewport, the greeting
and prompt tables in `dynamic`, the derivations in `insights`, and
`uiEdit`, whose boundary batch 1 already typed.
79 errors, and two optional markers cleared 63 of them.
`plural(n, word, irregular)` is called with two arguments sixty times in
`dynamic.ts` and its own body reads `irregular || \`${word}s\``, so the
third parameter has always been optional in everything but the
signature. `heading(value, sub)` is the same: `sub` is spread into the
block and `undefined` is what most callers mean. Marking both optional
is a statement about the existing contract, and the markers erase — the
emitted signatures still read `plural=(n,word,irregular)` and
`heading=(value,sub)`, checked in the output rather than assumed.
Those three `heading` errors landed in `lib/skills/workforceFlow.ts`,
already migrated and untouched here. Worth noting how that works: a
function's arity only starts being enforced on its callers once the file
defining it is TypeScript. Migrating a leaf makes claims about every
file that imports it, which is why this phase moves bottom-up.
The remaining nine were two `reduce` accumulators inferring `{}`, so
`Object.values` over them produced `unknown`. Both are now stated —
`{ label, count }` for the score bands, and the five-field hire grouping
— which is more useful than `any` and exactly what the lines below them
build.
Measured against `dde4ba6`:
typecheck 6 errors, unchanged; no new error anywhere
lint exit 0, 0 errors, 289 warnings
npm test 1684/1691, the same 7 failures verbatim
build exit 0, identical bundle hash 74d17e2d…
type erasure 83/83 byte-identical across Phase 11 so far
No baseline artifact touched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
|
|||
| dde4ba62c6 |
refactor(ts-migration): Phase 11 batch 6 — agent and skill components
The fifteen files under `src/components/agents/` (including `skills/`)
and `src/components/skills/`. 15/15 erase byte-identically; bundle hash
unchanged.
Renaming raised 43 errors and 29 of them came from one thing: these
files carry 24 `/** @param {any} props */` annotations, one per
component, and JSDoc stops applying at the extension boundary. Without
them TypeScript infers every destructured prop as required, so a call
site passing seven of nine props is an error — which is how a file that
declared its props `any` ended up with fourteen complaints about missing
`className`. Restoring the author's own declaration as `: any` is not
blanket typing; it is the annotation that was already there, in the only
form that still works.
`Workspace` in `AgentCanvas` was the one component in that file its
author left without the hatch. It now matches its siblings.
The rest were five separate things:
- `React.isValidElement(children)` no longer narrows enough to read
`children.props.id`: React 19 types `ReactElement`'s props as
`unknown`. `isValidElement<any>` says what the `cloneElement` call
beneath it has always assumed. The migration plan predicted this
site by name.
- `useSkillSections(page, placement)` is called with one argument by
`UiEditingProvider`, which its doc comment explicitly permits —
"called with no placement it returns every section on the page". The
parameter simply lacked its optional marker. The marker erases, so
the emitted signature is unchanged.
- `new Date(b.at) - new Date(a.at)` is valueOf coercion, which
JavaScript performs and TypeScript refuses to describe. Cast rather
than rewritten to `.getTime()`: that would change the emitted code,
and this comparison orders the list.
- `Object.values<any>` on a tally, the same inference gap as earlier
batches, which also fixed a `ReactNode` complaint downstream of it.
All fifteen are linted under their new extensions, checked by name.
Measured against `446df7b`:
typecheck 6 errors, down from 9; no new error anywhere
lint exit 0, 0 errors, 289 warnings, 15/15 linted by name
npm test 1684/1691, the same 7 failures verbatim
build exit 0, identical bundle hash 74d17e2d…
type erasure 72/72 byte-identical across Phase 11 so far
No baseline artifact touched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
|
|||
| 446df7b37b |
refactor(ts-migration): Phase 11 batch 5 — ui-tree and ui-editor
The eleven files that render and edit the node tree: six in
`src/components/ui-tree/`, five in `src/components/ui-editor/`. The
first `.jsx` -> `.tsx` of this phase.
Renaming raised six errors, all in one file, and one annotation cleared
all six. `UiNodeBoundary` is a class component declared
`extends React.Component` with no type arguments, so both its props and
its state are `{}` — which is why reading `this.props.node` and
`this.state.failed` looked wrong. `UiNodeBoundaryProps` and
`UiNodeBoundaryState` write down what the class already uses: `node` and
`children`, and `failed` plus `forNode`. `forNode` is what stops the
boundary staying latched after a broken node is hidden, so it is part of
the contract rather than an implementation detail.
Nothing else needed anything. That is the earlier batches paying off:
these files consume `lib/ui`, which was typed in batch 1, so the tree and
node values arriving here are already described.
The `.jsx` -> `.tsx` move is the one that can change emitted output —
esbuild's `tsx` loader elides unused imports where the `jsx` loader does
not, which cost a 942-byte bundle change in Phase 10. It did not happen
here: all eleven erase byte-identically, and the bundle hash is
unchanged.
ESLint now reports all eleven under their new extensions, checked by
name rather than by count. That is the failure Phase 0 existed to
prevent — `.tsx` outside the globs would have dropped them silently while
`eslint .` went on exiting 0.
Measured against `55ddaa1`:
typecheck 9 errors, unchanged; no new error anywhere
lint exit 0, 0 errors, 289 warnings, 11/11 linted by name
npm test 1684/1691, the same 7 failures verbatim
build exit 0, identical bundle hash 74d17e2d…
type erasure 57/57 byte-identical across Phase 11 so far
No baseline artifact touched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
|
|||
| 55ddaa134a |
refactor(ts-migration): Phase 11 batch 4 — the agent layer
All thirteen modules under `src/lib/agents/`. Renamed and annotated; no
logic touched. 13/13 erase to byte-identical JavaScript, and the bundle
still hashes to 74d17e2d…
Project typecheck errors are now 9, down from the 20 this phase started
from. Nothing was suppressed: batch 3's `Frontmatter` cleared 4 and this
batch's annotations cleared 7 more that had been sitting in `agentStore`,
`runtime` and `useAgents` since before the migration.
Two findings, both recorded rather than fixed:
`agentSkillIds(agent)` takes one parameter and is called with two, at
`runtime.ts:83` and `:95`. Not a bug — its own doc comment says so:
subagent skills were deliberately removed from it, because CLAUDE.md §3
makes `subagents` a delegation list rather than a skill list, and
"`agents` is still accepted so every call site keeps working; it is no
longer read." The contract is restored with an overload signature, which
emits no JavaScript — confirmed by reading the emitted output, where
`agentSkillIds` still takes exactly one parameter. Worth knowing that
`agentScopedDisabledWith` therefore computes what `agentScopedDisabled`
computes; that is intended, and `skill-check.mjs` asserts the behaviour
at eleven call sites.
`useAgents` returns five different shapes from eleven `return`
statements, which is the latent problem the migration plan predicted
here. `AgentActionResult` writes them down, but open: `ok` plus four
optional fields. Nothing stops a caller reading `.agent` off a failure
and getting `undefined` — `AgentDetail.jsx` reads `.conflict` and
`.error` off the same value. Closing it properly needs `as const` on
eleven literals so `ok` stops widening to `boolean` and starts
discriminating, which is an edit to these function bodies and not
something a rename may do. The type is the record of the decision, not
the decision.
Writing that interface also corrected my own count. I described four
shapes; the compiler rejected `duplicate`'s `{ ...result, id }` and made
it five.
Other annotations: React Query v5 infers `void` for an unconstrained
`mutationFn` parameter, so both mutations in `agentStore` had their
variables stated; `existingIds = []` in `agentLifecycle` infers
`undefined[]`, which rejects `.includes(id)`, so it is `string[]`; two
accumulators and two inline JSDoc hatches restated as annotations.
Measured against `fc8d7ee`:
typecheck 9 errors, down from 16; no new error anywhere
lint exit 0, 0 errors, 289 warnings
npm test 1684/1691, the same 7 failures verbatim
build exit 0, identical bundle hash
type erasure 46/46 byte-identical across Phase 11 so far
No baseline artifact touched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
|
|||
| fc8d7eec52 |
refactor(ts-migration): Phase 11 batch 3 — the rest of the skills layer
The remaining fifteen modules under `src/lib/skills/`, including the
three under `flows/`. `src/lib/skills` now holds no JavaScript.
Renaming them raised 134 errors, which came from nineteen values, not
134 places. Eleven were accumulators or parameters written `= {}`, whose
type is then `{}` — an object with no properties — so every later read of
a key looked like a mistake. Five were `Object.entries`/`values` on a
dynamic value, which yields `unknown` rather than `any` because
inference into their union parameter does not distribute. The rest were
`reduce` accumulators in the same position.
Annotating the nineteen sources cleared all 134. Where the keys were
knowable they are written down rather than waved away: both `prefill`
accumulators in `actions.ts` name the fields their own following lines
assign, and `dataResolver`'s two event tallies are
`Record<string, number>`, which is what they are. Where the value is
genuinely whatever an author wrote — a parsed YAML mapping, a skill
context — it stays `any`.
The one structural addition is `Frontmatter`, the return of
`parseFrontmatter`. Its no-frontmatter early return hands back a literal
`{}`, so TypeScript took the common shape of the two returns, which has
no properties; that single empty object is what made twenty-five later
readings of `data` look wrong. Typing the return also resolved four
pre-existing errors in this file and four more that had cascaded into
`lib/agents/registry.js`, so the project total is 16, below the 20 this
phase started from. Nothing was suppressed to get there.
Measured against `e73929f`:
typecheck 16 errors, down from 20; no new error anywhere
lint exit 0, 0 errors, 289 warnings
npm test 1684/1691, the same 7 failures verbatim
build exit 0, identical bundle hash 74d17e2d…
type erasure 33/33 byte-identical, all of Phase 11 so far
CORRECTION to the previous two commits. Both claim the migrated files
emit "byte-identical minified JavaScript". That check was broken when it
ran and proved nothing: it passed `--loader=js`/`--loader=ts` to esbuild
on named files, and esbuild accepts `--loader` without an extension only
for stdin. Both sides errored, both outputs were empty, and `cmp` found
two empty files equal. Eighteen "IDENTICAL" lines meant eighteen pairs of
nothing.
Repaired here and re-run over all 33 files. Two further things had to
change for the check to mean anything. It now proves it can detect a
difference before it is trusted, against a pair of files differing in one
character. And it compares with `--minify-whitespace --minify-syntax`
rather than `--minify`: full minification renames locals, and esbuild's
choice of names shifts with token counts, so twelve files differed only
in whether a binding was called `g` or `u` — alpha-equivalent, at
identical byte counts. Stripping comments and whitespace while keeping
identifiers is the comparison that answers the actual question.
The result is that the substantive claim was true throughout, and is now
actually evidenced: all 33 files erase to byte-identical JavaScript. It
was never the only evidence either — the production bundle hash and the
1691-check suite were compared in every batch, both valid, and both
unchanged.
No baseline artifact touched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
|
|||
| e73929f47e |
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
|
|||
| eaa677f815 |
refactor(ts-migration): Phase 11 batch 1 — the UI-node engine
Renames the ten modules under `src/lib/ui/` to TypeScript and annotates
them. No logic is touched: no reordered statements, no changed defaults,
no altered branches, no renamed locals, no edited strings.
The proof is mechanical rather than argued. esbuild's output for each of
the ten files, minified, is byte-for-byte what the `.js` file produced at
`ecf5e75`, and the production bundle hashes to `74d17e2d…` before and
after. `operations` and `patch` are pure renames — R100, not one
character changed.
What the annotations actually are:
- Four `/** @type */` and `@param` JSDoc hatches the author had already
written, restated as real annotations. These stop applying at the
extension boundary, which is where most of the errors came from.
`makeNode` keeps its declared `@returns {any}`; dropping it in favour
of an inferred shape would have quietly narrowed a contract the
author had deliberately left open.
- `NodeTypeRegistry.types` as `declare`, not a field declaration. A
plain one would emit a `defineProperty` under
`useDefineForClassFields`, i.e. a change to the shipped JS. `declare`
emits nothing, which the per-file comparison above confirms.
- Two accumulators (`wants`, `params`) given the shape their own
following lines build.
- `Object.entries<any>(…)` at two sites. Inferring `any` into the
union parameter of `entries` yields `unknown`, not `any`, so the
rule objects arrived unreadable; an explicit type argument restores
what JavaScript had, without a cast.
- `UiEditMatch`, an open interface, as `matchUiEdit`'s return.
`UiEditMatch` is the one judgement call and it is deliberately weak.
`kind` is optional and the rest is an index signature, because the
nineteen return shapes share field names carrying different meanings and
the suite reads these objects in around forty places. Writing the real
discriminated union is a schema this phase has no mandate to invent, and
`kind` stays `string` rather than a literal union partly so that no
node-type name is ever written into a type — the engine check forbids
exactly that.
`kind` is optional for a reason worth recording: at run time the planners
guard with `if (subject.kind) return subject`, so a `kind`-less object
never escapes. TypeScript cannot see it, because every `kind` widens to
`string` and a property that is `string` in every member is not a
discriminant, so truthiness narrowing leaves a shape the function cannot
produce. Marking it required would have been true of the runtime and
rejected at five return sites, and the only fixes are edits to agent
logic. The weaker claim is the honest one.
Renaming these files also surfaced twenty errors in `uiEdit.js`, which is
still JavaScript: `.js` and `.ts` infer this union differently. Verified
as a property of the rename and not of any edit, by compiling the
verbatim `ecf5e75` contents under a `.ts` extension — same twenty. The
return annotation clears them.
Measured against `ecf5e75`, all unchanged:
typecheck 20 errors, same files (no new error anywhere)
lint exit 0, 269 files, 0 errors, 289 warnings
npm test 1684/1691, the same 7 failures verbatim
build exit 0, identical bundle hash
emitted JS 10/10 byte-identical
The 7 failures and the `owliver-baseline.mjs` drift both pre-date this
commit — they are the parallel feature session's, present at `ecf5e75`
and measured there before this batch was applied. No baseline artifact
is touched; recapture waits until the agent migration is complete.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
|
|||
| ecf5e75d76 |
chore(ts-migration): migrate the app shell, routing and the last non-agent pages
Phase 10: `App`, `main`, `index.html`, and the four remaining `src/pages` files. ROUTING IS PROVEN UNCHANGED, not assumed. The 49 `<Route>` elements were fingerprinted before the rename and compared after: byte-identical, all 44 paths, all 13 `<Navigate>` redirects, the same nesting under `ProtectedRoute` and `AdminRoute`, and the same provider order - AuthProvider, then QueryClientProvider, then Router, with ScrollToTop and AuthenticatedApp inside and both toasters as siblings. There is no lazy loading to preserve; there never was any. Two coordinated edits the rename forced. `main` imported `@/App.jsx` by explicit extension, which stops resolving the moment `App` is `.tsx`; it is now extensionless `@/App`. `index.html` pointed its module script at `/src/main.jsx`; it points at `/src/main.tsx`. Both are required, and missing either would have been a blank page rather than a type error. ONE GENUINE SEMANTIC DIFFERENCE, INVESTIGATED AND ACCEPTED. esbuild elides unused imports under the TypeScript loader but keeps them under the JavaScript one, so `App` drops thirteen imports: `Layout`, `Overview`, `Positions`, `Candidates`, `HiredHistory`, `TalentPool`, `UserTracking`, `Analytics`, `Profile`, `WorkerProfile`, `KrowIdentity`, `Owliver` and `DesignSystem`. All thirteen are dead - each has zero JSX uses in `App`, because every route that once rendered them now `<Navigate>`s to an `/admin/*` equivalent. Before accepting it I checked that none of the thirteen modules can do anything when evaluated: no bare side-effect imports, no top-level calls, and every top-level binding a literal or a function declaration. The consequence is 942 fewer bytes in the index chunk and thirteen modules no longer evaluated at startup. Nothing observable changes, and the routes those pages are reached through are unaffected - they are reached through the `/admin` tree, which is untouched. That is the first time in this migration the production bundle has changed for a reason other than a comment, so it is recorded here rather than left to be noticed later. `CreatePosition` needed the only real typing. `vetting_criteria` is a `jsonb` column holding the five weighting percentages the page edits, typed `unknown` by the registry, and both the total and the three render sites read through it. `onDone` is called with the created id, so it takes arguments - the generator had classed it as zero-arg, and that rule is now narrowed to `onClose` alone. The generator's entity-import rule was narrowed first, as instructed: it now counts a type as used only when it appears in a type position inside a generated interface, rather than anywhere in the file text. That is what produced four unused-import lint errors in the previous batch. Verified: tsc 21 -> 20, set-difference showing one removed and none added; zero errors in any of the six files; all four pages emit byte-identical JavaScript; route fingerprint identical; npm test 1684/1691 with the same seven failures; Owliver baseline 59/59; lint 0 errors; no deferred agent-region file touched; baseline artifacts untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8 |
|||
| fcdaa32f4d |
chore(ts-migration): migrate the non-agent pages to TypeScript
Phase 9, first batch: 18 pages. Seventeen emit byte-identical JavaScript, the
eighteenth differs only by a JSDoc cast becoming a real annotation, and the
production bundle is byte-identical to
|
|||
| be49184006 |
chore(ts-migration): migrate root components and layouts to TypeScript
Phase 8, third batch: the three root components and both layouts. All five emit byte-identical JavaScript and the production bundle is byte-identical to |
|||
| 3d5f54bd5b |
chore(ts-migration): migrate components/forge and components/admin to TypeScript
Phase 8, second batch: 16 files. All 16 emit byte-identical JavaScript and the
production bundle is byte-identical to
|
|||
| e7e1e9873f |
chore(ts-migration): migrate components/krow to TypeScript with real prop types
Phase 8, first batch: 56 files, plus three supporting edits outside the folder.
All 56 emit byte-identical JavaScript and the production bundle is byte-identical
to
|
|||
| 543da9d4e7 |
chore(ts-migration): migrate hooks and auth context to TypeScript
Phase 7. Four files, and the production bundle is byte-identical.
This is the phase where the generated entity types finally pay off. React Query
v5 infers a `mutationFn`'s parameter as `void` when nothing constrains it, so
every destructuring in `krowHooks` needed its shape written down - and those
shapes are real contracts, not guesses. Thirteen mutations now name what they
take, reusing `JobApplication`, `JobPosting`, `WorkerProfile` and `Course` from
`@/types/entities`: `useHireCandidate` takes `{ application, job }`,
`useCompleteCourse` takes `{ profile, course, quizScore }`. Five `{ id, data }`
mutations share one `IdPatch`, where `data` stays `any` on purpose - a PATCH
body is whichever fields the caller is changing, and naming a subset would
describe one call site rather than the endpoint.
Two local types absorb places where an object gains fields after it is built,
which TypeScript does not allow on a literal. `AssignmentEntry` declares
`application_id` and `application` as the alternatives they are - an existing
application named by id, or one described for the server to file in the same
transaction. `LearningProfile` narrows four `jsonb` columns the learning
mutations append to and spread; what the elements hold is still unstated,
because it still is.
`AuthContextValue` writes down the twelve keys every consumer reads. Two are
permanently inert and say so. `user` is `any` rather than `User`, and that is a
narrow, documented exception: `auth.me()` resolves either to the server record
or to the localStorage mirror over `DEMO_USER`, and `admin/Profile` reads
`user.avatar_url`, which is neither a column on `users` nor in the `/me`
projection - so typing it `User` would be accurate about the server and would
turn an always-undefined read into a compile error in a file this phase does not
touch.
`authError` is typed `{ type?: string } | null` rather than `null`, and
TypeScript is the reason. Typed as the provider actually behaves - always
`null` - it made `ProtectedRoute`'s `authError.type === 'user_not_registered'`
a property access on `never`: correct, and a report that the branch cannot be
reached in this build. The branch and `UserNotRegisteredError` are real, so what
a consumer may be handed is what is written down. That this build never produces
one is current behaviour, not the contract.
`use-size` gets a `Size` interface and a typed ref - a small, entirely clear
contract.
Thirteen JSDoc `@param {any}` comments became real annotations. That was not
cosmetic: left in place they changed esbuild's parenthesisation under the `.ts`
loader and put two redundant bytes into the production bundle. Converting them
brought the bundle back to byte-identical, which is how the difference was found
at all.
Verified: tsc 35 -> 35, set-difference showing zero introduced and zero removed;
zero errors in any Phase 7 file; production bundle byte-identical to d440036;
npm test 1684/1691 with the same seven failures; lint 0 errors.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
|
|||
| d440036211 |
chore(ts-migration): migrate UI primitives and design system to TypeScript
Phase 6. 57 files: 23 vendored shadcn primitives, 30 design-system components,
4 charts. Plus `src/components/ds/props.ts`, which is types only.
Renaming these alone took typecheck from 37 to 1008, and the reason is worth
recording because it is the shape of every remaining phase.
These components had NO prop contract. No PropTypes, no validation: in the
JavaScript every prop was optional and every extra prop was spread onto the
underlying element. TypeScript infers a destructured parameter WITHOUT a default
as REQUIRED, so the moment the files became `.tsx` it invented a rule the
components never had and rejected several hundred call sites that have always
worked. That is the compiler describing its own inference, not a defect it
found.
Three mechanical fixes, each restoring a contract that already existed:
- 57 JSDoc `@type {React.ForwardRefExoticComponent<any>}` annotations become
real TypeScript annotations. Those comments were the previous authors'
deliberate compatibility types; JSDoc stops applying in a `.tsx` file, so
converting them preserves an intent that was already written down.
- 61 `React.forwardRef(...)` calls gain `<any, any>`. Without generics `ref`
infers `ForwardedRef<unknown>`, which no element's `Ref<T>` accepts - so
every primitive that forwards a ref to a `div` failed on the ref, not the
props.
- 78 component signatures take `DsProps`, a documented alias for
`Record<string, any>`. It exists so the decision is recorded once and is
greppable when someone tightens it, rather than being 78 bare `any`s with
no explanation between them. The prop NAMES are not lost: every component
still destructures them by name, which is where a reader looks.
Four files needed real types rather than compatibility ones. `ds/toast` takes
react-hot-toast's own `ToastOptions`, which narrows `position` to its
`ToastPosition` union instead of widening to `string` - the widening was what
made all six calls unassignable. `ds/Pagination`'s page range is genuinely
`(number | string)[]`, because it interleaves page numbers with '…' markers that
the renderer tests for. `ds/Field` narrows `children.props` at three reads, and
`ds/Avatar` needed the ref generic.
Two of my own automated passes were wrong and were caught rather than shipped. A
props-interface generator dropped alternating props, because non-overlapping
regex matches consume the separating comma - it made things worse (83 file
errors to 146) and was reverted wholesale. A second pass missed every
multi-line signature whose defaults contain a `)`, such as `onClose = () => {}`;
that needed a brace matcher rather than a character class.
56 of 57 files emit byte-identical JavaScript. The one exception is `ds/toast`,
where a JSDoc type CAST - `/** @type {ToastPosition} */ ('bottom-center')` -
became a real annotation, so the emitted output loses a comment and a pair of
now-redundant parentheses. The value is `"bottom-center"` either way; the
minified outputs differ only in esbuild's choice of mangled local names.
Verified: tsc 37 -> 35, set-difference showing zero introduced and two removed;
zero errors remain in any Phase 6 file; 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
|
|||
| 2b8f5746bd |
chore(ts-migration): migrate domain logic to TypeScript
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
|
|||
| 3e654c2bf7 |
chore(ts-migration): migrate seed fixtures to TypeScript
Phase 4c, completing `src/api`. Both files emit byte-identical JavaScript - 86,831 and 6,449 bytes - and neither needed a single annotation: they are data and pure helpers, and inference already describes them. Neither reaches the production bundle. No module under `src` imports either one; `base44Client` mentions `attendanceSeed` in a comment and nothing more. They exist for `skill-check.mjs`, `owliver-capture.mjs` and `seed-fixture.mjs`, which is why this was the safest phase in the whole migration and why it was left until the transport layer was done. `npm run seed:check` still answers "seed.json is stale", which it has since before this migration began - the backend fixture drifted from `src/api/seed` independently of any of this. What matters here is that it ANSWERS: the generator loaded the renamed module through the Phase 0 resolver rather than failing to find it. tsc unchanged at 40, npm test 1684/1691 with the same seven failures, lint 0 errors. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8 |
|||
| 1133eca16a |
chore(ts-migration): migrate base44 client to TypeScript
Phase 4b-ii, and the last file in the transport layer. Type-only: the plain
emitted JavaScript is byte-identical at 9,302 bytes.
This file owns the session, so the annotations stay at the edges and nothing
about the auth model moves. `SESSION_KEY`, the localStorage mirror, the three
module-level mutables, the shared hydration promise, the `{ ...user }` spreads,
`window.location.href` on logout and `window.location.reload()` in
`resetDemoData` are all exactly as they were.
Nine sites, eight of them parameter types. The ninth is `hydration`, which needs
`Promise<any> | null` because the runtime already assigns both: the shared first
`GET /me`, then `null` once it has been consumed or superseded by a login.
Inference would have fixed it at `Promise<any>` and rejected the assignments
that make the sharing work.
`entities` is deliberately NOT annotated, and neither is `createEntity`'s
return. A probe run during the inspection showed what annotating it costs:
`Record<EntityName, EntityClient<any>>` makes `list()` return `Promise<any[]>`
where inference gives `Promise<any>`, and `agentStore.js` does
`qc.getQueryData(KEY) || (await ...list(...))`. React Query types
`getQueryData` as `unknown`; `unknown || any` collapses to `any` while
`unknown || any[]` stays `unknown`, and `.find` on the next line stops
compiling. Two new errors in a file this phase does not migrate. The generated
record types and `EntityClientFor<K>` are ready for the phase that migrates the
forty-five call sites in `krowHooks.js`.
The auth returns are likewise left inferred rather than typed `User`. That was
checked, not assumed: `admin/Profile.jsx` reads `user.avatar_url`, which is
neither a column on `users` nor part of the `/me` projection in `me.go` - it is
always undefined at run time. Typing the return would have turned a latent dead
branch into two compile errors in a file this phase does not touch. Worth
knowing about separately; not this commit's business.
One stale comment is kept verbatim - `@param {any} request` above
`owliver.suggestions`, which names a parameter that does not exist. Removing it
was the only remaining difference in the emitted output, and byte-identity is
worth more here than tidying a comment. Flagged for a later docs pass.
Verified: tsc 42 -> 40 with a set-difference showing two removed and none added;
npm test 1684/1691, the same seven failures as the parent commit (six documented
as deliberate in scripts/__baseline__/README.md, one the stale backend fixture);
lint 0 errors; build succeeds with the API origin inlined. This is also the
first run of the suite with a source-text-inspected file migrated - the harness
fix in
|
|||
| 02a2ab05ef |
chore(ts-migration): resolve source-text reads across .js/.jsx/.ts/.tsx
The check suite consumes source files through two entirely separate channels, and Phase 0 only hardened one of them. `withSourceResolution` wraps `ssrLoadModule`, which covers the 159 module loads. It does not and cannot see the other 30 sites, which read source as TEXT through `readFileSync` to assert structural facts - "the panel imports no local suggestion ranker", "no runtime path writes a definition". Renaming `base44Client.js` to `.ts` is what surfaced the difference: ENOENT in the middle of a suite that had been passing, from a line no grep for `ssrLoadModule` would ever have found. `resolveSourcePath()` in `ssr-resolve.mjs` reuses the existing `candidatesFor()` ordering - the written path first, then `.ts`, then `.tsx` - and returns a path relative to the root, so every call site keeps its `join(ROOT, ...)` as it was. An unresolvable path comes back unchanged, which keeps the two absence assertions honest: a check proving `store.js` is GONE still asks about the path it means, and now also notices if the file returns under another extension. Thirty-seven lines change, each a one-for-one replacement. No assertion text, no record() message, no ordering, no logic. The audit found the reads in four shapes, and two of them a path grep cannot see: - direct readFileSync(join(ROOT, 'src/x.jsx'), 'utf8') - via a const const P = join(ROOT, 'src/x.jsx') - dir + name ['node.js', 'patch.js'].map((f) => readFileSync(join(ROOT, 'src/lib/ui', f))) - path array for (const f of files) readFileSync(join(ROOT, f)) The third and fourth hide thirteen filenames in adjacent arrays, which is why the first estimate of this work was twenty-two files and the real number is thirty-five. One directory scan also filtered `/\.jsx?$/` over `src/pages/admin` and `src/components/agents`. That one does not crash - it quietly matches nothing once those directories are TypeScript, and the check passes having inspected an empty set. Widened to `/\.[jt]sx?$/`. A silent shrink is worse than a failure, and CI's FLOOR of 900 would not have caught it. Deliberately NOT touched, because they are correctly extension-specific: the `dist/assets` filter reads built bundles, which are `.js` whatever the source was; `scripts/stubs/react-hot-toast.js` and `scripts/browser-flows.js` are scripts, not migrated source; and every comment that mentions a `.js` filename says something true about a file that still has that name. Proven rather than assumed. `npm test` reports 1684/1691 before and after, the same seven failures. Then `src/api/base44Client.js` - the exact file whose rename broke the suite - was renamed to `.ts`, the suite re-run, and the check that reads it as text passed: "the app does not import the seed fixture". The rename was reverted and the file verified byte-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8 |
|||
| dca184289e |
feat(hiring): final-selection queue, honest seat counts, and human interviews
Authored in a parallel session alongside the TypeScript migration; committed separately so the two never share a commit. No TypeScript migration file is included here. Candidates becomes the queue of hiring decisions waiting on a person, rather than a second Talent Pool listing every application the org ever took. Final selection is DERIVED - there is no `final_selection` value in the `application_status` enum and none is added. The fact it reads is the existence of an interview row, a NOT NULL foreign key, rather than `job_applications.interview_id`, which the schema keeps as an unconstrained soft reference precisely so it may dangle. `status = 'interview'` is set both when an interview is arranged and when one is completed, so status alone cannot tell a queue of people who have been interviewed from a queue of people merely booked in. Seats on a position are counted from the employment records instead of a stored column. A `filled` counter would be a second source of truth, and the day it disagreed with `staff` nothing could say which was lying. Someone who has left frees their seat, and over-hiring floors at zero rather than going negative. `DEMO_FILL` is gone. `hiringRecords.js` padded the hires list with five invented people so Hired History read as a history rather than as three rows; the padding reached Analytics too, where "total hires" counted eight against a database holding three. Hires now come only from `staff`. Both paths that file an application on somebody's behalf now carry `worker_profile_id`, the link back to the talent-pool record. The column is nullable, so omitting it saved cleanly and failed silently: the application belonged to an email address rather than to a person, and the hire it became could not be traced back to the profile it came from. `HiredChronology` used to `return null` with no hires, taking the `chronology` node identity out of the DOM with it - so on an honest empty dataset the section could not be addressed by Owliver or the layout editor at all. It now renders an empty state inside the section it keeps. Nine new checks cover the above; `npm test` reports 1684/1691. SIX SSR PARITY CHECKS FAIL ON PURPOSE, and `scripts/__baseline__/README.md` documents each with verified tag counts. Five are the `DEMO_FILL` removal: the Hired History and Analytics baselines were captured while the padding was in effect and, because they render with queries disabled, the padding is all they contain. The sixth is this change to what Candidates says. Do not regenerate those baselines to clear them - two of the checks exist to prove the UI node tree migration added exactly two `<div>`s, and that proof needs the baseline to be pre-migration markup. Recapturing now would write post-migration markup into a file named `pre-migration` and the check would compare the current render against itself forever. The debt is held until the migration work lands, when both files are recaptured together. The seventh failure, the stale backend seed fixture, predates all of this. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8 |
|||
| 64140c7add |
chore(ts-migration): migrate ai engine to TypeScript
Phase 4b-i. Type-only, and the production bundle proves it: built from this
commit's parent and from this commit, all six chunk hashes match. The plain
emitted JavaScript is byte-identical at 34,095 bytes.
Four annotation sites, no logic touched:
- `ROLE_TITLES` and `AVAILABILITY_TOKENS` get `[RegExp, string][]`. Left to
inference the element widens to `string | RegExp`, which has no `.test`.
Explicit tuples rather than `as const`, which would also have worked and
would additionally have made the arrays readonly - a change to the type this
module publishes for no benefit it uses.
- `ROUTES` gets `[RegExp, (prompt: string) => any, number][]`, because all
three positions are used for what they are: `.test()` on the first, a call
on the second, `think(ms)` on the third.
- `invokeLLM` gets a real parameter type. It inferred `{ prompt?: string }`
from its own destructuring defaults, and that single inference was
responsible for nine errors in files this commit does not touch - eight in
`krowAi.js`, one in `provingGround.ts` - every one of them a caller passing
`response_json_schema` or `model`, which the real integration accepts.
Naming the options type fixes all nine from here.
- `uploadFile` gets `{ file?: File }`.
The return type of `invokeLLM` stays `Promise<any>`, deliberately. The ten
handlers behind the router return ten different shapes, and `krowAi.js` branches
on the result at run time - `typeof res === 'string' ? res : res.text || String(res)`
- which a precise union would reject on every branch without a `.text`. The
looseness is the contract, not an omission.
`InvokeLLMOptions` is exported as a type only; the runtime exports are still
exactly `invokeLLM` and `uploadFile`.
Verified in isolation from the parallel feature work (
|
|||
| f96f128839 |
chore(ts-migration): migrate API transport to TypeScript
Phase 4a: `demoUser` and `httpClient`. Type-only. The production bundle is
byte-identical - built from this commit's parent and from this commit, all six
chunk hashes match.
`httpClient` is the contract boundary, so the annotations are deliberately
conservative:
- `KrowApiError` is a CAST, not a class. The three error sites still build a
plain `Error` and assign `.name`, `.status`, `.code` and `.details` onto it
exactly as before. `class KrowApiError extends Error` would have read
better and changed three things that callers depend on: the prototype
chain, `instanceof`, and how `name` comes to be set.
- `RESOURCE_PATHS` becomes `Record<EntityName, EntityResourcePath>`. That is
the first thing in the repo to check the eighteen entity names against the
eighteen in `ENTITY_NAMES`; until now the two lists agreed only by habit,
and a divergence would have surfaced as `base44.entities.Whatever` being
undefined with nothing to say why.
- `createEntity`'s parameter stays `string` and its return stays inferred.
Annotating the return `EntityClient<any>` was tried and reverted: it makes
`list()` return `Promise<any[]>` where inference gives `Promise<any>`, and
`agentStore.js` does `qc.getQueryData(KEY) || (await ...list(...))`. React
Query types `getQueryData` as `unknown`; `unknown || any` collapses to
`any`, `unknown || any[]` stays `unknown`, and `.find` on the next line
stopped compiling. Two new errors in a file this phase does not migrate,
for no gain. `EntityClientFor<K>` is ready for the phase that migrates
those consumers.
Unchanged and verified in the emitted output: `credentials: 'include'`, both
header branches on `body === undefined`, the URLSearchParams query encoding with
its repeated-array and undefined-omission rules, the `payload.data` unwrap that
drops `meta`, the verbatim server message, `status: 0` / `code: 'unreachable'`
for a transport failure, the `-created_date` and `limit` defaults, path
construction through `encodeURIComponent`, and `bulkCreate`'s sequential
`this.create` loop.
`DEMO_USER` is annotated `User`, which does real work: unannotated,
`role: 'admin'` widens to `string` and the default shape did not satisfy the
type the app uses for the thing it defaults.
Verified in isolation from the parallel feature work (
|
|||
| d1425f974c |
chore(ts-migration): add generated entity types
Phase 3.5. Entity record shapes, generated from the backend's resource
registry rather than transcribed from it. Type-only: every touched file emits
byte-identical JavaScript, and the two type modules emit nothing at all.
`scripts/gen-entity-types.mjs` reads
`krow-backend/go-api/internal/domain/resources_gen.go` - itself generated out of
information_schema, so it cannot drift from the migrations - and writes
`src/types/entities.generated.ts`: 15 resources, 298 columns. It checks for
drift by default and rewrites with --write, the same arrangement seed-fixture.mjs
uses, and skips cleanly when the backend is not checked out beside this repo.
Field types come from `Column.SelectExpr()` in `domain/resource.go`, which is
what the read projection actually emits, not from the Postgres type. The two
differ: uuid and citext are cast to text, numeric to float8, dates and
timestamps to formatted strings, and - the case that justifies generating rather
than typing by hand - `user_activity.id` is an identity bigint cast to text, so
it arrives as a STRING. Written by hand it would have been called a number, and
nothing would have contradicted that until a comparison quietly stopped
matching.
The five Phase 3 leaf utilities swap their `any` placeholders for these records,
as `Partial<...>`: each takes `= {}` or guards every read because it renders
before the query resolves, and requiring the whole record would force those
defaults out - a behaviour change in a scoring path.
jsonb is where the generator stops. Thirteen columns across eight entities are
typed `unknown`, correctly: what sits inside a jsonb column is not in
information_schema and nothing on the backend declares it. Where a module reads
through one it is narrowed to `unknown[]`, `any[]` or `any` - what kind of value
it is, and nothing about its contents. An earlier draft declared the fields
these modules read off them; it was removed. That would have been inventing a
schema the database does not hold, with the compiler then defending the guess.
Not wired into skill-check.mjs: that file is being changed concurrently by
unrelated feature work. Adding `"types:check": "node scripts/gen-entity-types.mjs"`
beside the existing seed:check is the natural next step and is deliberately left
for when that file is quiet.
Verified in isolation from the parallel feature work (commit
|
|||
| 1775395256 |
chore(ts-migration): establish TypeScript migration checkpoint
Phases 0-3 of the JS/JSX -> TS/TSX migration. No runtime behaviour changes:
every converted file emits byte-identical JavaScript, verified file by file.
Phase 0 - harness hardening, before any rename:
- scripts/ssr-resolve.mjs wraps `ssrLoadModule` so the ~170 literal module
paths in the check scripts resolve .js/.jsx/.ts/.tsx. Without it the first
rename would have silently destroyed the 1642-check suite that guards the
Owliver flow.
- eslint.config.js gains a TypeScript block. Its `files` globs listed only
{js,mjs,cjs,jsx}, so a renamed file would have dropped out of the run while
`eslint .` went on exiting 0 - the quietest failure mode available.
- MIGRATION_BASELINE.md records the measured starting point, including the
pre-existing seed-fixture failure and the already-broken standalone
owliver-baseline.mjs, so neither is later mistaken for migration damage.
Phase 1 - tsconfig.json succeeds jsconfig.json, carrying every option across at
its old value. `types` moves from [] to ["vite/client"], which fixes the eight
import.meta errors; @types/node is deliberately excluded so setTimeout stays a
number in browser code. allowJs and checkJs stay on, strict stays off.
Phase 2 - src/types/{api,entities,user}.ts. Transport envelope, error shape,
the entity-name union (the same 18 names are written down twice today, in
httpClient and base44Client, with nothing checking they agree), and the user
record. Every field transcribed from the API contract, the migrations and the
/me projection in me.go - not inferred. Entity record shapes are deliberately
absent: derivable, but nothing consumes them yet.
Phase 3 - nine leaf utilities renamed to .ts with annotations added only where
they could be established from existing usage. Entity records are typed `any`
with a comment naming what they are, rather than a guessed interface.
Verified: tsc 64 errors (65 before; one pre-existing TS2559 genuinely fixed,
none introduced), lint unchanged at 0 errors, skill-check 1641/1642 with only
the known failure, Owliver baseline section 59/59 green, production build
succeeds with the API origin inlined.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
|
|||
| f522b6508e |
archive issue fix
Some checks failed
CI / check (push) Failing after 5m5s
|
|||
| 6249e00a3a |
candidates and board ui agent issue
Some checks failed
CI / check (push) Failing after 4m58s
|
|||
| e02a0c23d4 |
Make a conversation a registry, and add the second one
Some checks failed
CI / check (push) Failing after 4m57s
`positionFlow.js` was five per-domain concerns in one file — a field table, an
`@`-token resolver, a sentence extractor, a commit vocabulary and a set of
outcome renderers — and only the control flow between them was general.
Everything else knew it was creating a job posting. Adding a second
conversation meant a second copy of all of it.
So the control flow is now `conversationFlow.js` and each kind of record is a
REGISTRY. Which conversation a skill runs is the skill's own `flow:` line,
resolved through a map: `routing.js` used to say `if (skill.id ===
'create-position')`, which made a second conversational skill a change to the
router rather than a file on disk — the `if agent_key == ...` shape the
platform rules out one level up. The panel's write callback is likewise a map
keyed by flow id instead of an `onCreatePosition` prop, and the outcome wording
comes off the registry, so nothing in the panel names a kind of record any
more.
`create-employee-role` is the second registry. The worker is asked for and
never assumed: a conversation that names nobody re-asks rather than falling
back to the session, because an operator records this on somebody's behalf. Its
`extract` is deliberately narrower than the posting's — "bartender, weekends,
$30/hr" settles three fields and leaves the subject alone, since guessing WHO a
record is about from a fragment is how a role gets filed against the wrong
person.
THE CONFIRMATION STEP ACCEPTED "create position" AND SILENTLY REJECTED "create
positions" — the plural the Positions page itself uses. An anchored regex missed
it, and the reader got the summary back with no indication of what was wrong
with what they said, which is indistinguishable from the screen not having
updated. Matching is now exact membership against a normalized reply, so a
vocabulary is a list of phrases somebody can read rather than an expression
somebody has to parse.
Two bugs in `extractRole`, both of which fabricated a value nobody typed on the
one field a position cannot be created without:
- The phrase pattern marks "new" as the role by the same grammar that marks
"sous chef", so "create new position" opened the conversation titled "New".
The scaffolding is a PHRASE at least as often as a single word, so a
per-word test still produced "Brand New" and "One More". Scaffolding words
are now stripped to DECIDE whether the phrase named anything, and the
ORIGINAL phrase is returned when it did — strip to test, never to rewrite,
or "second chef" becomes "Chef" and the cure is worse than the bug.
- `(?:a|an)?\s*` has no word boundary, so it matched the leading "a" of
"another" and the capture began mid-word. That mangled scaffolding into
"Nother New" and, worse, corrupted every role introduced with "an":
"create an open kitchen lead position" titled the position "N Open Kitchen
Lead". A real role, typed correctly, silently wrong. Found by mutation
testing the first fix.
`@companies` and `@workers` resolve from data the panel already holds — postings
and profiles the API has already scoped to the caller — so neither widens
anybody's view and neither costs a request. The company list is deliberately
unsorted: `useJobPostings` asks for `-created_date`, so the clients staffed for
most recently come first, and the panel does no ranking of its own. That last
part is a rule the suite enforces structurally, and it is the right rule — a
second opinion formed in the panel outranking the server's is exactly the kind
of thing that decays quietly.
`npm test` now refuses a conversation step whose field its registry does not
define. `stepsOf` drops unknown fields, so a typo means the flow asks fewer
questions than the file lists — and a skill whose steps are ALL unknown asks
none, jumps to the summary, and offers to write an empty record. Nothing errors
and the Markdown still reads correctly. 970 checks, up from 924; the new ones
walk both conversations end to end, because a wrong answer at the confirmation
step re-renders the same summary a right answer does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJvibeSc1JYXjatankqM1g
|
|||
| 1a0dc7e5f1 |
Settle subagents: it means delegate to, not borrow skills from
Some checks failed
CI / check (push) Has been cancelled
The key had two incompatible meanings running at once. CLAUDE.md §3 defines subagents as "keys of other specs this may DELEGATE to" and §6 as a tool call from the parent's perspective — the subagent runs its own turn, as the same caller, out of the parent's budget, and returns an answer. The backend implements exactly that. This side did something else: agentSkillIds folded one level of subagent skills into the parent's carried set, so a parent silently gained everything its subagents carried, and the UI described it that way — "other agents whose skills this one may also use", "borrowing them cannot reach data this page does not hold". Both are defensible readings. Only one is the specification, and running both meant krow-workforce-agent carried eight delegation tools AND the flattened skills of those same eight agents — able to answer a question directly or to ask an agent that had already lent it the means to answer. Two ways to do one thing, differing in cost and in what the trajectory records. So an agent carries what it declares. Reaching another agent is delegation, which the runtime does with its own budget and its own trajectory. The blast radius was one check, which is the useful part of the answer: only "a subagent cycle terminates" depended on the folding, because that traversal was the only thing that could loop. Nothing walks subagents here now, so the cycle question moved to where it belongs — refused at publish by definition.FindSubagentCycle, bounded at run time by the depth cap. The replacement checks assert the new meaning rather than deleting the old ones, because the previous behaviour reads as perfectly reasonable and will be reinvented otherwise. The `agents` parameter stays on agentSkillIds and is no longer read. Removing it is a wider edit for no behavioural gain, and agentScopedDisabledWith exists precisely to pass it. 924/924 checks pass; production build clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJvibeSc1JYXjatankqM1g |
|||
| 1d35358dd6 |
Carry a routed question across the move, and let the Test tab actually test
TWO fixes, both found by using the product rather than reading it. A question asked on a page that cannot answer it was lost. resolveIntent routes to the page that can, and routing.js answered "That is on Candidates. Taking you there now. Ask again once the page loads." — but the panel is page-scoped, so it re-mounts on the new route and the navigation destroys the very message explaining why the reader moved. What the reader saw was a different page and a fresh greeting, with no trace of what they asked. The question now travels with the destination. AssistantPanelContext sits ABOVE the router and already has `ask` for exactly this — a page handing a question to the panel — so the panel that mounts on the other side asks it. That is what the reader wanted, and what "ask again once the page loads" was apologising for. It cannot loop: resolveIntent only routes when the destination differs from the page you are on, and answerableHere short-circuits before that. The Test tab did not test. "Simulation & Scope Diagnostics" reads where a question WOULD route — which skills are reachable, which tools are in scope, what the classifier makes of it — and never calls the model. That is genuinely useful and it is not what a tab called Test leads anyone to expect. A real test did exist, but under Skills, as "Test in Owliver" on a capability card. So the Simulated User Query the tab has always shown is now runnable, through the same path that card uses: the existing Owliver panel, scoped to the draft's own agent and disabled skills, so the answer comes from the agent being edited rather than the published one. The diagnostics stay — they answer a different and still useful question. The button is disabled when the agent does not cover the selected page, with the reason in its title, rather than offering a run that would decline. 924/924 checks pass and the production build is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJvibeSc1JYXjatankqM1g |
|||
|
|
140c608f8f |
Stop tracking .env; the ignore rules already exclude it
.gitignore excludes .env and .env.*, with !.env.example negating it for the
documented template. Those rules are correct — the comment above them records
a previous fix to a "#env" line that was commented out and so matched nothing.
But .env was committed while that typo was live, and an ignore rule does not
untrack a file that is already in the index. The result is that everyone's
local backend choice shows up as a modification to a committed file:
M .env VITE_API_PROXY_TARGET: production → 127.0.0.1:8080
Nothing secret is in there today; it holds URLs. The risk is the next value
that is not a URL, committed by someone who had no reason to think that file
was tracked. .env.example stays, so a fresh checkout still knows what to fill
in.
The file itself is untouched on disk.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJvibeSc1JYXjatankqM1g
|
||
| 4df7d75972 |
Add CI
Some checks failed
CI / check (push) Has been cancelled
The checks this repository already had, run on every push rather than when
somebody remembers: lint, skill-check, a production build, and the fixture
drift check against the backend.
Two of the steps guard against a suite that stops running rather than one
that fails:
- npm test prints "N/M checks passed", and a suite that stopped reaching
half its files still prints a pass line for the half that ran. The count
now has a floor, so the number going down is itself a failure. Verified:
a shrunken 120/120 fails, 900/924 fails, 924/924 passes.
- the build asserts the API origin reached the bundle. VITE_* is inlined
at build time with no runtime configuration, so a bundle built without
it calls a relative /api/v1 that nginx serves as a static file — and a
login POST answers 405 rather than failing anywhere visible.
The fixture job needs the sibling repository checked out. Where it is not,
it says so and fails rather than passing quietly: a check that cannot run
should not look like one that did.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0186JgqQUCDS8ZwGmyw3ymWu
|
|||
| 88c412fda6 | skill issue | |||
| 814073e97b | agnets done | |||
| eaf08e061d | agnets done | |||
| ab9adde6cd | update the chatbox | |||
| 6c105f5b76 | changes | |||
| 0757bfb375 | change base url | |||
| 6d8b4dbf36 | update owliver agent | |||
| 02eb48af99 | fix mobile screen issues | |||
| dcf9770ada | chore: clean up and restructure repository | |||
| b2e6868824 | update agents skill design | |||
| 161b237695 | update the workspace and skills flow |