chore: add CI, lint config, login rate limit and split large modules

- Rate-limit failed logins (8 per 15 min per IP or username, 429 + Retry-After)
- Add ruff/mypy config and GitHub Actions workflow (ruff, pytest, tsc, tests, build)
- Apply ruff autofixes (import order, pyupgrade)
- Extract rpy_helpers.py, engine-util.ts, TranslationParts.tsx and
  translation-types.ts from oversized modules
- Add dependency-free React UI tests
- Refresh README and ARCHITECTURE (Naninovel/Tomie, status and plans)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Sonnet 5.5 committed 2026-10-25 11:23:22 +03:00
1 parent 57a4edcbfd
commit bbbcc6a5d8
42 files changed
+678 -391

No files matched your search

+3 -47
View File
@@ -1,35 +1,17 @@
// PGVN interpreter + in-game UI.
import { AudioSystem, type Volumes } from "./audio";
import { AudioSystem } from "./audio";
import { JumpSignal, PyDict, Python, decodeValue, encodeValue, pyStr, truthy, type Scope } from "./python";
import { Renderer, type ResolvedImage } from "./renderer";
import { fastPoint, interpolate, parseText, plainText, renderText, waitPoints } from "./text";
import { languageName, rememberLanguage, type Translator } from "./i18n";
import { naniText } from "./naninovel";
import { DEFAULT_SETTINGS, SLOTS, Restart, el, loadJSON, saveJSON, type SaveApi, type SaveMeta, type Settings } from "./engine-util";
import type {
CharacterDef, Expr, Frame, GameData, ImageDef, Label, Layer, Manifest, Node, SaveData, ScriptModule,
} from "./types";
export interface SaveMeta {
slot: string;
title: string;
updated_at: string;
}
export interface SaveApi<T = SaveData> {
list(): Promise<SaveMeta[]>;
get(slot: string): Promise<T | null>;
put(slot: string, title: string, data: T): Promise<void>;
remove(slot: string): Promise<void>;
}
interface Settings extends Volumes {
cps: number; // characters per second, 0 = instant
autoDelay: number; // seconds per 20 characters in auto mode
}
const DEFAULT_SETTINGS: Settings = { cps: 45, autoDelay: 1.2, music: 0.7, sound: 0.8, voice: 1 };
const SLOTS = 12;
export type { SaveApi, SaveMeta } from "./engine-util";
interface LabelRef {
label: Label;
@@ -37,32 +19,6 @@ interface LabelRef {
index: number;
}
class Restart extends Error {}
function el<K extends keyof HTMLElementTagNameMap>(tag: K, cls = "", text = ""): HTMLElementTagNameMap[K] {
const e = document.createElement(tag);
if (cls) e.className = cls;
if (text) e.textContent = text;
return e;
}
function loadJSON<T>(key: string, fallback: T): T {
try {
const raw = localStorage.getItem(key);
return raw ? { ...fallback, ...JSON.parse(raw) } : fallback;
} catch {
return fallback;
}
}
function saveJSON(key: string, value: unknown) {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch {
/* storage unavailable */
}
}
export class Engine {
private labels = new Map<string, LabelRef>();
private anchors = new Map<string, { label: string; path: number[]; params?: [string, Expr | null][] | null }>();