Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/common/utils/accessibility-preferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Validate and apply player accessibility preferences. (#419)
*/
export interface AccessibilityPreferences {
highContrast: boolean;
textToSpeech: boolean;
fontSizeScale: number;
colorBlindMode: "none" | "protanopia" | "deuteranopia" | "tritanopia";
}

export const DEFAULT_ACCESSIBILITY_PREFERENCES: AccessibilityPreferences = {
highContrast: false,
textToSpeech: false,
fontSizeScale: 1,
colorBlindMode: "none",
};

export function validateFontSizeScale(scale: number): boolean {
return scale >= 0.75 && scale <= 2.0;
}

export function mergeAccessibilityPreferences(
overrides: Partial<AccessibilityPreferences>,
): AccessibilityPreferences {
const merged = { ...DEFAULT_ACCESSIBILITY_PREFERENCES, ...overrides };
if (!validateFontSizeScale(merged.fontSizeScale)) {
merged.fontSizeScale = DEFAULT_ACCESSIBILITY_PREFERENCES.fontSizeScale;
}
return merged;
}
21 changes: 21 additions & 0 deletions src/common/utils/bandwidth-adaptive-payload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Select a mobile-appropriate response payload variant based on the
* client's reported network condition. (#420)
*/
export type NetworkType = "wifi" | "4g" | "3g" | "2g" | "offline";
export type PayloadVariant = "full" | "reduced" | "minimal";

export function selectPayloadVariant(network: NetworkType): PayloadVariant {
if (network === "wifi" || network === "4g") return "full";
if (network === "3g") return "reduced";
return "minimal";
}

export function shouldDeferSync(network: NetworkType): boolean {
return network === "offline" || network === "2g";
}

export function estimatedPayloadSizeKb(variant: PayloadVariant, baseSizeKb: number): number {
const factor = variant === "full" ? 1 : variant === "reduced" ? 0.5 : 0.2;
return Math.round(baseSizeKb * factor);
}
34 changes: 34 additions & 0 deletions src/common/utils/search-relevance-scorer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Score search result relevance using term-frequency overlap, for the
* advanced search and discovery feature. (#421)
*/
export interface SearchableDocument {
id: string;
title: string;
tags: string[];
}

function tokenize(text: string): string[] {
return text.toLowerCase().split(/\W+/).filter(Boolean);
}

export function scoreSearchResult(query: string, doc: SearchableDocument): number {
const queryTerms = tokenize(query);
const titleTerms = tokenize(doc.title);
const tagTerms = doc.tags.map((tag) => tag.toLowerCase());

let score = 0;
for (const term of queryTerms) {
if (titleTerms.includes(term)) score += 2;
if (tagTerms.includes(term)) score += 1;
}
return score;
}

export function searchAndRank(query: string, documents: SearchableDocument[]): SearchableDocument[] {
return documents
.map((doc) => ({ doc, score: scoreSearchResult(query, doc) }))
.filter((entry) => entry.score > 0)
.sort((a, b) => b.score - a.score)
.map((entry) => entry.doc);
}
22 changes: 22 additions & 0 deletions src/common/utils/test-data-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Generic test data factory to reduce fixture boilerplate. (#416)
*/
export type FactoryDefaults<T> = () => T;
export type FactoryOverrides<T> = Partial<T> | ((base: T) => Partial<T>);

export function createFactory<T>(defaults: FactoryDefaults<T>) {
return function build(overrides: FactoryOverrides<T> = {}): T {
const base = defaults();
const patch = typeof overrides === "function" ? overrides(base) : overrides;
return { ...base, ...patch };
};
}

export function buildMany<T>(build: (overrides?: FactoryOverrides<T>) => T, count: number): T[] {
return Array.from({ length: count }, () => build());
}

export function sequence(prefix: string): () => string {
let counter = 0;
return () => `${prefix}-${++counter}`;
}
Loading