Cleanup: extract constants, fix typecheck, add ESLint, organize store types
- Remove unused initCount state from useAuthGate hook - Replace magic number with MAX_SAVES_PER_USER constant in saves route - Extract duplicated EMAIL_REGEX and MIN_PASSWORD_LENGTH in auth routes - Fix game-simulation typecheck failure by adding DOM lib to tsconfig - Extract store UI types (ActivePage, InfraNav, etc.) to store/types.ts - Fix let→const for non-reassigned arrays in servingPipeline - Fix useless initial assignments in reputationSystem - Fix ambiguous multiline array access in sanityChecks - Add minimal ESLint config with typescript-eslint - Add .planning/ and *.log to .gitignore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,3 +10,5 @@ balance-report*.json
|
|||||||
balance-metrics*.csv
|
balance-metrics*.csv
|
||||||
multirun-summary.csv
|
multirun-summary.csv
|
||||||
multirun-timeseries.csv
|
multirun-timeseries.csv
|
||||||
|
.planning/
|
||||||
|
*.log
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import { createToken } from '../lib/jwt';
|
|||||||
import { authMiddleware } from '../middleware/auth';
|
import { authMiddleware } from '../middleware/auth';
|
||||||
import type { AppEnv } from '../types';
|
import type { AppEnv } from '../types';
|
||||||
|
|
||||||
|
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
const MIN_PASSWORD_LENGTH = 8;
|
||||||
|
|
||||||
const auth = new Hono<AppEnv>();
|
const auth = new Hono<AppEnv>();
|
||||||
|
|
||||||
auth.post('/anonymous', async (c) => {
|
auth.post('/anonymous', async (c) => {
|
||||||
@@ -27,11 +30,11 @@ auth.post('/register', authMiddleware, async (c) => {
|
|||||||
inviteCode: string;
|
inviteCode: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
if (!email || !EMAIL_REGEX.test(email)) {
|
||||||
return c.json({ error: 'Valid email required' }, 400);
|
return c.json({ error: 'Valid email required' }, 400);
|
||||||
}
|
}
|
||||||
if (!password || password.length < 8) {
|
if (!password || password.length < MIN_PASSWORD_LENGTH) {
|
||||||
return c.json({ error: 'Password must be at least 8 characters' }, 400);
|
return c.json({ error: `Password must be at least ${MIN_PASSWORD_LENGTH} characters` }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.REQUIRE_INVITE !== 'false') {
|
if (process.env.REQUIRE_INVITE !== 'false') {
|
||||||
@@ -117,8 +120,8 @@ auth.post('/change-password', authMiddleware, async (c) => {
|
|||||||
newPassword: string;
|
newPassword: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
if (!newPassword || newPassword.length < 8) {
|
if (!newPassword || newPassword.length < MIN_PASSWORD_LENGTH) {
|
||||||
return c.json({ error: 'New password must be at least 8 characters' }, 400);
|
return c.json({ error: `New password must be at least ${MIN_PASSWORD_LENGTH} characters` }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.mustResetPassword) {
|
if (!user.mustResetPassword) {
|
||||||
@@ -193,7 +196,7 @@ auth.post('/change-email', authMiddleware, async (c) => {
|
|||||||
currentPassword: string;
|
currentPassword: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
if (!email || !EMAIL_REGEX.test(email)) {
|
||||||
return c.json({ error: 'Valid email required' }, 400);
|
return c.json({ error: 'Valid email required' }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ savesRouter.get('/', async (c) => {
|
|||||||
.from(saves)
|
.from(saves)
|
||||||
.where(eq(saves.userId, userId))
|
.where(eq(saves.userId, userId))
|
||||||
.orderBy(desc(saves.updatedAt))
|
.orderBy(desc(saves.updatedAt))
|
||||||
.limit(10);
|
.limit(MAX_SAVES_PER_USER);
|
||||||
|
|
||||||
return c.json({ saves: userSaves });
|
return c.json({ saves: userSaves });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ export function useAuthGate(): AuthGateState {
|
|||||||
const [admin, setAdmin] = useState(false);
|
const [admin, setAdmin] = useState(false);
|
||||||
const [cloudSave, setCloudSave] = useState<CloudSaveInfo | null>(null);
|
const [cloudSave, setCloudSave] = useState<CloudSaveInfo | null>(null);
|
||||||
const [hasConflict, setHasConflict] = useState(false);
|
const [hasConflict, setHasConflict] = useState(false);
|
||||||
const [initCount, setInitCount] = useState(0);
|
|
||||||
|
|
||||||
const init = useCallback(async () => {
|
const init = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -98,7 +97,6 @@ export function useAuthGate(): AuthGateState {
|
|||||||
useState(() => { init(); });
|
useState(() => { init(); });
|
||||||
|
|
||||||
const retry = useCallback(() => {
|
const retry = useCallback(() => {
|
||||||
setInitCount(c => c + 1);
|
|
||||||
init();
|
init();
|
||||||
}, [init]);
|
}, [init]);
|
||||||
|
|
||||||
|
|||||||
@@ -46,37 +46,9 @@ import {
|
|||||||
TECH_TREE, onModelDeployed,
|
TECH_TREE, onModelDeployed,
|
||||||
} from '@token-empire/game-engine';
|
} from '@token-empire/game-engine';
|
||||||
import { INITIAL_RIVALS } from '@token-empire/game-engine';
|
import { INITIAL_RIVALS } from '@token-empire/game-engine';
|
||||||
|
import type { ActivePage, InfraNav, ModelsTab, UIState, GameNotification } from './types';
|
||||||
|
|
||||||
export type ActivePage = 'dashboard' | 'infrastructure' | 'research' | 'models'
|
export type { ActivePage, InfraNavLevel, InfraNav, ModelsTab, UIState, GameNotification } from './types';
|
||||||
| 'market' | 'serving' | 'talent' | 'data' | 'competitors' | 'finance' | 'achievements' | 'leaderboard' | 'invitations' | 'settings';
|
|
||||||
|
|
||||||
export type InfraNavLevel = 'clusters' | 'cluster' | 'campus' | 'datacenter';
|
|
||||||
|
|
||||||
export interface InfraNav {
|
|
||||||
level: InfraNavLevel;
|
|
||||||
clusterId?: string;
|
|
||||||
campusId?: string;
|
|
||||||
datacenterId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type ModelsTab = 'overview' | 'train' | 'models' | 'products';
|
|
||||||
|
|
||||||
interface UIState {
|
|
||||||
activePage: ActivePage;
|
|
||||||
notifications: GameNotification[];
|
|
||||||
infraNav: InfraNav;
|
|
||||||
modelsTab: ModelsTab;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GameNotification {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
message: string;
|
|
||||||
type: 'info' | 'success' | 'warning' | 'danger';
|
|
||||||
tick: number;
|
|
||||||
read: boolean;
|
|
||||||
action?: { label: string; page?: ActivePage; modelsTab?: ModelsTab };
|
|
||||||
}
|
|
||||||
|
|
||||||
function emptyDC(): Pick<DataCenter, 'networkSummary' | 'effectiveComputeRacks' | 'usedSlots' | 'usedPowerKW' | 'energyCostPerTick' | 'maintenanceCostPerTick' | 'currentUptime'> {
|
function emptyDC(): Pick<DataCenter, 'networkSummary' | 'effectiveComputeRacks' | 'usedSlots' | 'usedPowerKW' | 'energyCostPerTick' | 'maintenanceCostPerTick' | 'currentUptime'> {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
export type ActivePage = 'dashboard' | 'infrastructure' | 'research' | 'models'
|
||||||
|
| 'market' | 'serving' | 'talent' | 'data' | 'competitors' | 'finance' | 'achievements' | 'leaderboard' | 'invitations' | 'settings';
|
||||||
|
|
||||||
|
export type InfraNavLevel = 'clusters' | 'cluster' | 'campus' | 'datacenter';
|
||||||
|
|
||||||
|
export interface InfraNav {
|
||||||
|
level: InfraNavLevel;
|
||||||
|
clusterId?: string;
|
||||||
|
campusId?: string;
|
||||||
|
datacenterId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ModelsTab = 'overview' | 'train' | 'models' | 'products';
|
||||||
|
|
||||||
|
export interface UIState {
|
||||||
|
activePage: ActivePage;
|
||||||
|
notifications: GameNotification[];
|
||||||
|
infraNav: InfraNav;
|
||||||
|
modelsTab: ModelsTab;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GameNotification {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
type: 'info' | 'success' | 'warning' | 'danger';
|
||||||
|
tick: number;
|
||||||
|
read: boolean;
|
||||||
|
action?: { label: string; page?: ActivePage; modelsTab?: ModelsTab };
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import eslint from '@eslint/js';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
eslint.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
ignores: ['**/dist/**', '**/node_modules/**', '**/*.js', '**/*.mjs', '**/drizzle/**'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
||||||
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
|
'no-empty': ['error', { allowEmptyCatch: true }],
|
||||||
|
'preserve-caught-error': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
+4
-1
@@ -5,7 +5,7 @@
|
|||||||
"dev": "turbo dev",
|
"dev": "turbo dev",
|
||||||
"build": "turbo build",
|
"build": "turbo build",
|
||||||
"typecheck": "turbo typecheck",
|
"typecheck": "turbo typecheck",
|
||||||
"lint": "turbo lint",
|
"lint": "eslint .",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:watch": "vitest",
|
"test:watch": "vitest",
|
||||||
"clean": "turbo clean",
|
"clean": "turbo clean",
|
||||||
@@ -13,8 +13,11 @@
|
|||||||
"simulate:ci": "pnpm --filter @token-empire/game-simulation simulate:ci"
|
"simulate:ci": "pnpm --filter @token-empire/game-simulation simulate:ci"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^10.0.1",
|
||||||
|
"eslint": "^10.2.1",
|
||||||
"turbo": "^2.5.0",
|
"turbo": "^2.5.0",
|
||||||
"typescript": "^5.8.0",
|
"typescript": "^5.8.0",
|
||||||
|
"typescript-eslint": "^8.59.1",
|
||||||
"vitest": "^4.1.5"
|
"vitest": "^4.1.5"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.33.0",
|
"packageManager": "pnpm@10.33.0",
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ interface CachedSlot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let cachedDeploymentVersion = -1;
|
let cachedDeploymentVersion = -1;
|
||||||
let cachedSlots: CachedSlot[] = [];
|
const cachedSlots: CachedSlot[] = [];
|
||||||
const fleetOutput: ModelServingSlot[] = [];
|
const fleetOutput: ModelServingSlot[] = [];
|
||||||
|
|
||||||
const mainRemaining = new Map<string, number>();
|
const mainRemaining = new Map<string, number>();
|
||||||
@@ -83,7 +83,7 @@ const mainUsed = new Map<string, number>();
|
|||||||
const entRemaining = new Map<string, number>();
|
const entRemaining = new Map<string, number>();
|
||||||
const entUsed = new Map<string, number>();
|
const entUsed = new Map<string, number>();
|
||||||
|
|
||||||
let cachedUtilization: ModelUtilizationEntry[] = [];
|
const cachedUtilization: ModelUtilizationEntry[] = [];
|
||||||
|
|
||||||
export function resetFleetCache(): void {
|
export function resetFleetCache(): void {
|
||||||
cachedDeploymentVersion = -1;
|
cachedDeploymentVersion = -1;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export interface ReputationTickResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function processReputation(state: GameState, researchBonuses?: ResearchBonuses): ReputationState & { _safetyIncident?: boolean } {
|
export function processReputation(state: GameState, researchBonuses?: ResearchBonuses): ReputationState & { _safetyIncident?: boolean } {
|
||||||
let { safetyRecord, publicPerception, employeeSatisfaction, regulatoryStanding } = state.reputation;
|
let { safetyRecord, publicPerception } = state.reputation;
|
||||||
|
|
||||||
let safetyIncident = false;
|
let safetyIncident = false;
|
||||||
if (state.models.bestDeployedSafetyScore > 0) {
|
if (state.models.bestDeployedSafetyScore > 0) {
|
||||||
@@ -39,13 +39,13 @@ export function processReputation(state: GameState, researchBonuses?: ResearchBo
|
|||||||
const safetyResearchCount = state.research.completedResearch
|
const safetyResearchCount = state.research.completedResearch
|
||||||
.filter(r => r.includes('alignment') || r.includes('interpretability') || r.includes('constitutional')).length;
|
.filter(r => r.includes('alignment') || r.includes('interpretability') || r.includes('constitutional')).length;
|
||||||
const complianceBonus = safetyResearchCount * 8;
|
const complianceBonus = safetyResearchCount * 8;
|
||||||
regulatoryStanding = Math.min(100, Math.max(0,
|
const regulatoryStanding = Math.min(100, Math.max(0,
|
||||||
50 + complianceBonus - regulatoryPressure,
|
50 + complianceBonus - regulatoryPressure,
|
||||||
));
|
));
|
||||||
|
|
||||||
const talentMorale = Object.values(state.talent.departments)
|
const talentMorale = Object.values(state.talent.departments)
|
||||||
.reduce((sum, d) => sum + d.morale, 0) / 4;
|
.reduce((sum, d) => sum + d.morale, 0) / 4;
|
||||||
employeeSatisfaction = talentMorale * 100;
|
const employeeSatisfaction = talentMorale * 100;
|
||||||
|
|
||||||
const reputationResearchBonus = researchBonuses?.reputationBonus ?? 0;
|
const reputationResearchBonus = researchBonuses?.reputationBonus ?? 0;
|
||||||
publicPerception = Math.min(100, publicPerception + reputationResearchBonus * PUBLIC_PERCEPTION_GROWTH_RATE);
|
publicPerception = Math.min(100, publicPerception + reputationResearchBonus * PUBLIC_PERCEPTION_GROWTH_RATE);
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ export function runSanityChecks(metrics: SimulationMetrics[]): SanityCheckResult
|
|||||||
const key = 'reputation-scale-consistency';
|
const key = 'reputation-scale-consistency';
|
||||||
if (!seen.has(key)) {
|
if (!seen.has(key)) {
|
||||||
seen.add(key);
|
seen.add(key);
|
||||||
const lowName = ['safetyRecord', 'publicPerception', 'employeeSatisfaction', 'regulatoryStanding']
|
const reputationFields = ['safetyRecord', 'publicPerception', 'employeeSatisfaction', 'regulatoryStanding'];
|
||||||
[components.indexOf(belowThreshold[0])];
|
const lowName = reputationFields[components.indexOf(belowThreshold[0])];
|
||||||
violations.push({
|
violations.push({
|
||||||
tick: m.tick, check: key,
|
tick: m.tick, check: key,
|
||||||
message: `${lowName} = ${belowThreshold[0].toFixed(2)} while others are 10+. Likely a scale mismatch (0-1 vs 0-100)`,
|
message: `${lowName} = ${belowThreshold[0].toFixed(2)} while others are 10+. Likely a scale mismatch (0-1 vs 0-100)`,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"extends": "@token-empire/tsconfig/node.json",
|
"extends": "@token-empire/tsconfig/node.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"lib": ["ES2022", "DOM"],
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"rootDir": "src"
|
"rootDir": "src"
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+706
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user