Files
krow_talent_app/scripts/i18n-smoke.mjs
Aravind 26f5116bb0 Remember the last few turns, and stop citation markup reaching the reader
TWO THINGS A READER SAW TODAY.

Owliver had no memory. A run is one turn — the API takes an `input` and no
message list, and agent_runs records each run independently — which is right for
an API and wrong for a panel that looks like a conversation. "Which of those is
at risk?" arrived with no "those".

The proper fix is a `messages` array on the run request. This is not that: the
transcript already lives in the browser, so it travels inside the question until
the API grows a field for it. recall.ts is shaped like that future field so the
swap is a deletion.

BOUNDED IN TOKENS, NOT TURNS, because turns are not a unit of cost: three short
exchanges are nothing and three carrying a table each is a question that no
longer fits. The deployment allows 8,000 tokens a minute and a heavy run already
spends most of it, so recall gets a 600-token ceiling — about 7% of a minute —
each turn clipped to 400 characters, and eviction oldest-first, because dropping
the most recent exchange drops the one the follow-up is about.

The transcript is fenced and labelled as data on the same terms as retrieved
documents: an earlier answer is the model's own words, but an earlier QUESTION
is the reader's, and a reader can type anything.

CITATIONS. context.go hands the model <source id="…"> and said "cite it" without
saying how, so it invented a format per answer. The panel stripped four; a
reader got three it had never seen — the <source> tag echoed back, 【uuid】 in
fullwidth brackets, and <br> drawn as text by the Markdown renderer. All three
are stripped now, <br> becoming a real newline so bullets stay on separate
lines. The fullwidth rule matches horizontal whitespace only: \s* swallowed the
newline a <br> had just become and ran two bullets together, which the test
caught.

Verified: 1732/1732 skill-checks, clean typecheck and build. The citation rules
carry the production answer verbatim as a case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-07 19:46:17 +05:30

60 lines
3.1 KiB
JavaScript

/* Renders every admin page in both languages and fails on a crash or a raw
key reaching the screen — the two failures a reader would notice first. */
import { createServer } from 'vite';
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { join } from 'node:path';
import { withSourceResolution } from './ssr-resolve.mjs';
if (!('window' in globalThis)) {
globalThis.window = { matchMedia: () => ({ matches: false, addEventListener(){}, removeEventListener(){} }),
addEventListener(){}, removeEventListener(){} };
globalThis.localStorage = { getItem: () => null, setItem(){}, removeItem(){} };
}
const server = await createServer({ root: process.cwd(), server: { middlewareMode: true },
appType: 'custom', logLevel: 'error',
resolve: { alias: { 'react-hot-toast': join(process.cwd(), 'scripts/stubs/react-hot-toast.js') } } });
withSourceResolution(server);
const { QueryClient, QueryClientProvider } = await import('@tanstack/react-query');
const { MemoryRouter } = await import('react-router-dom');
const { UiEditingProvider } = await server.ssrLoadModule('/src/components/ui-tree/UiEditingProvider.jsx');
const i18n = (await server.ssrLoadModule('/src/lib/i18n/index.ts')).default;
const PAGES = [
['Control Center', '/src/pages/admin/ControlCenter.jsx', '/admin', 'control-center'],
['Positions', '/src/pages/admin/Positions.jsx', '/admin/positions', 'positions'],
['Candidates', '/src/pages/admin/Candidates.jsx', '/admin/candidates', 'candidates'],
['Talent Pool', '/src/pages/admin/TalentPool.jsx', '/admin/talent-pool', 'talent-pool'],
['Analytics', '/src/pages/admin/Analytics.jsx', '/admin/analytics', 'analytics'],
['Hired History', '/src/pages/admin/HiredHistory.jsx', '/admin/hired', 'hired-history'],
['Activity', '/src/pages/admin/Activity.jsx', '/admin/activity', 'activity'],
];
const KEYISH = /\b[a-z][A-Za-z0-9]{2,}\.[a-z][A-Za-z0-9]{2,}\b/g;
let bad = 0;
for (const [name, path, route, key] of PAGES) {
for (const lng of ['en', 'es']) {
await i18n.changeLanguage(lng);
let html;
try {
const Page = (await server.ssrLoadModule(path)).default;
const client = new QueryClient({ defaultOptions: { queries: { retry: false, enabled: false } } });
html = renderToStaticMarkup(
React.createElement(MemoryRouter, { initialEntries: [route] },
React.createElement(QueryClientProvider, { client },
React.createElement(UiEditingProvider, { page: key }, React.createElement(Page)))));
} catch (err) {
console.log(` CRASH ${name} [${lng}] — ${String(err.message).slice(0, 80)}`); bad += 1; continue;
}
const text = html.replace(/<[^>]*>/g, ' ');
const keys = [...new Set(text.match(KEYISH) || [])].filter((k) => !/\.(jsx?|tsx?|json|md|com|app)$/.test(k));
if (keys.length) { console.log(` KEYS ${name} [${lng}] — ${keys.slice(0, 5).join(', ')}`); bad += 1; }
else console.log(` ok ${name} [${lng}]`);
}
}
await i18n.changeLanguage('en');
console.log(bad ? `\n ${bad} problem(s)` : '\n all pages render in both languages, no raw keys');
await server.close();
process.exitCode = bad ? 1 : 0;