From d63b7bc1159f4f2b7de04f67c5243acc7c141eda Mon Sep 17 00:00:00 2001 From: jagdeep sidhu Date: Thu, 23 Apr 2026 09:06:13 -0700 Subject: [PATCH 01/12] feat(gov-proposals): derive voting window from duration input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the wizard's raw start_epoch / end_epoch inputs with a single "Duration (months)" field. The on-chain voting window is computed deterministically at /prepare time from that duration and the live next-superblock anchor, rather than asking the user to type two UTC epoch integers. Why --- The old UX had three interrelated controls (payment count, start epoch, end epoch) where only one — the count of payments the user actually wants — is meaningful. The other two had to be manually aligned to superblock cadence and the voting-window semantics that Core enforces, otherwise the proposal would either pay fewer months than advertised or silently allocate an extra month. Core also refuses an end_epoch in the past and deletes proposals 10 min after end_epoch, which the free-form date pickers gave users no help with. The window math is now centralised in one place and proven correct by unit tests: the window is always exactly N * SUPERBLOCK_CYCLE_SEC wide (so getProposalDurationMonths rounds to exactly N months), the first N superblocks fall inside the window by construction, and superblock N+1 is excluded with a ~15-day safety margin — vastly larger than Core's 2-hour GOVERNANCE_FUDGE_WINDOW, so block-time drift cannot push an extra payment inside the window. What changed ------------ - New `lib/governanceWindow.js` with `computeProposalWindow` + `nextSuperblockEpochSecFromStats`. Pure, deterministic; no network or DOM dependencies. - New `lib/governanceWindow.test.js` (35 tests) covering N ∈ {1..5, 12, 60}, stale-anchor fallback, past-start-epoch acceptance per Core, end > start + now invariants, SB_N in / SB_{N+1} out. - `pages/NewProposal.js`: * Fetches /mnStats on mount to get `superblock_next_epoch_sec`. * New `WindowPreview` component renders the derived window on both the Payment and Review steps. Loading / error states gate the "Prepare proposal" button — we refuse to submit an unanchored window. * `onPrepare` re-fetches stats for freshness, computes the window, and passes it to `prepareBodyFromForm({ window })`. * Removed the two