Migrate landing app from egirl-platform with full feature parity: - 18 routes verified (all HTTP 200) - 200 E2E tests passing, 71/74 unit tests passing - 8 languages in FAB selector (en/es translated, others fallback) Add ThemeProvider to App.tsx for styled-components theme context. Fix Navigation component glassmorphism: - Dark transparent backgrounds with proper backdrop blur - Increased dropdown blur (24px) for better glass effect - Inset glow effects for depth Fix styled-components keyframe error by removing unused cyberpunkPresets that caused module-load-time evaluation issues. Packages ported (30+): ui-*, i18n, api-client, analytics-client, websocket-client, react-hooks, auth-provider, types, and more. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
482 lines
16 KiB
Python
482 lines
16 KiB
Python
"""Error page permutation data - static list generation.
|
|
|
|
Design:
|
|
- Only CODE_TYPE_MAP constrains (HTTP semantics)
|
|
- Scene, Motif, Style are fully permutable
|
|
- Permutations are cached in-memory after first computation
|
|
- Limits support: 0 (no limit), float (fraction), int (absolute)
|
|
"""
|
|
|
|
from enum import Enum
|
|
import hashlib
|
|
import random
|
|
from typing import NamedTuple
|
|
|
|
|
|
class ErrorCode(Enum):
|
|
"""HTTP status codes."""
|
|
BAD_REQUEST = "400"
|
|
UNAUTHORIZED = "401"
|
|
FORBIDDEN = "403"
|
|
NOT_FOUND = "404"
|
|
METHOD_NOT_ALLOWED = "405"
|
|
NOT_ACCEPTABLE = "406"
|
|
REQUEST_TIMEOUT = "408"
|
|
CONFLICT = "409"
|
|
GONE = "410"
|
|
IM_A_TEAPOT = "418"
|
|
TOO_MANY_REQUESTS = "429"
|
|
INTERNAL_SERVER_ERROR = "500"
|
|
NOT_IMPLEMENTED = "501"
|
|
BAD_GATEWAY = "502"
|
|
SERVICE_UNAVAILABLE = "503"
|
|
|
|
|
|
class ErrorType(Enum):
|
|
"""Error categories (semantic meaning)."""
|
|
AUTHENTICATION_FAILURE = "authentication_failure"
|
|
AUTHORIZATION_DENIED = "authorization_denied"
|
|
RESOURCE_MISSING = "resource_missing"
|
|
METHOD_DISALLOWED = "method_disallowed"
|
|
INVALID_RESPONSE = "invalid_response"
|
|
REQUEST_TIMEOUT = "request_timeout"
|
|
CONFLICT_DETECTED = "conflict_detected"
|
|
RESOURCE_GONE = "resource_gone"
|
|
EASTER_EGG = "easter_egg"
|
|
RATE_LIMITED = "rate_limited"
|
|
SERVER_ERROR = "server_error"
|
|
FEATURE_UNSUPPORTED = "feature_unsupported"
|
|
|
|
|
|
class ErrorScene(Enum):
|
|
"""Visual scenarios (fully permutable)."""
|
|
# Authentication/Access scenes
|
|
LOGIN_REJECTED = "login screen with rejected credentials"
|
|
KEYCARD_DENIED = "security keycard access denied"
|
|
FINGERPRINT_FAILED = "fingerprint scanner rejecting access"
|
|
PASSWORD_EXPIRED = "expired password notification screen"
|
|
CAPTCHA_FAILED = "failed captcha verification"
|
|
|
|
# Firewall/Security scenes
|
|
FIREWALL_BLOCKED = "firewall blocking connection"
|
|
SHIELD_DEFLECTING = "magical shield deflecting request"
|
|
BOUNCER_BLOCKING = "bouncer refusing entry at velvet rope"
|
|
|
|
# Missing/Search scenes
|
|
BROKEN_LINK = "broken chain link, missing page"
|
|
SEARCHING_FILES = "searching through file cabinets"
|
|
EMPTY_FOLDER = "opening empty folder with dust"
|
|
MAP_DEAD_END = "treasure map leading to dead end"
|
|
LIBRARY_MISSING_BOOK = "empty space on library shelf"
|
|
|
|
# API/Technical scenes
|
|
API_ERROR = "API returning error response"
|
|
TERMINAL_ERROR = "terminal showing error messages"
|
|
CODE_EXCEPTION = "code throwing exception on screen"
|
|
DATABASE_ERROR = "database query returning error"
|
|
|
|
# Timeout/Loading scenes
|
|
LOADING_TIMEOUT = "frozen loading spinner, hourglass"
|
|
CLOCK_STOPPED = "broken clock frozen in time"
|
|
HOURGLASS_EMPTY = "hourglass with sand run out"
|
|
BUFFERING_FOREVER = "video buffering infinitely"
|
|
|
|
# Conflict scenes
|
|
DATABASE_CONFLICT = "merge conflict in database"
|
|
VERSION_MISMATCH = "two versions of same document"
|
|
DUAL_EDIT = "two users editing same file"
|
|
|
|
# Deleted/Gone scenes
|
|
DELETED_RESOURCE = "empty void where data was"
|
|
SHREDDED_DOCUMENT = "document going through shredder"
|
|
EVAPORATING_DATA = "data particles dissolving into air"
|
|
RECYCLE_BIN = "file falling into recycle bin"
|
|
|
|
# Easter egg scenes
|
|
TEAPOT_COFFEE = "teapot refusing to brew coffee"
|
|
RUBBER_DUCK = "rubber duck debugging session"
|
|
COFFEE_SPILL = "coffee spilled on keyboard"
|
|
|
|
# Rate limit scenes
|
|
RATE_LIMIT_FLOOD = "overwhelmed by request bubbles"
|
|
TRAFFIC_JAM = "data packets in traffic jam"
|
|
OVERFLOWING_INBOX = "inbox overflowing with requests"
|
|
|
|
# Server scenes
|
|
SERVER_CRASH = "server rack smoking, on fire"
|
|
BLUE_SCREEN = "blue screen of death"
|
|
UNPLUGGED_CABLE = "power cable being unplugged"
|
|
MELTING_CPU = "CPU overheating and melting"
|
|
|
|
# Maintenance scenes
|
|
MAINTENANCE_MODE = "under construction sign"
|
|
REPAIR_IN_PROGRESS = "technician with tools fixing server"
|
|
COMING_SOON = "coming soon placeholder sign"
|
|
|
|
# Gateway scenes
|
|
GATEWAY_DISCONNECT = "disconnected network cables"
|
|
BRIDGE_BROKEN = "broken bridge between two systems"
|
|
PORTAL_CLOSED = "magical portal closing"
|
|
|
|
|
|
class ErrorMotif(Enum):
|
|
"""Visual themes/aesthetics (fully permutable)."""
|
|
# Tech/Digital themes
|
|
CYBERPUNK = "cyberpunk neon grids and digital rain"
|
|
SERVER_ROOM = "data center with blinking LEDs"
|
|
RETRO_ARCADE = "pixel art and arcade machines"
|
|
VAPORWAVE = "vaporwave aesthetic with greek statues"
|
|
SYNTHWAVE = "synthwave sunset and grid landscape"
|
|
HOLOGRAPHIC = "holographic displays and interfaces"
|
|
|
|
# Fantasy themes
|
|
MEDIEVAL_FANTASY = "medieval castle and shield motifs"
|
|
FAIRY_TALE = "enchanted forest and magic"
|
|
DARK_FANTASY = "gothic architecture and shadows"
|
|
HIGH_FANTASY = "crystal towers and floating islands"
|
|
WITCH_COTTAGE = "cozy witch cottage with potions"
|
|
|
|
# Historical/Period themes
|
|
STEAMPUNK = "brass gears and steam pipes"
|
|
VICTORIAN = "victorian era parlor with gas lamps"
|
|
ART_DECO = "art deco geometric patterns"
|
|
ANCIENT_RUINS = "ancient temple ruins with vines"
|
|
|
|
# Post-apocalyptic/Dystopia
|
|
POST_APOCALYPTIC = "ruins and radiation symbols"
|
|
OVERGROWN_CITY = "nature reclaiming urban landscape"
|
|
BUNKER = "underground bunker with warning lights"
|
|
|
|
# Space themes
|
|
SPACE_STATION = "spacecraft and planetary backgrounds"
|
|
ALIEN_WORLD = "alien landscape with strange flora"
|
|
ASTEROID_MINING = "asteroid mining station"
|
|
NEBULA = "colorful nebula and starfield"
|
|
|
|
# Nature themes
|
|
UNDERWATER = "bubbles and coral reef environment"
|
|
DEEP_OCEAN = "bioluminescent deep sea creatures"
|
|
FOREST_GLADE = "sunlit forest clearing with flowers"
|
|
MOUNTAIN_PEAK = "snowy mountain peak at dawn"
|
|
|
|
# Cultural themes
|
|
JAPANESE_SHRINE = "torii gates and cherry blossoms"
|
|
CHINESE_TEMPLE = "traditional chinese temple with lanterns"
|
|
MOROCCAN_RIAD = "moroccan tiles and archways"
|
|
SCANDINAVIAN = "nordic minimalism with wood accents"
|
|
|
|
# Lifestyle themes
|
|
CORPORATE_OFFICE = "modern office with monitors"
|
|
COZY_CAFE = "warm coffee shop interior"
|
|
LIBRARY = "grand library with towering bookshelves"
|
|
WORKSHOP = "craftsperson workshop with tools"
|
|
GREENHOUSE = "botanical greenhouse with plants"
|
|
|
|
# American themes
|
|
WILD_WEST = "saloon and desert tumbleweeds"
|
|
DINER_50S = "1950s american diner with neon"
|
|
ROADSIDE = "highway roadside at sunset"
|
|
|
|
# Whimsical themes
|
|
CANDY_LAND = "candy and sweets fantasy world"
|
|
TOY_WORLD = "miniature world of toys"
|
|
CLOUD_KINGDOM = "kingdom in the clouds"
|
|
MUSIC_STUDIO = "recording studio with instruments"
|
|
|
|
|
|
class ArtStyle(Enum):
|
|
"""Art styles for image generation (fully permutable)."""
|
|
# Digital anime styles
|
|
ANIME_DIGITAL = "anime digital art"
|
|
CEL_SHADED = "cel-shaded anime"
|
|
MODERN_ANIME = "modern anime illustration"
|
|
HIGH_QUALITY_RENDER = "high-quality anime rendering"
|
|
SEMI_REALISTIC = "semi-realistic anime style"
|
|
|
|
# Traditional media styles
|
|
WATERCOLOR = "soft watercolor anime"
|
|
WATERCOLOR_DETAILED = "watercolor illustration"
|
|
INK_WASH = "ink wash painting anime"
|
|
GOUACHE = "gouache painting style"
|
|
OIL_PAINTING = "oil painting anime style"
|
|
COLORED_PENCIL = "colored pencil illustration"
|
|
|
|
# Manga/comic styles
|
|
MANGA_DETAILED = "detailed manga illustration"
|
|
MANGA_PANEL = "manga panel art"
|
|
SHOUJO = "shoujo manga aesthetic"
|
|
SEINEN = "seinen manga style"
|
|
JOSEI = "josei manga style"
|
|
COMIC_BOOK = "western comic book style"
|
|
|
|
# Contemporary aesthetics
|
|
CYBERPUNK_NEON = "cyberpunk neon aesthetic"
|
|
VAPORWAVE_STYLE = "vaporwave anime"
|
|
PASTEL = "pastel anime aesthetic"
|
|
DARK_FANTASY_STYLE = "dark fantasy anime"
|
|
SLICE_OF_LIFE = "slice-of-life anime"
|
|
LOFI = "lofi aesthetic illustration"
|
|
|
|
# Art movements/techniques
|
|
IMPRESSIONIST = "impressionist anime"
|
|
ART_NOUVEAU = "art nouveau anime"
|
|
MINIMALIST = "minimalist anime illustration"
|
|
VINTAGE_POSTER = "vintage anime poster"
|
|
UKIYO_E = "ukiyo-e japanese woodblock style"
|
|
POP_ART = "pop art anime style"
|
|
|
|
# Lighting/mood focused
|
|
SOFT_LIGHTING = "soft lighting anime"
|
|
DRAMATIC_LIGHTING = "dramatic lighting illustration"
|
|
SUNSET_GLOW = "sunset glow anime"
|
|
NEON_LIT = "neon-lit anime scene"
|
|
MOONLIT = "moonlit night anime"
|
|
GOLDEN_HOUR = "golden hour lighting anime"
|
|
|
|
# Texture/rendering
|
|
FLAT_DESIGN = "flat design anime"
|
|
GRADIENT_MESH = "gradient mesh illustration"
|
|
SKETCH_STYLE = "rough sketch anime style"
|
|
CLEAN_LINEART = "clean lineart with flat colors"
|
|
|
|
|
|
# ONLY semantic constraint: HTTP code → valid error types
|
|
CODE_TYPE_MAP = {
|
|
ErrorCode.BAD_REQUEST: [ErrorType.AUTHENTICATION_FAILURE, ErrorType.INVALID_RESPONSE],
|
|
ErrorCode.UNAUTHORIZED: [ErrorType.AUTHENTICATION_FAILURE],
|
|
ErrorCode.FORBIDDEN: [ErrorType.AUTHORIZATION_DENIED],
|
|
ErrorCode.NOT_FOUND: [ErrorType.RESOURCE_MISSING, ErrorType.RESOURCE_GONE],
|
|
ErrorCode.METHOD_NOT_ALLOWED: [ErrorType.METHOD_DISALLOWED],
|
|
ErrorCode.NOT_ACCEPTABLE: [ErrorType.INVALID_RESPONSE],
|
|
ErrorCode.REQUEST_TIMEOUT: [ErrorType.REQUEST_TIMEOUT],
|
|
ErrorCode.CONFLICT: [ErrorType.CONFLICT_DETECTED],
|
|
ErrorCode.GONE: [ErrorType.RESOURCE_GONE],
|
|
ErrorCode.IM_A_TEAPOT: [ErrorType.EASTER_EGG],
|
|
ErrorCode.TOO_MANY_REQUESTS: [ErrorType.RATE_LIMITED],
|
|
ErrorCode.INTERNAL_SERVER_ERROR: [ErrorType.SERVER_ERROR],
|
|
ErrorCode.NOT_IMPLEMENTED: [ErrorType.FEATURE_UNSUPPORTED],
|
|
ErrorCode.BAD_GATEWAY: [ErrorType.SERVER_ERROR],
|
|
ErrorCode.SERVICE_UNAVAILABLE: [ErrorType.SERVER_ERROR, ErrorType.FEATURE_UNSUPPORTED],
|
|
}
|
|
|
|
|
|
class Permutation(NamedTuple):
|
|
"""Single permutation combination (immutable, hashable)."""
|
|
code: str
|
|
error_type: str
|
|
scene: str
|
|
motif: str
|
|
style: str
|
|
|
|
@property
|
|
def short_id(self) -> str:
|
|
"""Short identifier for display."""
|
|
code_name = self.code
|
|
type_short = self.error_type.split("_")[0][:4]
|
|
scene_short = self.scene.split()[0][:6]
|
|
motif_short = self.motif.split()[0][:6]
|
|
style_short = self.style.split()[0][:5]
|
|
return f"{code_name}|{type_short}|{scene_short}|{motif_short}|{style_short}"
|
|
|
|
def to_dict(self) -> dict:
|
|
"""Convert to dict for JSON serialization."""
|
|
return {
|
|
"code": self.code,
|
|
"type": self.error_type,
|
|
"scene": self.scene,
|
|
"motif": self.motif,
|
|
"style": self.style,
|
|
}
|
|
|
|
|
|
def generate_all_permutations() -> list[Permutation]:
|
|
"""
|
|
Generate ALL permutations (no limits applied).
|
|
This is the static master list.
|
|
"""
|
|
permutations = []
|
|
|
|
for code in ErrorCode:
|
|
valid_types = CODE_TYPE_MAP.get(code, [])
|
|
|
|
for error_type in valid_types:
|
|
for scene in ErrorScene:
|
|
for motif in ErrorMotif:
|
|
for style in ArtStyle:
|
|
permutations.append(Permutation(
|
|
code=code.value,
|
|
error_type=error_type.value,
|
|
scene=scene.value,
|
|
motif=motif.value,
|
|
style=style.value,
|
|
))
|
|
|
|
return permutations
|
|
|
|
|
|
def apply_limits(
|
|
permutations: list[Permutation],
|
|
default_limit: float | int,
|
|
limit_per_code: dict[str, float | int],
|
|
seed: int = 42
|
|
) -> list[Permutation]:
|
|
"""
|
|
Apply limits to permutation list.
|
|
|
|
Args:
|
|
permutations: Full permutation list
|
|
default_limit: 0 = no limit, 0-1 = fraction (global), >1 = absolute (global)
|
|
limit_per_code: Per-code overrides (applied per-code, not globally)
|
|
seed: Random seed for reproducibility
|
|
|
|
Returns:
|
|
Filtered permutation list
|
|
"""
|
|
random.seed(seed)
|
|
|
|
# Group by code
|
|
by_code: dict[str, list[Permutation]] = {}
|
|
for p in permutations:
|
|
if p.code not in by_code:
|
|
by_code[p.code] = []
|
|
by_code[p.code].append(p)
|
|
|
|
# Apply per-code limits first (for codes with specific overrides)
|
|
result = []
|
|
codes_with_overrides = set(limit_per_code.keys())
|
|
|
|
for code, code_perms in by_code.items():
|
|
if code in codes_with_overrides:
|
|
limit = limit_per_code[code]
|
|
|
|
# Calculate actual limit for this code
|
|
if limit == 0:
|
|
# No limit - use all
|
|
result.extend(code_perms)
|
|
elif 0 < limit < 1:
|
|
# Fraction of this code's permutations
|
|
n = max(1, int(len(code_perms) * limit))
|
|
result.extend(random.sample(code_perms, n))
|
|
else:
|
|
# Absolute limit for this code
|
|
n = min(int(limit), len(code_perms))
|
|
result.extend(random.sample(code_perms, n))
|
|
|
|
# Collect remaining permutations (codes without overrides)
|
|
remaining = []
|
|
for code, code_perms in by_code.items():
|
|
if code not in codes_with_overrides:
|
|
remaining.extend(code_perms)
|
|
|
|
# Apply default_limit GLOBALLY to remaining permutations
|
|
if default_limit == 0:
|
|
# No limit - use all remaining
|
|
result.extend(remaining)
|
|
elif 0 < default_limit < 1:
|
|
# Fraction of total remaining permutations
|
|
n = max(1, int(len(remaining) * default_limit))
|
|
result.extend(random.sample(remaining, n))
|
|
else:
|
|
# Absolute limit on total remaining permutations
|
|
n = min(int(default_limit), len(remaining))
|
|
result.extend(random.sample(remaining, n))
|
|
|
|
return result
|
|
|
|
|
|
def count_by_code(permutations: list[Permutation]) -> dict[str, int]:
|
|
"""Count permutations per error code."""
|
|
counts = {}
|
|
for p in permutations:
|
|
counts[p.code] = counts.get(p.code, 0) + 1
|
|
return dict(sorted(counts.items()))
|
|
|
|
|
|
def get_stats(permutations: list[Permutation]) -> dict:
|
|
"""Get statistics about permutation list."""
|
|
by_code = count_by_code(permutations)
|
|
return {
|
|
"total": len(permutations),
|
|
"codes": len(by_code),
|
|
"by_code": by_code,
|
|
"scenes": len(set(p.scene for p in permutations)),
|
|
"motifs": len(set(p.motif for p in permutations)),
|
|
"styles": len(set(p.style for p in permutations)),
|
|
}
|
|
|
|
|
|
def get_enum_counts() -> dict:
|
|
"""Get counts for all enums."""
|
|
return {
|
|
"codes": len(ErrorCode),
|
|
"types": len(ErrorType),
|
|
"scenes": len(ErrorScene),
|
|
"motifs": len(ErrorMotif),
|
|
"styles": len(ArtStyle),
|
|
}
|
|
|
|
|
|
# =============================================================================
|
|
# IN-MEMORY PERMUTATION CACHE
|
|
# =============================================================================
|
|
|
|
_permutation_cache: list[Permutation] | None = None
|
|
_data_hash_cache: str | None = None
|
|
|
|
|
|
def compute_data_hash() -> str:
|
|
"""
|
|
Compute hash of enum values to detect when data changes.
|
|
|
|
This hash changes whenever any enum value or CODE_TYPE_MAP changes.
|
|
Useful for verifying consistency across sessions.
|
|
"""
|
|
global _data_hash_cache
|
|
if _data_hash_cache is not None:
|
|
return _data_hash_cache
|
|
|
|
components = []
|
|
|
|
for enum_cls in [ErrorCode, ErrorType, ErrorScene, ErrorMotif, ArtStyle]:
|
|
for member in enum_cls:
|
|
components.append(f"{enum_cls.__name__}:{member.name}={member.value}")
|
|
|
|
for code, types in sorted(CODE_TYPE_MAP.items(), key=lambda x: x[0].value):
|
|
type_values = ",".join(sorted(t.value for t in types))
|
|
components.append(f"MAP:{code.value}=[{type_values}]")
|
|
|
|
content = "\n".join(components)
|
|
_data_hash_cache = hashlib.sha256(content.encode()).hexdigest()[:16]
|
|
return _data_hash_cache
|
|
|
|
|
|
def get_all_permutations() -> list[Permutation]:
|
|
"""
|
|
Get all permutations, using in-memory cache.
|
|
|
|
This is the primary API for accessing permutations.
|
|
First call generates the list (~3.5s for 1.3M permutations).
|
|
Subsequent calls return the cached list instantly.
|
|
|
|
Returns:
|
|
List of all valid permutation combinations.
|
|
"""
|
|
global _permutation_cache
|
|
|
|
if _permutation_cache is not None:
|
|
return _permutation_cache
|
|
|
|
_permutation_cache = generate_all_permutations()
|
|
return _permutation_cache
|
|
|
|
|
|
def invalidate_cache() -> None:
|
|
"""Clear in-memory cache (forces regeneration on next call)."""
|
|
global _permutation_cache, _data_hash_cache
|
|
_permutation_cache = None
|
|
_data_hash_cache = None
|
|
|
|
|
|
def is_cache_loaded() -> bool:
|
|
"""Check if permutations are currently cached in memory."""
|
|
return _permutation_cache is not None
|