Cleanup: extract constants, fix typecheck, add ESLint, organize store types
Balance Check / balance-simulation (push) Successful in 37s
Balance Check / multi-run-balance (push) Successful in 13m39s
CI / build-and-push (push) Failing after 19s

- 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:
2026-04-28 22:45:32 -04:00
parent 6ea136083a
commit ea3951aa0c
13 changed files with 780 additions and 47 deletions
+9 -6
View File
@@ -7,6 +7,9 @@ import { createToken } from '../lib/jwt';
import { authMiddleware } from '../middleware/auth';
import type { AppEnv } from '../types';
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const MIN_PASSWORD_LENGTH = 8;
const auth = new Hono<AppEnv>();
auth.post('/anonymous', async (c) => {
@@ -27,11 +30,11 @@ auth.post('/register', authMiddleware, async (c) => {
inviteCode: string;
}>();
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
if (!email || !EMAIL_REGEX.test(email)) {
return c.json({ error: 'Valid email required' }, 400);
}
if (!password || password.length < 8) {
return c.json({ error: 'Password must be at least 8 characters' }, 400);
if (!password || password.length < MIN_PASSWORD_LENGTH) {
return c.json({ error: `Password must be at least ${MIN_PASSWORD_LENGTH} characters` }, 400);
}
if (process.env.REQUIRE_INVITE !== 'false') {
@@ -117,8 +120,8 @@ auth.post('/change-password', authMiddleware, async (c) => {
newPassword: string;
}>();
if (!newPassword || newPassword.length < 8) {
return c.json({ error: 'New password must be at least 8 characters' }, 400);
if (!newPassword || newPassword.length < MIN_PASSWORD_LENGTH) {
return c.json({ error: `New password must be at least ${MIN_PASSWORD_LENGTH} characters` }, 400);
}
if (!user.mustResetPassword) {
@@ -193,7 +196,7 @@ auth.post('/change-email', authMiddleware, async (c) => {
currentPassword: string;
}>();
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
if (!email || !EMAIL_REGEX.test(email)) {
return c.json({ error: 'Valid email required' }, 400);
}
+1 -1
View File
@@ -25,7 +25,7 @@ savesRouter.get('/', async (c) => {
.from(saves)
.where(eq(saves.userId, userId))
.orderBy(desc(saves.updatedAt))
.limit(10);
.limit(MAX_SAVES_PER_USER);
return c.json({ saves: userSaves });
});
-2
View File
@@ -37,7 +37,6 @@ export function useAuthGate(): AuthGateState {
const [admin, setAdmin] = useState(false);
const [cloudSave, setCloudSave] = useState<CloudSaveInfo | null>(null);
const [hasConflict, setHasConflict] = useState(false);
const [initCount, setInitCount] = useState(0);
const init = useCallback(async () => {
setLoading(true);
@@ -98,7 +97,6 @@ export function useAuthGate(): AuthGateState {
useState(() => { init(); });
const retry = useCallback(() => {
setInitCount(c => c + 1);
init();
}, [init]);
+2 -30
View File
@@ -46,37 +46,9 @@ import {
TECH_TREE, onModelDeployed,
} 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'
| '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 };
}
export type { ActivePage, InfraNavLevel, InfraNav, ModelsTab, UIState, GameNotification } from './types';
function emptyDC(): Pick<DataCenter, 'networkSummary' | 'effectiveComputeRacks' | 'usedSlots' | 'usedPowerKW' | 'energyCostPerTick' | 'maintenanceCostPerTick' | 'currentUptime'> {
return {
+30
View File
@@ -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 };
}