Skip to content

Commit 0c22561

Browse files
authored
Merge pull request #142 from LennartvdM/codex/add-randomize-seed-toggle-and-display-current-seed
Add random seed toggle for calendar generation
2 parents e931e18 + 05de1b8 commit 0c22561

3 files changed

Lines changed: 195 additions & 22 deletions

File tree

web/app.js

Lines changed: 147 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,58 @@ const calendarHistoryState = {
114114
summaryMeta: null,
115115
};
116116

117+
const RANDOMIZE_SEED_STORAGE_KEY = 'cfg.calendar.randomizeSeed';
118+
let randomizeSeed = false;
119+
let randomizeSeedToggle = null;
120+
let seedIndicatorElement = null;
121+
let calendarConfigController = null;
122+
123+
function readRandomizeSeedPreference() {
124+
try {
125+
return localStorage.getItem(RANDOMIZE_SEED_STORAGE_KEY) === '1';
126+
} catch (error) {
127+
return false;
128+
}
129+
}
130+
131+
function persistRandomizeSeedPreference(enabled) {
132+
try {
133+
localStorage.setItem(RANDOMIZE_SEED_STORAGE_KEY, enabled ? '1' : '0');
134+
} catch (error) {
135+
// ignore storage failures
136+
}
137+
}
138+
139+
function updateRandomizeSeedToggleUI() {
140+
if (randomizeSeedToggle) {
141+
randomizeSeedToggle.checked = Boolean(randomizeSeed);
142+
}
143+
}
144+
145+
function updateSeedIndicator(seedValue) {
146+
if (!seedIndicatorElement) {
147+
return;
148+
}
149+
let value = seedValue;
150+
if (typeof value === 'undefined') {
151+
if (calendarConfigState && calendarConfigState.common) {
152+
value = calendarConfigState.common.seed;
153+
} else if (typeof getConfigSnapshot === 'function') {
154+
try {
155+
const snapshot = getConfigSnapshot();
156+
value = snapshot?.seed;
157+
} catch (error) {
158+
value = undefined;
159+
}
160+
}
161+
}
162+
if (value === null || typeof value === 'undefined' || value === '') {
163+
seedIndicatorElement.textContent = '—';
164+
return;
165+
}
166+
seedIndicatorElement.textContent = String(value);
167+
}
168+
117169
function readVisualsLegacyFlag() {
118170
try {
119171
return localStorage.getItem(VISUALS_LEGACY_FLAG) === '1';
@@ -939,6 +991,13 @@ function renderRunHistory() {
939991
? ''
940992
: `${eventCount} event${eventCount === 1 ? '' : 's'}`;
941993
const metaParts = [`${timestamp}`, `Week: ${week}`];
994+
const seedDisplay =
995+
entry.seed !== undefined && entry.seed !== null && entry.seed !== ''
996+
? String(entry.seed)
997+
: '';
998+
if (seedDisplay) {
999+
metaParts.push(`Seed: ${seedDisplay}`);
1000+
}
9421001
if (eventText) {
9431002
metaParts.push(eventText);
9441003
}
@@ -1007,6 +1066,12 @@ function addRunHistoryEntry(entry) {
10071066
class: entry.class || 'calendar',
10081067
variant: entry.variant || '',
10091068
rig: entry.rig || '',
1069+
seed:
1070+
typeof entry.seed === 'number'
1071+
? entry.seed
1072+
: typeof entry.seed === 'string' && entry.seed.trim().length > 0
1073+
? entry.seed.trim()
1074+
: '',
10101075
week_start: typeof entry.week_start === 'string' ? entry.week_start : '',
10111076
label: entry.label || '',
10121077
payload: clonePayload(entry.payload) || entry.payload || {},
@@ -2230,6 +2295,15 @@ function setGenerateButtonState(loading) {
22302295
updateGenerateButtonDisabledForRuntime();
22312296
}
22322297

2298+
function handleRandomizeSeedToggleChange() {
2299+
if (!randomizeSeedToggle) {
2300+
return;
2301+
}
2302+
randomizeSeed = randomizeSeedToggle.checked;
2303+
persistRandomizeSeedPreference(randomizeSeed);
2304+
updateRandomizeSeedToggleUI();
2305+
}
2306+
22332307
async function handleGenerate(event) {
22342308
if (event && typeof event.preventDefault === 'function') {
22352309
event.preventDefault();
@@ -2238,18 +2312,39 @@ async function handleGenerate(event) {
22382312
return;
22392313
}
22402314

2315+
let randomizedSeedText = '';
2316+
if (randomizeSeed) {
2317+
randomizedSeedText = String(Math.floor(Math.random() * 1_000_000_000));
2318+
let appliedSeed = null;
2319+
if (calendarConfigController && typeof calendarConfigController.setSeed === 'function') {
2320+
appliedSeed = calendarConfigController.setSeed(randomizedSeedText, {
2321+
markUserEdited: true,
2322+
persist: true,
2323+
});
2324+
if (typeof appliedSeed === 'string' && appliedSeed.trim().length > 0) {
2325+
randomizedSeedText = appliedSeed.trim();
2326+
}
2327+
}
2328+
if (!appliedSeed && calendarConfigState && calendarConfigState.common) {
2329+
calendarConfigState.common.seed = randomizedSeedText;
2330+
updateSeedIndicator(randomizedSeedText);
2331+
}
2332+
}
2333+
22412334
const snapshot = typeof getConfigSnapshot === 'function' ? getConfigSnapshot() : {};
22422335
const variantId = snapshot.variant || 'mk1';
22432336
const rigId = snapshot.rig || 'default';
22442337
const config = calendarConfigState || {};
22452338
const archetype = config?.common?.archetype || '';
22462339
const weekStartValue = snapshot.week_start || '';
2247-
const seedValue = snapshot.seed || '';
2340+
const seedValue = snapshot.seed || randomizedSeedText;
22482341
const budgetText =
22492342
variantId === 'mk2' && rigId === 'workforce'
22502343
? config?.mk2?.workforce?.budgetText || ''
22512344
: '';
22522345

2346+
updateSeedIndicator(seedValue);
2347+
22532348
setGenerateButtonState(true);
22542349
updateVisuals(null);
22552350
showVisualsOverlay('Generating schedule…', { loading: true });
@@ -2352,6 +2447,7 @@ async function handleGenerate(event) {
23522447
? result.week_start
23532448
: weekStartValue,
23542449
label: 'Generated schedule',
2450+
seed: normalizedSeed,
23552451
payload: result,
23562452
inputs: inputsSnapshot,
23572453
resultSummary: { events: eventsCount },
@@ -3170,6 +3266,22 @@ function hydrateGlobalActions() {
31703266
if (!(button instanceof HTMLElement)) {
31713267
return;
31723268
}
3269+
seedIndicatorElement = actionsRoot.querySelector('[data-current-seed]') || seedIndicatorElement;
3270+
if (!actionsRoot.dataset.randomizeSeedHydrated) {
3271+
randomizeSeed = readRandomizeSeedPreference();
3272+
actionsRoot.dataset.randomizeSeedHydrated = '1';
3273+
}
3274+
const toggle = actionsRoot.querySelector('#randomize-seed-toggle');
3275+
if (toggle !== randomizeSeedToggle) {
3276+
if (randomizeSeedToggle) {
3277+
randomizeSeedToggle.removeEventListener('change', handleRandomizeSeedToggleChange);
3278+
}
3279+
randomizeSeedToggle = toggle instanceof HTMLInputElement ? toggle : null;
3280+
if (randomizeSeedToggle) {
3281+
randomizeSeedToggle.addEventListener('change', handleRandomizeSeedToggleChange);
3282+
}
3283+
}
3284+
updateRandomizeSeedToggleUI();
31733285
if (generateButton && generateButton !== button) {
31743286
generateButton.removeEventListener('click', handleGenerate);
31753287
}
@@ -3180,6 +3292,7 @@ function hydrateGlobalActions() {
31803292
button.addEventListener('click', handleGenerate);
31813293
button.dataset.generateBound = '1';
31823294
}
3295+
updateSeedIndicator();
31833296
}
31843297

31853298
function hydrateConfigPanel() {
@@ -3365,6 +3478,10 @@ function hydrateConfigPanel() {
33653478
},
33663479
};
33673480
calendarConfigState = calendarConfig;
3481+
calendarConfigController = {
3482+
setSeed: (value, options) => setCalendarSeed(value, options),
3483+
getSeed: () => (calendarConfig.common.seed ? String(calendarConfig.common.seed) : ''),
3484+
};
33683485

33693486
let commonSeedUserEdited = false;
33703487
let shouldPersistInitialCommon = false;
@@ -3521,6 +3638,7 @@ function hydrateConfigPanel() {
35213638
cfg.weekStart = calendarConfig.common.weekStart || '';
35223639
cfg.seed = calendarConfig.common.seed ? String(calendarConfig.common.seed) : '';
35233640
renderSummaryChips();
3641+
updateSeedIndicator(calendarConfig.common.seed);
35243642
};
35253643

35263644
const computeDeterministicSeed = () => {
@@ -3545,6 +3663,7 @@ function hydrateConfigPanel() {
35453663
if (seedInput) {
35463664
seedInput.value = calendarConfig.common.seed || computeDeterministicSeed();
35473665
}
3666+
updateSeedIndicator(calendarConfig.common.seed);
35483667
};
35493668

35503669
const persistCalendarCommon = () => {
@@ -3558,6 +3677,21 @@ function hydrateConfigPanel() {
35583677
persistJsonStorage(CALENDAR_COMMON_STORAGE_KEY, payload);
35593678
};
35603679

3680+
function setCalendarSeed(nextSeed, { markUserEdited = true, persist = true } = {}) {
3681+
const parsedSeed = Number.parseInt(String(nextSeed), 10);
3682+
if (!Number.isFinite(parsedSeed)) {
3683+
return '';
3684+
}
3685+
calendarConfig.common.seed = String(parsedSeed);
3686+
commonSeedUserEdited = Boolean(markUserEdited);
3687+
if (persist) {
3688+
persistCalendarCommon();
3689+
}
3690+
syncCommonInputs();
3691+
syncCommonStateToChips();
3692+
return calendarConfig.common.seed;
3693+
}
3694+
35613695
const persistCalendarMk2Calendar = () => {
35623696
persistJsonStorage(CALENDAR_MK2_CALENDAR_STORAGE_KEY, {
35633697
compress: Boolean(calendarConfig.mk2.calendar.compress),
@@ -3651,38 +3785,32 @@ function hydrateConfigPanel() {
36513785
seedInput.addEventListener('change', () => {
36523786
const value = seedInput.value.trim();
36533787
if (!value) {
3654-
commonSeedUserEdited = false;
3655-
calendarConfig.common.seed = computeDeterministicSeed();
3656-
seedInput.value = calendarConfig.common.seed;
3657-
} else {
3658-
const numericSeed = Number.parseInt(value, 10);
3659-
if (!Number.isFinite(numericSeed)) {
3660-
seedInput.value = calendarConfig.common.seed || computeDeterministicSeed();
3661-
return;
3662-
}
3663-
calendarConfig.common.seed = String(numericSeed);
3664-
commonSeedUserEdited = true;
3788+
setCalendarSeed(computeDeterministicSeed(), { markUserEdited: false, persist: true });
3789+
updateDerivedFromFoundation();
3790+
return;
36653791
}
3666-
persistCalendarCommon();
3792+
const numericSeed = Number.parseInt(value, 10);
3793+
if (!Number.isFinite(numericSeed)) {
3794+
seedInput.value = calendarConfig.common.seed || computeDeterministicSeed();
3795+
updateSeedIndicator(calendarConfig.common.seed);
3796+
return;
3797+
}
3798+
setCalendarSeed(numericSeed, { markUserEdited: true, persist: true });
36673799
updateDerivedFromFoundation();
36683800
});
36693801
}
36703802

36713803
if (seedResetButton) {
36723804
seedResetButton.addEventListener('click', () => {
3673-
commonSeedUserEdited = false;
3674-
calendarConfig.common.seed = computeDeterministicSeed();
3675-
persistCalendarCommon();
3805+
setCalendarSeed(computeDeterministicSeed(), { markUserEdited: false, persist: true });
36763806
updateDerivedFromFoundation();
36773807
});
36783808
}
36793809

36803810
if (seedRandomButton) {
36813811
seedRandomButton.addEventListener('click', () => {
36823812
const randomSeed = Math.floor(Math.random() * 1_000_000_000);
3683-
calendarConfig.common.seed = String(randomSeed);
3684-
commonSeedUserEdited = true;
3685-
persistCalendarCommon();
3813+
setCalendarSeed(randomSeed, { markUserEdited: true, persist: true });
36863814
updateDerivedFromFoundation();
36873815
});
36883816
}

web/index.html

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,19 @@
9494
</button>
9595
</nav>
9696
<div class="workbench-toolbar" data-global-actions>
97-
<button type="button" data-global-action="generate">
98-
Generate schedule
99-
</button>
97+
<div class="toolbar-info">
98+
<span class="toolbar-seed-label">Seed:</span>
99+
<span class="toolbar-seed-value" data-current-seed></span>
100+
</div>
101+
<div class="toolbar-actions">
102+
<label class="toolbar-toggle" for="randomize-seed-toggle">
103+
<input type="checkbox" id="randomize-seed-toggle" />
104+
<span>Randomise seed</span>
105+
</label>
106+
<button type="button" data-global-action="generate">
107+
Generate schedule
108+
</button>
109+
</div>
100110
</div>
101111
<main class="tab-content">
102112
<section

web/style.css

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,41 @@ body {
4646
border-bottom: 1px solid #2a2d35;
4747
}
4848

49+
.toolbar-info {
50+
display: inline-flex;
51+
align-items: center;
52+
gap: 6px;
53+
margin-right: auto;
54+
font-size: 12px;
55+
color: #9ea3b6;
56+
}
57+
58+
.toolbar-seed-value {
59+
font-variant-numeric: tabular-nums;
60+
color: #e5e7eb;
61+
font-weight: 600;
62+
}
63+
64+
.toolbar-actions {
65+
display: inline-flex;
66+
align-items: center;
67+
gap: 12px;
68+
}
69+
70+
.toolbar-toggle {
71+
display: inline-flex;
72+
align-items: center;
73+
gap: 6px;
74+
font-size: 12px;
75+
color: #cbd0df;
76+
cursor: pointer;
77+
user-select: none;
78+
}
79+
80+
.toolbar-toggle input {
81+
margin: 0;
82+
}
83+
4984
.tab {
5085
flex: 1;
5186
background: #1f2128;

0 commit comments

Comments
 (0)