Wave 6: refactor generators batch 4 to crypto random

- refactor(generators): migrate Teams, Time, Uuid, Weighted to crypto random
- refactor(generators): migrate WheelSpinner, YesNo to crypto random

All 28 generators now use cryptographically secure random via src/lib/client/random.ts
This commit is contained in:
emil
2026-05-13 20:41:27 +03:00
parent 8e0485c215
commit 0081510914
8 changed files with 92 additions and 69 deletions
+28 -2
View File
@@ -8,7 +8,7 @@
"plan_name": "critical-refactor",
"status": "active",
"started_at": "2026-05-13T16:25:19.266Z",
"updated_at": "2026-05-13T17:34:08.166Z",
"updated_at": "2026-05-13T17:41:13.262Z",
"session_ids": [
"ses_1de6ede59ffexUwt5C0sddEWwR"
],
@@ -120,6 +120,19 @@
"status": "completed",
"ended_at": "2026-05-13T17:34:08.166Z",
"elapsed_ms": 93514
},
"todo:27": {
"task_key": "todo:27",
"task_label": "27",
"task_title": "Refactor TeamsGenerator.astro",
"session_id": "ses_1dd94b493ffemX2z2dEFdh8W7R",
"agent": "Sisyphus-Junior",
"category": "unspecified-high",
"started_at": "2026-05-13T17:37:23.881Z",
"status": "completed",
"updated_at": "2026-05-13T17:41:13.262Z",
"ended_at": "2026-05-13T17:41:13.262Z",
"elapsed_ms": 229381
}
}
}
@@ -127,7 +140,7 @@
"active_plan": "/home/emil/Desktop/Coding/AI/Randify.pro/.sisyphus/plans/critical-refactor.md",
"started_at": "2026-05-13T16:25:19.266Z",
"status": "active",
"updated_at": "2026-05-13T17:34:08.166Z",
"updated_at": "2026-05-13T17:41:13.262Z",
"session_ids": [
"ses_1de6ede59ffexUwt5C0sddEWwR"
],
@@ -239,6 +252,19 @@
"status": "completed",
"ended_at": "2026-05-13T17:34:08.166Z",
"elapsed_ms": 93514
},
"todo:27": {
"task_key": "todo:27",
"task_label": "27",
"task_title": "Refactor TeamsGenerator.astro",
"session_id": "ses_1dd94b493ffemX2z2dEFdh8W7R",
"agent": "Sisyphus-Junior",
"category": "unspecified-high",
"started_at": "2026-05-13T17:37:23.881Z",
"status": "completed",
"updated_at": "2026-05-13T17:41:13.262Z",
"ended_at": "2026-05-13T17:41:13.262Z",
"elapsed_ms": 229381
}
},
"agent": "atlas"
+6 -6
View File
@@ -635,7 +635,7 @@ Max Concurrent: 8 (Wave 3)
- Message: `refactor(generators): migrate batch 3 to crypto random (Meal, Names, Number, Palette, Password, Rps, Shuffler)`
- Files: `src/components/generators/{Meal,Names,Number,Palette,Password,Rps,Shuffler}Generator.astro`
- [ ] 27. Refactor TeamsGenerator.astro
- [x] 27. Refactor TeamsGenerator.astro
**What to do**: Same pattern as Task 6.
**Recommended Agent Profile**: `unspecified-high`
**Parallelization**: YES (with Tasks 13-19, 20-26, 28-32)
@@ -643,7 +643,7 @@ Max Concurrent: 8 (Wave 3)
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
**Commit**: NO
- [ ] 28. Refactor TimeGenerator.astro
- [x] 28. Refactor TimeGenerator.astro
**What to do**: Same pattern as Task 6.
**Recommended Agent Profile**: `unspecified-high`
**Parallelization**: YES (with Tasks 13-19, 20-26, 27, 29-32)
@@ -651,7 +651,7 @@ Max Concurrent: 8 (Wave 3)
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
**Commit**: NO
- [ ] 29. Refactor UuidGenerator.astro
- [x] 29. Refactor UuidGenerator.astro
**What to do**: Same pattern as Task 6.
**Recommended Agent Profile**: `unspecified-high`
**Parallelization**: YES (with Tasks 13-19, 20-26, 27-28, 30-32)
@@ -659,7 +659,7 @@ Max Concurrent: 8 (Wave 3)
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
**Commit**: NO
- [ ] 30. Refactor WeightedGenerator.astro
- [x] 30. Refactor WeightedGenerator.astro
**What to do**: Same pattern as Task 6.
**Recommended Agent Profile**: `unspecified-high`
**Parallelization**: YES (with Tasks 13-19, 20-26, 27-29, 31-32)
@@ -667,7 +667,7 @@ Max Concurrent: 8 (Wave 3)
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
**Commit**: NO
- [ ] 31. Refactor WheelSpinner.astro
- [x] 31. Refactor WheelSpinner.astro
**What to do**: Same pattern as Task 6. Note: file is `WheelSpinner.astro`, not `WheelGenerator.astro`.
**Recommended Agent Profile**: `unspecified-high`
**Parallelization**: YES (with Tasks 13-19, 20-26, 27-30, 32)
@@ -675,7 +675,7 @@ Max Concurrent: 8 (Wave 3)
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
**Commit**: NO
- [ ] 32. Refactor YesNoGenerator.astro
- [x] 32. Refactor YesNoGenerator.astro
**What to do**: Same pattern as Task 6.
**Recommended Agent Profile**: `unspecified-high`
**Parallelization**: YES (with Tasks 13-19, 20-26, 27-31)
+13 -21
View File
@@ -139,21 +139,22 @@ const errMorePlayers = isRu
<script>
import { CopyFeedback } from "@/lib/client/clipboard";
import { createErrorDisplay } from "@/lib/client/validation";
import { shuffleArray } from "@/lib/client/random";
import { getClientLang, getClientT } from "@/lib/client/i18n";
const isRu = document.documentElement.lang === "ru";
const COPY_LABEL = isRu ? "Копировать" : "Copy";
const COPIED_LABEL = isRu ? "Скопировано" : "Copied";
const TEAM_PREFIX = isRu ? "Команда" : "Team";
const ERR_ADD_TWO = isRu
const lang = getClientLang();
const T = getClientT();
const TEAM_PREFIX = lang === "ru" ? "Команда" : "Team";
const ERR_ADD_TWO = lang === "ru"
? "Добавьте хотя бы 2 участника."
: "Add at least 2 participants.";
const ERR_TEAMS_MIN = isRu
const ERR_TEAMS_MIN = lang === "ru"
? "Минимум 2 команды."
: "Minimum 2 teams.";
const ERR_TEAMS_MAX = isRu
const ERR_TEAMS_MAX = lang === "ru"
? "Максимум 20 команд."
: "Maximum 20 teams.";
const ERR_MORE_PLAYERS = isRu
const ERR_MORE_PLAYERS = lang === "ru"
? "Участников должно быть больше, чем команд."
: "Participants must outnumber teams.";
@@ -201,17 +202,8 @@ const errMorePlayers = isRu
const errors = createErrorDisplay(errorEl);
const clipboard = new CopyFeedback(copyIcon, checkIcon);
function shuffle<T>(arr: T[]): T[] {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
function splitIntoTeams(items: string[], teamCount: number): string[][] {
const shuffled = shuffle(items);
const shuffled = shuffleArray(items);
const teams: string[][] = Array.from({ length: teamCount }, () => []);
shuffled.forEach((item, i) => {
teams[i % teamCount].push(item);
@@ -281,7 +273,7 @@ const errMorePlayers = isRu
});
copyBtn.classList.remove("invisible");
copyLabel.textContent = COPY_LABEL;
copyLabel.textContent = T.copy;
}
async function copyResults() {
@@ -295,10 +287,10 @@ const errMorePlayers = isRu
await navigator.clipboard.writeText(text);
clipboard.showCopied();
copyLabel.textContent = COPIED_LABEL;
copyLabel.textContent = T.copied;
setTimeout(() => {
copyLabel.textContent = COPY_LABEL;
copyLabel.textContent = T.copy;
}, 1500);
}
@@ -174,8 +174,12 @@ const T = useT(isRu ? "ru" : "en");
import { CopyFeedback } from "@/lib/client/clipboard";
import { createErrorDisplay } from "@/lib/client/validation";
import { popElement } from "@/lib/client/animations";
import { randomInt } from "@/lib/client/random";
import { getClientLang, getClientT } from "@/lib/client/i18n";
const isRu = document.documentElement.lang === "ru";
const lang = getClientLang();
const isRu = lang === "ru";
const T = getClientT();
const ERR_FROM_BEFORE_TO = isRu
? "«От» должно быть раньше «До»."
@@ -369,11 +373,11 @@ const T = useT(isRu ? "ru" : "en");
// Generate random time within range, respecting interval
const rangeMinutes = toMins - fromMins;
const maxSteps = Math.floor(rangeMinutes / interval);
const randomStep = Math.floor(Math.random() * (maxSteps + 1));
const randomStep = randomInt(0, maxSteps);
const randomTotalMinutes = fromMins + randomStep * interval;
const { h, m } = minutesToTime(randomTotalMinutes);
const s = Math.floor(Math.random() * 60);
const s = randomInt(0, 59);
displayResult(h, m, s);
}
+7 -13
View File
@@ -133,10 +133,10 @@ const T = useT(isRu ? "ru" : "en");
<script>
import { CopyFeedback } from "@/lib/client/clipboard";
import { getClientT } from "@/lib/client/i18n";
import { randomInt, randomBytes } from "@/lib/client/random";
const isRu = document.documentElement.lang === "ru";
const COPY_ALL_LABEL = isRu ? "Копировать всё" : "Copy all";
const COPIED_LABEL = isRu ? "Скопировано" : "Copied";
const T = getClientT();
const lengthWrap = document.getElementById(
"ug-length-wrap",
@@ -173,17 +173,11 @@ const T = useT(isRu ? "ru" : "en");
function uuidV4(): string {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
const r = crypto.getRandomValues(new Uint8Array(1))[0] & 0xf;
const r = randomInt(0, 15);
return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
});
}
function randomBytes(n: number): Uint8Array {
const buf = new Uint8Array(n);
crypto.getRandomValues(buf);
return buf;
}
function toHex(bytes: Uint8Array): string {
return Array.from(bytes)
.map((b) => b.toString(16).padStart(2, "0"))
@@ -223,7 +217,7 @@ const T = useT(isRu ? "ru" : "en");
});
copyBtn.classList.remove("invisible");
copyLabel.textContent = COPY_ALL_LABEL;
copyLabel.textContent = T.copyAll;
}
async function copyAll() {
@@ -231,10 +225,10 @@ const T = useT(isRu ? "ru" : "en");
await navigator.clipboard.writeText(lastTokens.join("\n"));
clipboard.showCopied();
copyLabel.textContent = COPIED_LABEL;
copyLabel.textContent = T.copied;
setTimeout(() => {
copyLabel.textContent = COPY_ALL_LABEL;
copyLabel.textContent = T.copyAll;
}, 1500);
}
@@ -126,13 +126,17 @@ const T = useT(isRu ? "ru" : "en");
<script>
import { CopyFeedback } from "@/lib/client/clipboard";
import { createErrorDisplay } from "@/lib/client/validation";
import { randomFloat } from "@/lib/client/random";
import { getClientLang, getClientT } from "@/lib/client/i18n";
const isRu = document.documentElement.lang === "ru";
const COPY_LABEL = isRu ? "Копировать" : "Copy";
const COPIED_LABEL = isRu ? "Скопировано" : "Copied";
const lang = getClientLang();
const isRu = lang === "ru";
const T = getClientT();
const COPY_LABEL = T.copy;
const COPIED_LABEL = T.copied;
const ERR_ITEM_EMPTY = isRu ? "Имя элемента не может быть пустым." : "Item name cannot be empty.";
const ERR_WEIGHT_POSITIVE = isRu ? "Вес должен быть положительным числом." : "Weight must be a positive number.";
const ERR_ADD_AT_LEAST_ONE = isRu ? "Добавьте хотя бы один элемент с весом." : "Add at least one item with a weight.";
const ERR_ADD_AT_LEAST_ONE = T.errAddAtLeast1;
const LABEL_WEIGHT = isRu ? "Вес" : "Weight";
const LABEL_ITEM = isRu ? "Элемент" : "Item";
const LABEL_REMOVE = isRu ? "Удалить" : "Remove";
@@ -252,7 +256,7 @@ const T = useT(isRu ? "ru" : "en");
function weightedPick(items: { name: string; weight: number }[]): { name: string; weight: number } {
const totalWeight = items.reduce((sum, item) => sum + item.weight, 0);
let random = Math.random() * totalWeight;
let random = randomFloat() * totalWeight;
for (const item of items) {
random -= item.weight;
+16 -16
View File
@@ -139,14 +139,14 @@ Frank</textarea
<script>
import { CopyFeedback } from "@/lib/client/clipboard";
import { createErrorDisplay } from "@/lib/client/validation";
import { randomInt, randomFloat } from "@/lib/client/random";
import { getClientT } from "@/lib/client/i18n";
const isRu = document.documentElement.lang === "ru";
const COPY_LABEL = isRu ? "Копировать" : "Copy";
const COPIED_LABEL = isRu ? "Скопировано" : "Copied";
const ERR_MIN2 = isRu
? "Добавьте хотя бы 2 элемента."
: "Add at least 2 items.";
const ERR_MAX24 = isRu ? "Максимум 24 элемента." : "Maximum 24 items.";
const T = getClientT();
const COPY_LABEL = T.copy;
const COPIED_LABEL = T.copied;
const ERR_MIN2 = T.errAddAtLeast2;
const ERR_MAX24 = T.errMax24;
const COLORS = [
"#534AB7",
@@ -299,19 +299,19 @@ Frank</textarea
const n = items.length;
const seg = TAU / n;
const winnerIdx = Math.floor(Math.random() * n);
const winnerIdx = randomInt(0, n - 1);
const desiredPhase = (((-(winnerIdx + 0.5) * seg) % TAU) + TAU) % TAU;
const currentPhase = ((currentRotation % TAU) + TAU) % TAU;
let delta = (desiredPhase - currentPhase + TAU) % TAU;
if (delta < 0.5) delta += TAU;
const jitter = (Math.random() - 0.5) * seg * 0.55;
const fullTurns = 5 + Math.floor(Math.random() * 3);
const jitter = (randomFloat() - 0.5) * seg * 0.55;
const fullTurns = randomInt(5, 7);
const totalDelta = fullTurns * TAU + delta + jitter;
const startRot = currentRotation;
const duration = 4000 + Math.random() * 2000;
const duration = 4000 + randomFloat() * 2000;
const t0 = performance.now();
isSpinning = true;
@@ -378,22 +378,22 @@ Frank</textarea
for (let i = 0; i < 60; i++) {
const el = document.createElement("div");
const w = 4 + Math.random() * 6;
const h = 4 + Math.random() * 6;
const w = 4 + randomFloat() * 6;
const h = 4 + randomFloat() * 6;
el.style.cssText =
`position:fixed;width:${w}px;height:${h}px;` +
`background:${COLORS[i % COLORS.length]};border-radius:1px;` +
`pointer-events:none;left:${cx}px;top:${cy}px;will-change:transform,opacity;z-index:9999;`;
document.body.appendChild(el);
const angle = Math.random() * TAU;
const speed = 4 + Math.random() * 7;
const angle = randomFloat() * TAU;
const speed = 4 + randomFloat() * 7;
particles.push({
el,
x: cx,
y: cy,
vx: Math.cos(angle) * speed,
vy: -(Math.abs(Math.sin(angle)) * speed + 2 + Math.random() * 4),
vy: -(Math.abs(Math.sin(angle)) * speed + 2 + randomFloat() * 4),
});
}
@@ -111,8 +111,11 @@ const confidenceLabel = isRu ? "Уверенность" : "Confidence";
<script>
import { CopyFeedback } from "@/lib/client/clipboard";
import { popElement } from "@/lib/client/animations";
import { randomInt } from "@/lib/client/random";
import { getClientLang, getClientT } from "@/lib/client/i18n";
const isRu = document.documentElement.lang === "ru";
const isRu = getClientLang() === "ru";
const T = getClientT();
const ANSWERS = isRu
? ["ДА", "НЕТ", "ВОЗМОЖНО"]
: ["YES", "NO", "MAYBE"];
@@ -149,8 +152,8 @@ const confidenceLabel = isRu ? "Уверенность" : "Confidence";
}
function generate() {
const answer = ANSWERS[Math.floor(Math.random() * ANSWERS.length)];
const confidence = Math.floor(Math.random() * 51) + 50;
const answer = ANSWERS[randomInt(0, ANSWERS.length - 1)];
const confidence = randomInt(50, 100);
resultEl.textContent = answer;
copyBtn.classList.remove("invisible");