diff --git a/DESIGN.md b/DESIGN.md index 58c54de..8967986 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -96,3 +96,7 @@ Docs, default tab: five more panels in the same grid between "Risks" and "Find B Vault title, 660px and below: the picture's left edge is `max(0, min((panel - picture) / 2, panel + 14px - 4px - picture x 0.635 - 138px))`: the middle of the panel when her bubble fits (375px and wider), otherwise as far right as leaves the bubble 4px inside the yellow frame. The bubble is 138px wide (three lines at 16px; it was 170px, and 146px under 360px) and keeps its offset from the picture, so its tail still points at her mouth. Her shelf line (the 3px yellow line under her feet) runs from the left frame to the right frame. The heading is centred. The panel's height, the picture's height and every vertical distance are v14's. Above 660px nothing changes. Deposit and Redeem titles, 660px and below, while the heading shows the focus marker (`:focus-visible`: a 3px ink outline inside a 6px yellow band; iPhone browsers show it after a tab is tapped): her shelf line sits 6px under her feet instead of 3px and ends 6px before the heading's left edge, which is the marker's outer edge, so the line and the marker's bottom band are one 3px yellow line into the corner. The rule uses `:has()` and applies only when forced colours are off; without the marker the line is v14's. + +## v16 presentation details + +Tokenomics tab, release chart: a single royal-blue line with its light area and its four yearly points, captioned "Total ITEM available"; under the chart one legend entry with the blue line mark, "260 million at launch → 1 billion by year four". No dashed line, no red line. The release table's columns are "End of", "Liquidity rewards released", "Development unlocked" and "Total ITEM available". Sizes, colours, axes and the phone layout are v15's. diff --git a/README.md b/README.md index cdf5fe1..9b9758f 100644 --- a/README.md +++ b/README.md @@ -238,3 +238,18 @@ What the checks allow in this update: - check-visibility compares v14 at 375×812 with no exception left: every first-screen element keeps its words and its vertical place on all six pages; the Docs panels, the open Deposit stock list and the status notices equal v14; the Deposit and Redeem titles equal v14 across the panel; the Vault title is asserted across the panel (picture in the middle, bubble beside her head inside the frame, shelf line across the panel, heading centred) with v14 proving the look it replaces. - check-art keeps every earlier assertion (whole bubble words, the bubble off her face, hands and the crate and off every opaque picture pixel, the strip clear of the text, no taller Vault title) at every width from 320 to 2560px in Chromium and WebKit, and adds: the Vault phone layout at every phone width; the live v14 title beside it (nothing in a title moves above 660px; on phones the Deposit and Redeem titles equal v14 and the Vault title differs only sideways as decided), with the v15 check rejecting the v14 look; and the focus marker at 660, 430, 393, 375, 360, 359 and 320px (the line on the marker's bottom band up to its outer edge, the v14 line put back is rejected, the line returns after blur, no shelf line at 661px). - The fork walk is unchanged in what it checks: all 91 v14 checks under the same names in the same order, none added, the same 70 page sends, the same 24 screenshot names at JPEG quality 18. check-motion, check-fixes, inspect-export, check-guards, check-preservation, check-interface, check-tokenomics, check-tokenomics-live and check-bask-guide are unchanged. + +## Site update v16 + +The current patch is applied to v15, commit 5110ca96c434441cdffaec727413c54ede26dafe. Two things, both decided by the owner on 2026-10-13: +- Tokenomics tab, the release chart: one blue line, the total ITEM available, with the caption "Total ITEM available" and one legend line, "260 million at launch → 1 billion by year four". The dashed liquidity-rewards line and the red development line and their legend entries are gone; the table keeps the full breakdown under the headings "Liquidity rewards released", "Development unlocked" and "Total ITEM available". The introduction and the note describe the schedule with liquidity providers staked in both reward pools from launch through the four years; the "at most", "earliest dates" and paused-gauge wording is gone, and one allocation term reads "Released over four years, with more rewards at the start". Every number, formula, allocation and vesting date is unchanged; the total line's path, the axes, the chart coordinates and the styles are unchanged. Two sources change: Tokenomics.tsx and tokenomicsData.ts. +- A check script: check-motion.mjs counted every animationiteration event on the page while it tested the slogan strip's loop, so it failed whenever the Deposit page showed the CLOSED sign (the sign runs two endless CSS animations of its own). It now counts only events that come from the strip. The strip's loop is asserted as before. +- No transaction input, send preparation, chain read, wallet request, storage key or application network call changes. Every other web/src file is byte-identical to v15, styles.css included. + +What the checks allow in this update: + +- check-calldata (HEAD = v15): the edited files are Tokenomics.tsx and tokenomicsData.ts, restored to v15 through documented pieces; their independent digests are the reviewed new ones; the calls that left (the two component lines' paths; four formatting calls of the old accessible description), the two added formatting calls of the legend, the two imports that left and the two that came, and the four changed data declarations are each written out; the sink scans and the null-address guard before the only optional read stay. Every other source is frozen whole; styles.css equals v15. The byte-identical send(), connect/disconnect texts, counter uses, hook return, raw protected blocks and the differential encoding (496 identical, 1,773 refused) all stay. +- check-bundle: source scope Tokenomics.tsx and tokenomicsData.ts; no new file; the limit stays 8,388,608 bytes. +- check-visibility compares v15 at 375×812 with no exception: every first-screen element keeps its words and vertical place; the three title panels equal v15 across the panel and the Vault title keeps its v15 placing. +- check-art keeps every assertion; its reference page is the live v15 site now, so every title must equal it at every compared width (the v15 Vault placing is still asserted on its own at every phone width, and the v14 placing and the v14 shelf line put back are still rejected). +- The fork walk compares with the v15 record: the same checks under the same names in order, the same 70 page sends, the same 24 screenshot names at JPEG quality 18. check-tokenomics, check-tokenomics-live and the walk's Tokenomics checks read their expected words from the data file and pass unchanged. diff --git a/web/scripts/check-art.mjs b/web/scripts/check-art.mjs index 5fba25a..bdda5d0 100644 --- a/web/scripts/check-art.mjs +++ b/web/scripts/check-art.mjs @@ -848,8 +848,8 @@ try { } // v12 SPEC 1: compare the Vault height at every width with the live // title. Its public data does not affect title sizing, so this second page - // loads only local files and never adds a public RPC read. v15: the same - // page (the live v14 site) is the reference for the placing of every title. + // loads only local files and never adds a public RPC read. The same page + // (the live site) is the reference for the placing of every title. baselinePage=await browser.newPage({viewport:{width:1440,height:1000},reducedMotion:'reduce'}); await baselinePage.route('**/*',route=>route.request().url().startsWith(base+'/')?route.continue():route.abort()); await baselinePage.goto(base+'/baseline/#vault'); @@ -878,31 +878,28 @@ try { const g=await page.evaluate(titleGeometry,{R:REGIONS,name,pixels:width%40===0}); assertGeometry(g,`${engine} ${name} ${width}`,width%40===0); assert.equal(g.ring.shown,false,`${engine} ${name} ${width}: a focus marker in the sweep`); - // v15: the live v14 title at the same width (Vault: every width; Deposit and Redeem: every phone width and - // every 40 px above). Above phones nothing moved. On phones the Deposit and Redeem titles are where they - // were while the heading has no focus marker; the Vault title moved only as decided: the picture and its - // bubble sideways together towards the middle, the bubble narrower, the shelf line longer, the heading - // centred; no height or vertical place changed. + // The live title at the same width (Vault: every width; Deposit and Redeem: every phone width and every + // 40 px above): nothing in a title is placed differently from the live site while the heading has no + // focus marker. (v15 moved the Vault title on phones; the live site carries that placing now, and + // assertGeometry above still asserts it at every phone width.) if(name==='vault'||width<=660||width%40===0) { await baselinePage.setViewportSize({width,height:1000}); - const was=await baselinePage.evaluate(titleGeometry,{R:REGIONS,name,pixels:false}),label=`${engine} ${name} ${width}`; + const label=`${engine} ${name} ${width}`; + let was=await baselinePage.evaluate(titleGeometry,{R:REGIONS,name,pixels:false}); + // The reference page also moves the focus to its heading on a change of page, and as a background page it + // may do so late: the comparison is of the plain state, so take that focus away first. + if(was.ring.shown){await baselinePage.evaluate(()=>document.activeElement?.blur?.());was=await baselinePage.evaluate(titleGeometry,{R:REGIONS,name,pixels:false});} + assert.equal(was.ring.shown,false,label+': a focus marker on the reference page'); if(name==='vault') assert.ok(g.titleHeight<=was.titleHeight+0.5,engine+' vault '+width+': title grew from '+was.titleHeight+' to '+g.titleHeight); - if(name!=='vault'||width>660) assert.deepEqual(placed(g),placed(was),label+': v15 moved something in the title'); - else { - const a=placed(g),b=placed(was); - assert.deepEqual([a.picture.y,a.picture.w,a.picture.h,a.bubble.y,a.bubble.h,a.bubbleLines,a.h1,a.titleHeight,a.shelf.top,a.shelf.bottom,a.shelf.left,a.shelf.colour],[b.picture.y,b.picture.w,b.picture.h,b.bubble.y,b.bubble.h,b.bubbleLines,b.h1,b.titleHeight,b.shelf.top,b.shelf.bottom,b.shelf.left,b.shelf.colour],label+': v15 changed a height or a vertical place in the Vault title'); - assert.ok(Math.abs(g.bubbleFromPicture-was.bubbleFromPicture)<0.05,label+': the bubble left its place beside her head (layout places snap to 1/64 px) '+(g.bubbleFromPicture-was.bubbleFromPicture)); - assert.ok(was.pictureOff>g.pictureOff+10&&was.shelf.rightassertVaultPhone(was,label+' live v14'),{name:'AssertionError'},label+': the v15 check accepts the live v14 look'); - Math.abs(g.pictureOff)<0.5?centred++:nearest++; - } + assert.deepEqual(placed(g),placed(was),label+': something in the title is placed differently from the live title'); + if(name==='vault'&&width<=660) Math.abs(g.pictureOff)<0.5?centred++:nearest++; compared++; } lines[g.bubbleLines]=(lines[g.bubbleLines]||0)+1; } checks.push(name==='vault' - ?`${engine} vault v15: on phones (320-660 px) the picture stands in the middle of the panel at ${centred} widths and, on the ${nearest} narrowest, as near it as her three-line bubble allows 4 px inside the frame; shelf line across the panel, heading centred; the bubble keeps its place beside her head; no height or vertical place differs from the live v14 title, which the same check rejects; above 660 px the title equals the live v14 title at every width` - :`${engine} ${name} v15: the title equals the live v14 title (picture, bubble, heading, shelf line) at every phone width and every 40 px above (${compared} widths) while the heading has no focus marker`); + ?`${engine} vault title: on phones (320-660 px) the picture stands in the middle of the panel at ${centred} widths and, on the ${nearest} narrowest, as near it as her three-line bubble allows 4 px inside the frame; shelf line across the panel, heading centred (v15); the title equals the live title at every width from 320 to 2560 px` + :`${engine} ${name} title: equals the live title (picture, bubble, heading, shelf line) at every phone width and every 40 px above (${compared} widths) while the heading has no focus marker`); checks.push(`${engine} ${name}: every width 320-2560 px keeps whole bubble words (bubble lines ${JSON.stringify(lines)}), the strip clear of the title text, the title text centred beside her (Vault heading 17 px under her on phones), the bubble in its frame and the wallet hint gap; picture pixels checked every 40 px`); } // v15: the heading's focus marker. A key press first, so that the focus the page itself moves to the heading @@ -932,6 +929,13 @@ try { // Vault: the line is 17 px above the heading; the marker's band stays clear of it and nothing moves. assert.deepEqual(placed(marked),placed(plain),label+': the Vault title moved with the marker'); assert.ok(marked.h1.top-6-marked.shelf.bottom>=7.5,label+': the marker touches the shelf line'); + // The v14 placing put back (picture and bubble centred as one group, the short line, the heading at the + // left): the same check rejects it; with it removed the check passes again. + const legacy=await page.addStyleTag({content:'.page-title.character-vault .title-character{--gx:max(0px, calc((100% - var(--zone)) / 2))!important}.page-title.character-vault .title-character:after{right:auto!important;width:calc(var(--pad) + var(--gx) + var(--w) + 12px)!important}.page-title.character-vault>h1{text-align:left!important}'}); + const old=await page.evaluate(titleGeometry,{R:REGIONS,name,pixels:false}); + assert.throws(()=>assertVaultPhone(old,label+' with the v14 placing'),{name:'AssertionError'},label+': the check accepts the v14 placing'); + await legacy.evaluate(e=>e.remove()); + assertVaultPhone(await page.evaluate(titleGeometry,{R:REGIONS,name,pixels:false}),label+' restored'); } else { assertShelfMeetsMarker(marked,label); assert.deepEqual({...placed(marked),shelf:null},{...placed(plain),shelf:null},label+': something besides the shelf line moved with the marker'); @@ -953,7 +957,7 @@ try { assert.equal(after.ring.shown,false,label+': marker still shown after blur'); assert.deepEqual(placed(after),placed(plain),label+': the title is not back in place after blur'); } - checks.push(`${engine} v15 focus marker (keyboard focus moved to the heading by a change of page) at 660, 430, 393, 375, 360, 359 and 320 px: on Deposit and Redeem her shelf line runs from the frame to the marker's outer left edge on the marker's bottom band, same height and colour, and nothing else moves; the same check rejects the live v14 line put back; without the marker and after blur the line is the live v14 line; on Vault the marker stays clear of the line; at 661 px there is no shelf line`); + checks.push(`${engine} v15 focus marker (keyboard focus moved to the heading by a change of page) at 660, 430, 393, 375, 360, 359 and 320 px: on Deposit and Redeem her shelf line runs from the frame to the marker's outer left edge on the marker's bottom band, same height and colour, and nothing else moves; the same check rejects the v14 line put back; without the marker and after blur the line is the plain line; on Vault the marker stays clear of the line and the v14 placing put back is rejected; at 661 px there is no shelf line`); await baselinePage.close();baselinePage=undefined; await browser.close();browser=undefined; } diff --git a/web/scripts/check-bundle.mjs b/web/scripts/check-bundle.mjs index 0a38415..02ccdff 100644 --- a/web/scripts/check-bundle.mjs +++ b/web/scripts/check-bundle.mjs @@ -6,9 +6,9 @@ import {createHash} from 'node:crypto'; const root=process.env.BASKET_SOURCE_ROOT||path.resolve('..'); const git=args=>execFileSync('git',args,{cwd:root,encoding:'utf8'}); const changed=git(['diff','--name-only','HEAD']).trim().split('\n').filter(Boolean); -// v15 (owner decisions of 2026-10-13: the Vault title picture centred on phones; the shelf line meeting the heading's focus marker on Deposit and Redeem): only the style sheet may change. -// check-calldata independently freezes every script source and pins the added style rules. -const v15Source=['styles.css'].map(f=>'web/src/'+f); +// v16 (owner-approved Tokenomics chart correction of 2026-10-13: one total line, reviewed words): only these two sources may change. +// check-calldata independently pins each reviewed source and freezes every other. +const v16Source=['Tokenomics.tsx','tokenomicsData.ts'].map(f=>'web/src/'+f); const SHELL_SHA256='82f66899a0c19f61299254c0005ee5117af0a912b97e8e57d249288e9dbc962d'; const SHARE_SHA256='dc14dd6f4068ec9325426583104b1c6ae8748fbb17d8326863c9612280eacd85'; // The new favicon: red panel, yellow frame, the header's basket in yellow. @@ -23,10 +23,10 @@ for(const p of changed){ // v12 section 5: the exact page shell is pinned below; build configuration and validation fixtures stay unchanged. assert.ok(!['web/vite.config.ts','web/tsconfig.json'].includes(p)&&(!p.startsWith('web/validation/')||p==='web/validation/README.md'),'Page shell, build configuration or validation fixture changed: '+p); assert.ok(!/(^|\/)(lib|node_modules|\.github|\.git)(\/|$)|(^|\/)\.env(?:\.|$)|(^|\/)(foundry\.toml|foundry\.lock|remappings\.txt|\.gitmodules|package\.json|[^/]*lock[^/]*)$/.test(p),'Protected path changed: '+p); - if(p.startsWith('web/src/'))assert.ok(v15Source.includes(p),'Source outside the v15 scope changed: '+p); + if(p.startsWith('web/src/'))assert.ok(v16Source.includes(p),'Source outside the v16 scope changed: '+p); assert.ok(!p.startsWith('src/'),'Root contract source changed'); } -// v15: no new source and no new public asset. +// v16: no new source and no new public asset. const untracked=git(['ls-files','--others','--exclude-standard','--','web/src','web/public']).trim().split('\n').filter(Boolean); assert.deepEqual(untracked,[],'New files in web/src or web/public'); assert.equal(sha256('web/index.html'),SHELL_SHA256,'exact v12 source shell'); diff --git a/web/scripts/check-calldata.mjs b/web/scripts/check-calldata.mjs index 9ba9b13..9802fe1 100644 --- a/web/scripts/check-calldata.mjs +++ b/web/scripts/check-calldata.mjs @@ -11,10 +11,11 @@ const git=args=>execFileSync('git',args,{cwd:root,encoding:'utf8'}); const baseline=p=>git(['show','HEAD:'+p]); const read=p=>fs.readFileSync(path.join(root,p),'utf8'); const sha256=s=>createHash('sha256').update(s).digest('hex'); -// v15 on HEAD=v14: the owner's two phone decisions of 2026-10-13, both style rules only. styles.css gains rules after -// HEAD's text (the Vault title picture centred on phones; on Deposit and Redeem the shelf line meets the heading's -// focus marker). No script changes: every web/src file except styles.css is byte-identical to HEAD, the two v14 files -// (Docs.tsx, Flows.tsx) and the four Docs module files included. Transactions and all chain reads remain HEAD's. +// v16 on HEAD=v15: two things. (1) The owner-approved Tokenomics chart correction ("we should only show the supply as if +// the protocol has depositors"; "yes make it that"): Tokenomics.tsx and tokenomicsData.ts lose the chart's two component +// lines and their legend entries and change reviewed words and labels; the total line, every number, formula and the +// null-address guard are HEAD's. (2) A check script: check-motion.mjs counts the strip's own animation events only. +// Every other web/src file is byte-identical to HEAD, styles.css included. Transactions and chain reads remain HEAD's. const protectedFiles=[ "BaskGuide.tsx", "Docs.tsx", @@ -22,7 +23,6 @@ const protectedFiles=[ "Losses.tsx", "Owner.tsx", "Scenery.tsx", - "Tokenomics.tsx", "Vault.tsx", "components.tsx", "main.tsx", @@ -34,21 +34,21 @@ const protectedFiles=[ "model.ts", "newYork.ts", "poolMath.ts", - "tokenomicsData.ts", "vault.abi.json" ]; for(const f of protectedFiles) assert.equal(read('web/src/'+f),baseline('web/src/'+f),f); -// v15 adds no source file. The Docs module of v14 (four display-only sources) is part of HEAD now: frozen above, and -// still pinned by its independently reviewed digests and scanned for wallet, storage, network and send sinks; the -// ITEM statistics read stays behind the null-address guard. styles.css starts with HEAD's whole text (its first 63880 -// bytes are still the exact approved module CSS); the v15 rules are appended after it and pinned below. +// v16 adds no source file. The Docs module of v14 (four display-only sources) is part of HEAD: BaskGuide.tsx and +// baskGuideData.ts are frozen above; Tokenomics.tsx and tokenomicsData.ts are the two v16 files (documented pieces +// below). All four stay pinned by independently reviewed digests (the two v16 files by their new ones) and scanned +// for wallet, storage, network and send sinks; the ITEM statistics read stays behind the null-address guard. +// styles.css equals HEAD's text (its first 63880 bytes are still the exact approved module CSS). const NEW_SOURCES=[]; const MODULE_SOURCES=['Tokenomics.tsx','tokenomicsData.ts','BaskGuide.tsx','baskGuideData.ts']; const allSources=fs.readdirSync(path.join(root,'web/src'),{recursive:true}).filter(p=>fs.statSync(path.join(root,'web/src',p)).isFile()).map(p=>p.replaceAll('\\','/')).sort(); assert.deepEqual(allSources,[...git(['ls-tree','-r','--name-only','HEAD','web/src']).trim().split('\n').map(p=>p.slice('web/src/'.length)),...NEW_SOURCES].sort(),'no new source'); const modulePins={ - "Tokenomics.tsx": "9090a62ee2c36373beb7712fea6489ba6aa5f4852ba110be71beb1c11c27f1f6", - "tokenomicsData.ts": "18c92542419fcde454a8640705ebe2b1579d3e01eaa88cbf5edfb1e7053eccfe", + "Tokenomics.tsx": "6d57c2f9d8629f7207368b6d00618da9351e72aba0de52fcff225490be47c85a", + "tokenomicsData.ts": "8e2cfc45c8055f2dbfdc7a7610e57856809845d52dd103712dd8398f3b11a99a", "BaskGuide.tsx": "9427416f3e67521e3805bf0cc480ef4960d4d5058677327782a7175b61e8d01c", "baskGuideData.ts": "009ed45d5af35bbb34bc62c89bcc90b46253894d2aca60f793ec3e4ac465b169", }; @@ -61,24 +61,37 @@ for(const f of MODULE_SOURCES){ assert.match(read('web/src/Tokenomics.tsx'),/if \(!hasAddresses\(addresses\)\) return null;[\s\S]*const results = await reader/,'null guard before the sole optional existing many reader'); assert.ok(read('web/src/styles.css').startsWith(baseline('web/src/styles.css')),'CSS additions only'); assert.equal(sha256(fs.readFileSync(path.join(root,'web/src/styles.css')).subarray(0,63880)),'556616a829d32cd47a07e81280a030b9adcd49ffa1a665776ca894c63e593ad8','entire approved module CSS is an exact prefix; v14 Deposit rules follow it'); -// v15: the only source difference is the text after HEAD's styles.css. It is pinned whole by a reviewed digest, and -// it can load nothing, name no file and run nothing: no url(), @import, @font-face, image-set(), src, expression or -// content, no backslash escape (which could spell one of those), and only the two phone media queries. -const cssTail=fs.readFileSync(path.join(root,'web/src/styles.css')).subarray(Buffer.byteLength(baseline('web/src/styles.css'))).toString('utf8'),cssRules=cssTail.replace(/\/\*[\s\S]*?\*\//g,''); -assert.equal(sha256(cssTail),'75e11c77094ccfe8889f6a224e973cf9091a289e99b30cf34e81ecc7d9cadd1a','v15: the reviewed style rules after the text of HEAD'); -assert.ok(!/url\s*\(|@import|@font-face|@namespace|image-set|\bsrc\b|expression|javascript:|behavior|content\s*:|\\|<|http/i.test(cssRules),'v15 style rules: nothing that loads, names a file or runs'); -assert.deepEqual([...cssRules.matchAll(/@[a-z-]+[^{]*/g)].map(m=>m[0].trim()),['@media (max-width: 660px)','@media (max-width: 660px) and (forced-colors: none)'],'v15 style rules: phones only'); -assert.deepEqual([...new Set([...cssRules.matchAll(/^\s*(--[a-z-]+|[a-z-]+)\s*:/gm)].map(m=>m[1]))].sort(),['--bw','--gx','bottom','right','text-align','width'],'v15 style rules: only these properties'); -// v15 edits no script file, so this list is empty and every source is frozen above. (For an edited file it holds the -// documented pieces [now, HEAD]: byte-identical to HEAD once they are put back, each exactly once, so +// v16: no style rule after HEAD's text (the v15 rules are HEAD's own now). +const cssTail=fs.readFileSync(path.join(root,'web/src/styles.css')).subarray(Buffer.byteLength(baseline('web/src/styles.css'))).toString('utf8'); +assert.equal(cssTail,'','v16: no style rule after the text of HEAD'); +// The v16 files: byte-identical to HEAD once their documented pieces [v16, HEAD] are put back (each exactly once), so // every word, call and attribute in them is pinned. The list between the two marker lines was generated from the // difference to HEAD; the pins further down do not depend on it: they compare with HEAD itself or with texts written -// out in this file, so a piece cannot carry a change to a transaction, a wallet request or a chain read past them.) -// >>> v15 pieces (generated from the difference to HEAD, not edited by hand) +// out in this file, so a piece cannot carry a change to a transaction, a wallet request or a chain read past them. +// >>> v16 pieces (generated from the difference to HEAD, not edited by hand) const editedFiles={ + "Tokenomics.tsx":[ + ["import { many } from \"./chain\";\nimport { ADDRESSES, ALLOCATIONS, ALLOCATION_HEADERS, ALLOCATION_ROWS, RELEASE_HEADERS, RELEASE_ROWS, PARAGRAPHS, T, OUTSIDE_AT_LAUNCH, TOTAL_SUPPLY, outsideContracts, formatItem, SUPPLY_LABEL, RELEASE_LABEL, type Addresses } from \"./tokenomicsData\";\n\n", + "import { many } from \"./chain\";\nimport { ADDRESSES, ALLOCATIONS, ALLOCATION_HEADERS, ALLOCATION_ROWS, RELEASE_HEADERS, RELEASE_ROWS, PARAGRAPHS, T, gaugeReleased, developmentVested, outsideContracts, formatItem, SUPPLY_LABEL, RELEASE_LABEL, type Addresses } from \"./tokenomicsData\";\n\n"], + [" const line = (fn: (seconds: number) => number) => samples.map((m, i) => `${i ? \"L\" : \"M\"}${x(m).toFixed(3)},${y(fn(m / 48 * T)).toFixed(3)}`).join(\" \");\n return {[0, 250, 500, 750, 1000].map(n => {formatItem(n)})}{[0, 12, 24, 36, 48].map(m => {m})}{[12, 24, 36, 48].map(m => )};\n}\nfunction ReleaseChart() {\n return
Total ITEM available
Million ITEM
Months after launch
{formatItem(OUTSIDE_AT_LAUNCH / 1e6)} million at launch → {formatItem(TOTAL_SUPPLY / 1e9)} billion by year four
;\n}\n", + " const line = (fn: (seconds: number) => number) => samples.map((m, i) => `${i ? \"L\" : \"M\"}${x(m).toFixed(3)},${y(fn(m / 48 * T)).toFixed(3)}`).join(\" \");\n return {[0, 250, 500, 750, 1000].map(n => {formatItem(n)})}{[0, 12, 24, 36, 48].map(m => {m})}{[12, 24, 36, 48].map(m => )};\n}\nfunction ReleaseChart() {\n return
Released over four years
Million ITEM
Months after launch
Outside the contracts, at mostLiquidity rewards releasedDevelopment vested
;\n}\n"] + ], + "tokenomicsData.ts":[ + [" \"who\": \"BASK/USDG liquidity providers\",\n \"terms\": \"Released over four years, with more rewards at the start\"\n },\n", + " \"who\": \"BASK/USDG liquidity providers\",\n \"terms\": \"Released by the gauge over four years, front loaded, only while someone is staked\"\n },\n"], + [" \"How the supply is divided:\",\n \"What is available when. At launch, 260,000,000 ITEM (26% of the supply) is available through the launch pool, the swarm's share, operations and the unlocked development amount. The remaining 740,000,000 ITEM is released over four years: 700,000,000 as liquidity rewards and 40,000,000 through development vesting. The chart and table show this schedule with liquidity providers staked in both reward pools from launch throughout the four years.\",\n \"The line shows the total ITEM available: the 260,000,000 available at launch plus the liquidity rewards and development tokens released by each date. The table shows the breakdown.\",\n \"Where value flows. ITEM pays the people who make BASK and ITEM tradable: liquidity providers earn ITEM from the gauges. ITEM holders who stake earn the vault's fees, 0.5% of every deposit and 0.5% of every redemption, paid in BASK. Trading fees in the two Uniswap v2 pools stay with the liquidity providers of those pools. Trades in the launch pool carry a 1.25% fee set by IMD: 1% to the protocol's operations wallet and 0.25% to IMD.\",\n", + " \"How the supply is divided:\",\n \"What is available when. At launch, 26% of the supply exists outside the reward and vesting contracts: the launch pool, the swarm's share, operations and the unlocked development amount. The other 74% is held by contracts and comes out only on schedule: 70% through the two gauges, to the people staked in them, and 4% through vesting. If both gauges run without a pause, the most that can be outside those contracts is:\",\n \"A gauge's clock only runs while someone is staked, so these are the earliest dates, not fixed ones.\",\n \"Where value flows. ITEM pays the people who make BASK and ITEM tradable: liquidity providers earn ITEM from the gauges. ITEM holders who stake earn the vault's fees, 0.5% of every deposit and 0.5% of every redemption, paid in BASK. Trading fees in the two Uniswap v2 pools stay with the liquidity providers of those pools. Trades in the launch pool carry a 1.25% fee set by IMD: 1% to the protocol's operations wallet and 0.25% to IMD.\",\n"], + ["export const ALLOCATION_HEADERS = [\"Share\",\"ITEM\",\"Who\",\"Terms\"] as const;\nexport const RELEASE_HEADERS = [\"End of\",\"Liquidity rewards released\",\"Development unlocked\",\"Total ITEM available\"] as const;\nexport const ADDRESSES: Record<\"ITEM\" | \"ITEM_STAKING\" | \"BASK_USDG_GAUGE\" | \"ITEM_ETH_GAUGE\" | \"DEV_VESTING\", Address | null> = {\n", + "export const ALLOCATION_HEADERS = [\"Share\",\"ITEM\",\"Who\",\"Terms\"] as const;\nexport const RELEASE_HEADERS = [\"End of\",\"Released by the gauges\",\"Vested\",\"Outside the contracts, at most\"] as const;\nexport const ADDRESSES: Record<\"ITEM\" | \"ITEM_STAKING\" | \"BASK_USDG_GAUGE\" | \"ITEM_ETH_GAUGE\" | \"DEV_VESTING\", Address | null> = {\n"], + ["export const SUPPLY_LABEL = ALLOCATIONS.map(r => r.who + \" \" + r.share + \"%\").join(\", \");\nexport const RELEASE_LABEL = \"Total ITEM available: \" + formatItem(OUTSIDE_AT_LAUNCH) + \" at launch, \" + RELEASES.map(r => formatItem(r.outside) + \" at year \" + r.year).join(\", \") + \". Includes the launch allocation, liquidity rewards and unlocked development tokens, with liquidity providers staked in both reward pools throughout the four years.\";\n", + "export const SUPPLY_LABEL = ALLOCATIONS.map(r => r.who + \" \" + r.share + \"%\").join(\", \");\nexport const RELEASE_LABEL = \"Outside the contracts, at most: \" + formatItem(OUTSIDE_AT_LAUNCH) + \" at launch, \" + RELEASES.map(r => formatItem(r.outside) + \" at year \" + r.year).join(\", \") + \". Liquidity rewards released: \" + formatItem(GAUGE_ALLOCATION) + \" over four years. Development vested: 0 before month 12, \" + formatItem(developmentVested(CLIFF)) + \" at month 12, \" + formatItem(VESTED_ALLOCATION) + \" at month 48.\";\n"] + ] }; -// <<< v15 pieces -assert.deepEqual(Object.keys(editedFiles),[],'v15 edits no script file'); +// <<< v16 pieces +assert.deepEqual(Object.keys(editedFiles),[ + "Tokenomics.tsx", + "tokenomicsData.ts" +],'the v16 files'); const putBack=(text,pieces,what)=>{for(const [n,o] of pieces){assert.equal(text.split(n).length,2,what+': expected once: '+n);text=text.split(n).join(o);}return text;}; for(const [f,edits] of Object.entries(editedFiles)) assert.equal(putBack(read('web/src/'+f),edits,f),baseline('web/src/'+f),f+' changed beyond the documented pieces'); // Calls that build, encode, simulate or send a transaction (or parse its arguments) or read the chain. @@ -132,16 +145,17 @@ function difference(a,b){ } // Guarded calls: the exact, documented differences from HEAD, each added call with the place it must sit in // (everything equals HEAD, in order and place). The existing receipt read and display-only amount parser are -// already part of HEAD. v15 adds no call at all: no script file changes. +// already part of HEAD. v16 adds no guarded call, wallet request or chain read. const expected={}; // Action attributes: the exact differences from HEAD, in page order, each [element, attribute, why]. Everything else // equals HEAD in order, text for text. None of them builds or starts a transaction (no onSubmit, getSpec, build or // claim) and none contains a guarded call (both asserted below). The list between the marker lines was generated // from the difference to HEAD, as the pieces were; each why was written by hand, and an entry without one fails. -// >>> v15 attributes (generated from the difference to HEAD; each why written by hand) +// >>> v16 attributes (generated from the difference to HEAD; each why written by hand) const attributeChanges={ + }; -// <<< v15 attributes +// <<< v16 attributes // Every call expression: HEAD's calls, in order and place, are all still there, except the removed ones listed here; // a call that only gained the documented piece inside it (put back before comparing) counts as kept. Every added // call, including display helpers and effects, is independently pinned below, not only the guarded calls. @@ -153,8 +167,29 @@ const attributeChanges={ // read is added; the existing receipt read and answer handling are independently byte-pinned. // The guarded startup eth_accounts answer is part of HEAD and must remain exact. const restoreAccountReply=(x)=>x.text; -// Reviewed differences in calls and watched-name uses: none in v15. Independent of the generated pieces. -const callChanges={}; +// Reviewed v16 differences in calls and watched-name uses. Independent of the generated pieces. +const callChanges={ + "Tokenomics.tsx": { + "why": "v16 (owner-approved chart correction, 2026-10-13): the release chart shows one line, the total ITEM available. Its two component lines (liquidity rewards released, development vested) and their legend entries are removed, the caption and the one legend line are re-worded, and the legend's two figures are formatted from the data file's own launch and supply constants. The total line and its area, the axes, the points, the tables and the null-address guard before the only optional read are HEAD's. No read, send, storage or network use is added.", + "pieces": [], + "removed": [ + "line(gaugeReleased)", + "line(developmentVested)" + ], + "uses": [] + }, + "tokenomicsData.ts": { + "why": "v16 (owner-approved chart correction, 2026-10-13): reviewed words only. One allocation term, the two release paragraphs, the release table's three column headings and the chart's accessible description are re-worded to describe the total available with liquidity providers staked from launch; the description no longer formats the two component totals. Every number, formula, schedule, address constant and helper is HEAD's.", + "pieces": [], + "removed": [ + "formatItem(GAUGE_ALLOCATION)", + "formatItem(developmentVested(CLIFF))", + "developmentVested(CLIFF)", + "formatItem(VESTED_ALLOCATION)" + ], + "uses": [] + } +}; const WATCHED=[...names].filter(x=>!x.includes('.')).concat('client'); // The state setters and refs a file declares: const [x, setX] = useState(...) gives setX, const r = useRef(...) gives r. function stateNames(text,file){ @@ -204,8 +239,8 @@ function localDeclarations(file,text,fn,list){ } const sources=git(['ls-tree','-r','--name-only','HEAD','web/src']).trim().split('\n').filter(p=>/\.tsx?$/.test(p)).map(p=>p.slice(8)); assert.deepEqual(git(['ls-files','--cached','--others','--exclude-standard','web/src']).trim().split('\n').filter(p=>/\.tsx?$/.test(p)).map(p=>p.slice(8)).sort(),[...sources,...NEW_SOURCES].sort(),'No source file added or removed'); -// Every source is frozen (byte-identical to HEAD): v15 has no edited script file. -assert.deepEqual([...sources].sort(),[...protectedFiles,...Object.keys(editedFiles)].filter(f=>/\.tsx?$/.test(f)).sort(),'every web/src source is frozen'); +// Every source is either frozen (byte-identical to HEAD) or a v16 file with its documented pieces (above). +assert.deepEqual([...sources].sort(),[...protectedFiles,...Object.keys(editedFiles)].filter(f=>/\.tsx?$/.test(f)).sort(),'every web/src source is frozen or a documented v16 file'); const scans={},facts={attributes:{},guarded:{},calls:{}}; for(const f of sources){ const old=calls(baseline('web/src/'+f),f),now=calls(read('web/src/'+f),f),c=callChanges[f]??{pieces:[],removed:[],uses:[]},used=c.pieces.map(()=>0); @@ -228,13 +263,23 @@ if(process.env.BASKET_GATE_FACTS)fs.writeFileSync(process.env.BASKET_GATE_FACTS, // ordered callee names make the list readable without repeating large nested JSX bodies. No regeneration block // includes these pins. Changing a callback, receiver, argument, location, order or count changes the digest. // >>> reviewed calls (manual; never refreshed by put-pieces) -const addedCallPins={}; +const addedCallPins={ + "Tokenomics.tsx": { + "sha256": "7227ef4f006ce23b4c3ab906792bbf5a6a714da0bec09c988611e350d7ebd540", + "callees": "formatItem formatItem" + }, + "tokenomicsData.ts": { + "sha256": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "callees": "" + } +}; // <<< reviewed calls -// The exact reviewed source of the two v14 files, kept as a second pin beside the frozen-file comparison with HEAD -// (the four module files are pinned above). +// The exact reviewed source of the two v14 files (frozen now) and of the two v16 files, beyond either generated block. const reviewedSourcePins={ "Docs.tsx": "6c9cc7e308109e50b7b9a6e5c861fc4d9feec927e7d568f4c238e9a807930e77", - "Flows.tsx": "b9452b067fabd93c4ce9718d19e4c70906263068bcb81589b2e151beb411c309" + "Flows.tsx": "b9452b067fabd93c4ce9718d19e4c70906263068bcb81589b2e151beb411c309", + "Tokenomics.tsx": "6d57c2f9d8629f7207368b6d00618da9351e72aba0de52fcff225490be47c85a", + "tokenomicsData.ts": "8e2cfc45c8055f2dbfdc7a7610e57856809845d52dd103712dd8398f3b11a99a" }; for(const f of Object.keys(editedFiles)){ const {now,all}=scans[f],list=all.added.map(i=>[now.all[i].text,now.all[i].place]); @@ -405,7 +450,7 @@ function loadsOf(text,file){ })(tree); return out; } -// v15 adds nothing a page loads or opens: every file equals HEAD here (the module files are scanned above too). +// v16 adds nothing a page loads or opens: every file equals HEAD here (the module files are scanned above too). const loadDisplayAdded={}; const loadUses={}; for(const f of sources){ @@ -475,7 +520,7 @@ function sendParts(text){ } const headSend=sendParts(baseline('web/src/wallet.tsx')),nowSend=sendParts(read('web/src/wallet.tsx')); assert.equal(nowSend.head,headSend.head,'wallet.tsx send(): byte-identical head through receipt wait'); -// v15 does not touch wallet.tsx: send() is byte-identical to HEAD, head and tail (no piece); the mined-hash +// v16 does not touch wallet.tsx: send() is byte-identical to HEAD, head and tail (no piece); the mined-hash // statements are pinned by sendTailWrites below as well. const SEND_TAIL_PIECES=[ ]; @@ -574,7 +619,7 @@ for(const name of ['short','approvals'])rawProtected('Deposit '+name,'Flows.tsx' // component or hook (parameters included) is still declared there once, by the same text: the same import, the same // useState or useRef, the same expression. So `vault`, `token`, `amount`, `depositArgs`, `VAULT`, `quote`, `inputs`, // `open`, `approvals`, `account` and every other name the pinned transaction code uses still mean what they meant at -// HEAD. The names an update adds at the top of a file are listed here (none in v15); a name it adds at the top of a component or hook +// HEAD. The names an update adds at the top of a file are listed here (two imports of the data file's constants in Tokenomics.tsx); a name it adds at the top of a component or hook // must be new there and must not hide a name declared at the top of the file. function scopes(text,file){ const tree=parse(text,file),print=printerOf(tree),module=Object.create(null),functions=Object.create(null); @@ -635,10 +680,72 @@ for(const f of Object.keys(editedFiles)){ assert.deepEqual(n,h,f+': a name used by the transaction code is declared around it differently from HEAD'); transactionCode[f]={pieces:n.count,namesDeclaredAroundThem:n.names}; } -const namesAdded={}; -// v15 changes no declaration at the top of a file. -const moduleChanges={}; -// v15 changes no declaration at the top of a component or hook; never generated by put-pieces. +const namesAdded={ + "Tokenomics.tsx": { + "OUTSIDE_AT_LAUNCH": "import OUTSIDE_AT_LAUNCH from \"./tokenomicsData\"", + "TOTAL_SUPPLY": "import TOTAL_SUPPLY from \"./tokenomicsData\"" + }, + "tokenomicsData.ts": {} +}; +// v16: the exact declarations that change at the top of the two files (two imports leave Tokenomics.tsx; four data +// declarations of tokenomicsData.ts carry the reviewed words); never generated by put-pieces. +const moduleChanges={ + "Tokenomics.tsx": [ + { + "name": "gaugeReleased", + "head": [ + "import gaugeReleased from \"./tokenomicsData\"" + ], + "now": null + }, + { + "name": "developmentVested", + "head": [ + "import developmentVested from \"./tokenomicsData\"" + ], + "now": null + } + ], + "tokenomicsData.ts": [ + { + "name": "ALLOCATIONS", + "head": [ + "ALLOCATIONS = [ { \"share\": 49, \"amount\": 490000000, \"who\": \"BASK/USDG liquidity providers\", \"terms\": \"Released by the gauge over four years, front loaded, only while someone is staked\" }, { \"share\": 21, \"amount\": 210000000, \"who\": \"ITEM/ETH liquidity providers\", \"terms\": \"Same schedule, same rules\" }, { \"share\": 10, \"amount\": 100000000, \"who\": \"ITEM/ETH launch pool\", \"terms\": \"Paired with ETH at launch; where ITEM first trades\" }, { \"share\": 10, \"amount\": 100000000, \"who\": \"The IMD swarm that built and launched the contracts\", \"terms\": \"Paid to IMD's members under IMD's own rules\" }, { \"share\": 5, \"amount\": 50000000, \"who\": \"Operations\", \"terms\": \"Seeding the two pools and the first stakes; held by the owner wallet\" }, { \"share\": 4, \"amount\": 40000000, \"who\": \"Development, vested\", \"terms\": \"Nothing for 12 months, 25% at month 12, the rest evenly to month 48; cannot be revoked or sped up\" }, { \"share\": 1, \"amount\": 10000000, \"who\": \"Development, unlocked\", \"terms\": \"Sent once at launch\" } ] as const" + ], + "now": [ + "ALLOCATIONS = [ { \"share\": 49, \"amount\": 490000000, \"who\": \"BASK/USDG liquidity providers\", \"terms\": \"Released over four years, with more rewards at the start\" }, { \"share\": 21, \"amount\": 210000000, \"who\": \"ITEM/ETH liquidity providers\", \"terms\": \"Same schedule, same rules\" }, { \"share\": 10, \"amount\": 100000000, \"who\": \"ITEM/ETH launch pool\", \"terms\": \"Paired with ETH at launch; where ITEM first trades\" }, { \"share\": 10, \"amount\": 100000000, \"who\": \"The IMD swarm that built and launched the contracts\", \"terms\": \"Paid to IMD's members under IMD's own rules\" }, { \"share\": 5, \"amount\": 50000000, \"who\": \"Operations\", \"terms\": \"Seeding the two pools and the first stakes; held by the owner wallet\" }, { \"share\": 4, \"amount\": 40000000, \"who\": \"Development, vested\", \"terms\": \"Nothing for 12 months, 25% at month 12, the rest evenly to month 48; cannot be revoked or sped up\" }, { \"share\": 1, \"amount\": 10000000, \"who\": \"Development, unlocked\", \"terms\": \"Sent once at launch\" } ] as const" + ] + }, + { + "name": "PARAGRAPHS", + "head": [ + "PARAGRAPHS = [ \"ITEM's supply is 1,000,000,000, created once at launch. Nothing can mint more. It is a plain token: anyone can hold, send or trade it.\", \"How the supply is divided:\", \"What is available when. At launch, 26% of the supply exists outside the reward and vesting contracts: the launch pool, the swarm's share, operations and the unlocked development amount. The other 74% is held by contracts and comes out only on schedule: 70% through the two gauges, to the people staked in them, and 4% through vesting. If both gauges run without a pause, the most that can be outside those contracts is:\", \"A gauge's clock only runs while someone is staked, so these are the earliest dates, not fixed ones.\", \"Where value flows. ITEM pays the people who make BASK and ITEM tradable: liquidity providers earn ITEM from the gauges. ITEM holders who stake earn the vault's fees, 0.5% of every deposit and 0.5% of every redemption, paid in BASK. Trading fees in the two Uniswap v2 pools stay with the liquidity providers of those pools. Trades in the launch pool carry a 1.25% fee set by IMD: 1% to the protocol's operations wallet and 0.25% to IMD.\", \"What cannot change. The split above, the four-year gauge schedule, the 7-day fee stream and the vesting dates are constants in contracts with no owner. Nobody holds a key that can alter them.\" ] as const" + ], + "now": [ + "PARAGRAPHS = [ \"ITEM's supply is 1,000,000,000, created once at launch. Nothing can mint more. It is a plain token: anyone can hold, send or trade it.\", \"How the supply is divided:\", \"What is available when. At launch, 260,000,000 ITEM (26% of the supply) is available through the launch pool, the swarm's share, operations and the unlocked development amount. The remaining 740,000,000 ITEM is released over four years: 700,000,000 as liquidity rewards and 40,000,000 through development vesting. The chart and table show this schedule with liquidity providers staked in both reward pools from launch throughout the four years.\", \"The line shows the total ITEM available: the 260,000,000 available at launch plus the liquidity rewards and development tokens released by each date. The table shows the breakdown.\", \"Where value flows. ITEM pays the people who make BASK and ITEM tradable: liquidity providers earn ITEM from the gauges. ITEM holders who stake earn the vault's fees, 0.5% of every deposit and 0.5% of every redemption, paid in BASK. Trading fees in the two Uniswap v2 pools stay with the liquidity providers of those pools. Trades in the launch pool carry a 1.25% fee set by IMD: 1% to the protocol's operations wallet and 0.25% to IMD.\", \"What cannot change. The split above, the four-year gauge schedule, the 7-day fee stream and the vesting dates are constants in contracts with no owner. Nobody holds a key that can alter them.\" ] as const" + ] + }, + { + "name": "RELEASE_HEADERS", + "head": [ + "RELEASE_HEADERS = [\"End of\", \"Released by the gauges\", \"Vested\", \"Outside the contracts, at most\"] as const" + ], + "now": [ + "RELEASE_HEADERS = [\"End of\", \"Liquidity rewards released\", \"Development unlocked\", \"Total ITEM available\"] as const" + ] + }, + { + "name": "RELEASE_LABEL", + "head": [ + "RELEASE_LABEL = \"Outside the contracts, at most: \" + formatItem(OUTSIDE_AT_LAUNCH) + \" at launch, \" + RELEASES.map(r => formatItem(r.outside) + \" at year \" + r.year).join(\", \") + \". Liquidity rewards released: \" + formatItem(GAUGE_ALLOCATION) + \" over four years. Development vested: 0 before month 12, \" + formatItem(developmentVested(CLIFF)) + \" at month 12, \" + formatItem(VESTED_ALLOCATION) + \" at month 48.\"" + ], + "now": [ + "RELEASE_LABEL = \"Total ITEM available: \" + formatItem(OUTSIDE_AT_LAUNCH) + \" at launch, \" + RELEASES.map(r => formatItem(r.outside) + \" at year \" + r.year).join(\", \") + \". Includes the launch allocation, liquidity rewards and unlocked development tokens, with liquidity providers staked in both reward pools throughout the four years.\"" + ] + } + ] +}; +// v16 changes no declaration at the top of a component or hook; never generated by put-pieces. const scopeChanges={}; const newNames={}; for(const f of Object.keys(editedFiles)){ diff --git a/web/scripts/check-motion.mjs b/web/scripts/check-motion.mjs index 76665a5..6a1e7de 100644 --- a/web/scripts/check-motion.mjs +++ b/web/scripts/check-motion.mjs @@ -292,13 +292,17 @@ try { }, position); assert.ok(gap < 0.01, `Blank of ${gap} px at ${position} ${width}`); } - // Real playback across both resets: no animationiteration event reaches the page. + // Real playback across both resets: no animationiteration event comes from the strip. Only the strip's own + // events count: while the vault is closed the Deposit page's CLOSED sign runs two endless CSS animations of its + // own, and their iterations say nothing about the strip (v16; before, they failed this check). const wrap = await strip.evaluate(async (m) => { const a = m.getAnimations({ subtree: true }), d = a[0].effect.getTiming().duration, crossed = []; let events = 0; - const count = () => events++; + const count = (e) => { + if (m.contains(e.target)) events++; + }; document.addEventListener("animationiteration", count, true); for (const at of [d / 2, d]) { for (const x of a) { diff --git a/web/scripts/check-visibility.mjs b/web/scripts/check-visibility.mjs index c87413c..ab705a0 100644 --- a/web/scripts/check-visibility.mjs +++ b/web/scripts/check-visibility.mjs @@ -46,16 +46,16 @@ const base=`http://127.0.0.1:${server.address().port}`; const b=await chromium.launch(),page=await b.newPage({viewport:{width:375,height:812},reducedMotion:'reduce'}); const results=[]; -// v15 on the v14 baseline (the live site): no first-screen element changes its -// words or its vertical place on any page; nothing is excepted. The one decided -// difference is across the Vault title panel on phones: the picture stands in -// the middle (it stood left of it), her bubble keeps its place beside her head, -// her shelf line runs across the panel and the heading is centred. The Deposit -// and Redeem titles equal the baseline (no heading focus marker is shown here: -// the pages are opened directly; check-art covers the marker). Every earlier -// state (Vault heading, Docs paragraphs and panels, the open Deposit stock list, -// Redeem layout, status notices) must equal the baseline. The header, slogan -// strip, and start of each title character stay pinned. +// v16 on the v15 baseline (the live site): no first-screen element changes its +// words or its vertical place on any page; nothing is excepted. All three title +// panels (Vault, Deposit, Redeem) equal the baseline across the panel, and the +// Vault title keeps the placing decided in v15: the picture in the middle, her +// bubble beside her head inside the frame, her shelf line across the panel, the +// heading centred. No heading focus marker is shown here (the pages are opened +// directly; check-art covers the marker). Every earlier state (Vault heading, +// Docs paragraphs and panels, the open Deposit stock list, Redeem layout, status +// notices) must equal the baseline. The header, slogan strip, and start of each +// title character stay pinned. const characterPages=['vault','deposit','redeem']; const docsAdded=[ 'The basket has no set weights. Any listed stock can be deposited, in any amount, as long as the vault’s checks pass and the vault stays under its size limit. So the mix is whatever holders have put in, and it changes with every deposit and redemption.', @@ -277,30 +277,29 @@ try { assert.deepEqual(newLayout.capacity,oldLayout.capacity,'Capacity label and figure value remain unchanged'); checks.push('Vault: heading, empty title paragraphs, panel height, capacity label and figure value equal the baseline'); // v15 (owner, 2026-10-13: "the graphic on mobile is off to the left and doesnt look good"; he chose the picture - // centred with the heading centred): across the panel only. The baseline proves the look it replaces. + // centred with the heading centred): the placing stays, and since v16 the baseline carries it too. const mid=a=>(a.frame.left+a.frame.right)/2,centre=r=>(r.left+r.right)/2,o=oldLayout.across,n=newLayout.across; assert.ok(near(centre(n.picture),mid(n)),'v15: the Vault picture stands in the middle of the panel: '+(mid(n)-centre(n.picture))); - assert.ok(mid(o)-centre(o.picture)>10,'Baseline carries the picture left of the middle'); assert.ok(near(n.bubble.left-n.picture.left,o.bubble.left-o.picture.left),'v15: the bubble keeps its place beside her head'); assert.ok(n.bubble.right<=n.frame.right-3.5&&n.bubble.left>=n.frame.left,'v15: the bubble stays 4 px inside the frame'); assert.ok(near(n.shelf.left,n.frame.left)&&near(n.shelf.right,n.frame.right),'v15: her shelf line runs across the panel'); - assert.ok(near(o.shelf.left,o.frame.left)&&o.shelf.right10,'Baseline carries the heading at the left'); + assert.equal(n.marker,false,'No heading focus marker when the page is opened directly'); + assert.deepEqual(rounded(n),rounded(o),'The Vault title equals the baseline across the panel'); assert.deepEqual(rounded([n.frame,n.heading]),rounded([o.frame,o.heading]),'v15: the panel and the heading box are where they were'); assert.deepEqual(rounded([newLayout.title,newLayout.picture]),rounded([oldLayout.title,oldLayout.picture]),'v15: the Vault title panel and picture keep their vertical place and height'); - checks.push('v15 Vault: the picture in the middle of the panel (left of it at the baseline), the bubble in its place beside her head and inside the frame, the shelf line across the panel, the heading centred; panel, heading box, picture height and every vertical place equal the baseline'); + checks.push('Vault title: the picture in the middle of the panel, the bubble in its place beside her head and inside the frame, the shelf line across the panel, the heading centred (v15); the whole title equals the baseline across the panel and in every vertical place'); } if(name==='deposit'||name==='redeem') { assert.equal(newLayout.across.marker,false,'No heading focus marker when the page is opened directly'); - assert.deepEqual(rounded(newLayout.across),rounded(oldLayout.across),'v15: the '+name+' title equals the baseline across the panel'); - assert.deepEqual(rounded([newLayout.title,newLayout.picture]),rounded([oldLayout.title,oldLayout.picture]),'v15: the '+name+' title panel and picture equal the baseline'); - checks.push('v15 '+name+': the title (picture, bubble, heading, shelf line) equals the baseline while the heading has no focus marker'); + assert.deepEqual(rounded(newLayout.across),rounded(oldLayout.across),'The '+name+' title equals the baseline across the panel'); + assert.deepEqual(rounded([newLayout.title,newLayout.picture]),rounded([oldLayout.title,oldLayout.picture]),'The '+name+' title panel and picture equal the baseline'); + checks.push(name+' title: picture, bubble, heading and shelf line equal the baseline while the heading has no focus marker'); } assert.ok(statusFixtureReads>beforeFixtureReads,'State comparison consumes the existing depositStatus fixture'); - assert.equal(oldLayout.led.signs.length,newLayout.led.signs.length,'Baseline and v15 carry the same sign count'); + assert.equal(oldLayout.led.signs.length,newLayout.led.signs.length,'Baseline and this build carry the same sign count'); if(statusCase==='normal') { assert.equal(newLayout.depositStatus.length,0,'Normal operation has no extra status area'); assert.equal(newLayout.led.signs.length,0,'Normal operation has no sign'); @@ -379,7 +378,7 @@ try { } for(const x of prior) { const y=counterpart(x); - // v15 changes no word and no vertical place: every prior first-screen element is there, where it was. No exception. + // No word and no vertical place changes: every prior first-screen element is there, where it was. No exception. if(y&&y.bottom<=812) { if(!near(y.y,x.y)||!near(y.bottom,x.bottom))regressions.push({...x,now:y,reason:'First-screen content moved vertically'}); continue; @@ -406,6 +405,6 @@ try { } assert.deepEqual(routingErrors,[],'Only local files and allowed public RPC reads; fixture account reads stay synthetic'); for(const r of results)if(r.regressions.length)throw Error(JSON.stringify({variant:r.variant,name:r.name,regressions:r.regressions})); - console.log('PASS 375x812 baseline visibility: six pages, no-wallet and connected;',results.reduce((n,r)=>n+r.prior.length,0),'previous first-screen elements with their words in the same vertical place, no exception; the Vault title across the panel as decided in v15, the Deposit and Redeem titles equal to the baseline; header height, slogan strip and character assertions retained'); + console.log('PASS 375x812 baseline visibility: six pages, no-wallet and connected;',results.reduce((n,r)=>n+r.prior.length,0),'previous first-screen elements with their words in the same vertical place, no exception; the three title panels equal to the baseline across the panel, the Vault title in its v15 placing; header height, slogan strip and character assertions retained'); fs.writeFileSync('../artifacts/phone-visibility.json',JSON.stringify(results,null,2)+'\n'); }finally{await b.close();await new Promise(r=>server.close(r));} diff --git a/web/scripts/run-browser.mjs b/web/scripts/run-browser.mjs index 6811ada..c14ebbd 100644 --- a/web/scripts/run-browser.mjs +++ b/web/scripts/run-browser.mjs @@ -2211,10 +2211,10 @@ try { // The existing fork clock advances with real elapsed time. Only its derived deadlines vary by run. // The gate separately compares exact calldata/deadlines at six fixed clocks; send capture checks live clock above. const normalized=records=>records.map(record=>['deposit','redeem'].includes(record.functionName)?{...record,args:[...record.args.slice(0,-1),'browser-clock-plus-600']}:record); - assert.deepEqual(normalized(serialized),normalized(previous.sends),'All 70 sends equal v14 apart from wall-clock-derived deadlines'); - // v15 changes style rules for phones only: the previous record is the v14 walk, and every one of its checks is - // here under the same name in the same order, with none added, renamed or left out. - assert.deepEqual(checks,previous.checks,'Every v14 page check retained under its name, in order, and none added'); + assert.deepEqual(normalized(serialized),normalized(previous.sends),'All 70 sends equal the previous walk apart from wall-clock-derived deadlines'); + // The previous record is the walk of the live commit: every one of its checks is here under the same name in + // the same order, with none added, renamed or left out (v15 and v16 change no page check). + assert.deepEqual(checks,previous.checks,'Every page check of the previous walk retained under its name, in order, and none added'); assert.deepEqual(shots,previous.shots,'Every prior screenshot retained in order'); } assert.equal(revoked.length,0); diff --git a/web/src/Tokenomics.tsx b/web/src/Tokenomics.tsx index af8e644..6510f74 100644 --- a/web/src/Tokenomics.tsx +++ b/web/src/Tokenomics.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { parseAbi, formatUnits, type Address } from "viem"; import { many } from "./chain"; -import { ADDRESSES, ALLOCATIONS, ALLOCATION_HEADERS, ALLOCATION_ROWS, RELEASE_HEADERS, RELEASE_ROWS, PARAGRAPHS, T, gaugeReleased, developmentVested, outsideContracts, formatItem, SUPPLY_LABEL, RELEASE_LABEL, type Addresses } from "./tokenomicsData"; +import { ADDRESSES, ALLOCATIONS, ALLOCATION_HEADERS, ALLOCATION_ROWS, RELEASE_HEADERS, RELEASE_ROWS, PARAGRAPHS, T, OUTSIDE_AT_LAUNCH, TOTAL_SUPPLY, outsideContracts, formatItem, SUPPLY_LABEL, RELEASE_LABEL, type Addresses } from "./tokenomicsData"; const colors = ["var(--royal)", "var(--sky)", "var(--sun)", "var(--red)", "var(--ink)", "#93aaca", "#dbe6f4"]; function Table({ headers, rows, kind }: { headers: readonly string[]; rows: readonly (readonly string[])[]; kind: string }) { @@ -16,10 +16,10 @@ function Curve({ width, height }: { width: number; height: number }) { // Both sides of the cliff are sampled: there is no gradual vesting in month 11. const samples = [...Array.from({ length: 12 }, (_, i) => i), 11.999999, ...Array.from({ length: 37 }, (_, i) => i + 12)]; const line = (fn: (seconds: number) => number) => samples.map((m, i) => `${i ? "L" : "M"}${x(m).toFixed(3)},${y(fn(m / 48 * T)).toFixed(3)}`).join(" "); - return {[0, 250, 500, 750, 1000].map(n => {formatItem(n)})}{[0, 12, 24, 36, 48].map(m => {m})}{[12, 24, 36, 48].map(m => )}; + return {[0, 250, 500, 750, 1000].map(n => {formatItem(n)})}{[0, 12, 24, 36, 48].map(m => {m})}{[12, 24, 36, 48].map(m => )}; } function ReleaseChart() { - return
Released over four years
Million ITEM
Months after launch
Outside the contracts, at mostLiquidity rewards releasedDevelopment vested
; + return
Total ITEM available
Million ITEM
Months after launch
{formatItem(OUTSIDE_AT_LAUNCH / 1e6)} million at launch → {formatItem(TOTAL_SUPPLY / 1e9)} billion by year four
; } const liveAbi = parseAbi(["function totalSupply() view returns (uint256)", "function released() view returns (uint256)", "function remaining() view returns (uint256)", "function totalStaked() view returns (uint256)", "function vested() view returns (uint256)"]); export const hasAddresses = (addresses: Addresses): addresses is Record => Object.values(addresses).every(value => value !== null); diff --git a/web/src/tokenomicsData.ts b/web/src/tokenomicsData.ts index cffef40..b9a4538 100644 --- a/web/src/tokenomicsData.ts +++ b/web/src/tokenomicsData.ts @@ -10,7 +10,7 @@ export const ALLOCATIONS = [ "share": 49, "amount": 490000000, "who": "BASK/USDG liquidity providers", - "terms": "Released by the gauge over four years, front loaded, only while someone is staked" + "terms": "Released over four years, with more rewards at the start" }, { "share": 21, @@ -52,13 +52,13 @@ export const ALLOCATIONS = [ export const PARAGRAPHS = [ "ITEM's supply is 1,000,000,000, created once at launch. Nothing can mint more. It is a plain token: anyone can hold, send or trade it.", "How the supply is divided:", - "What is available when. At launch, 26% of the supply exists outside the reward and vesting contracts: the launch pool, the swarm's share, operations and the unlocked development amount. The other 74% is held by contracts and comes out only on schedule: 70% through the two gauges, to the people staked in them, and 4% through vesting. If both gauges run without a pause, the most that can be outside those contracts is:", - "A gauge's clock only runs while someone is staked, so these are the earliest dates, not fixed ones.", + "What is available when. At launch, 260,000,000 ITEM (26% of the supply) is available through the launch pool, the swarm's share, operations and the unlocked development amount. The remaining 740,000,000 ITEM is released over four years: 700,000,000 as liquidity rewards and 40,000,000 through development vesting. The chart and table show this schedule with liquidity providers staked in both reward pools from launch throughout the four years.", + "The line shows the total ITEM available: the 260,000,000 available at launch plus the liquidity rewards and development tokens released by each date. The table shows the breakdown.", "Where value flows. ITEM pays the people who make BASK and ITEM tradable: liquidity providers earn ITEM from the gauges. ITEM holders who stake earn the vault's fees, 0.5% of every deposit and 0.5% of every redemption, paid in BASK. Trading fees in the two Uniswap v2 pools stay with the liquidity providers of those pools. Trades in the launch pool carry a 1.25% fee set by IMD: 1% to the protocol's operations wallet and 0.25% to IMD.", "What cannot change. The split above, the four-year gauge schedule, the 7-day fee stream and the vesting dates are constants in contracts with no owner. Nobody holds a key that can alter them." ] as const; export const ALLOCATION_HEADERS = ["Share","ITEM","Who","Terms"] as const; -export const RELEASE_HEADERS = ["End of","Released by the gauges","Vested","Outside the contracts, at most"] as const; +export const RELEASE_HEADERS = ["End of","Liquidity rewards released","Development unlocked","Total ITEM available"] as const; export const ADDRESSES: Record<"ITEM" | "ITEM_STAKING" | "BASK_USDG_GAUGE" | "ITEM_ETH_GAUGE" | "DEV_VESTING", Address | null> = { ITEM: null, ITEM_STAKING: null, BASK_USDG_GAUGE: null, ITEM_ETH_GAUGE: null, DEV_VESTING: null, }; @@ -81,4 +81,4 @@ export const RELEASES = [1, 2, 3, 4].map(year => { export const ALLOCATION_ROWS = ALLOCATIONS.map(r => [r.share + "%", formatItem(r.amount), r.who, r.terms]); export const RELEASE_ROWS = RELEASES.map(r => ["Year " + r.year, formatItem(r.gauges), formatItem(r.vested), formatItem(r.outside) + " (" + r.share + "%)"]); export const SUPPLY_LABEL = ALLOCATIONS.map(r => r.who + " " + r.share + "%").join(", "); -export const RELEASE_LABEL = "Outside the contracts, at most: " + formatItem(OUTSIDE_AT_LAUNCH) + " at launch, " + RELEASES.map(r => formatItem(r.outside) + " at year " + r.year).join(", ") + ". Liquidity rewards released: " + formatItem(GAUGE_ALLOCATION) + " over four years. Development vested: 0 before month 12, " + formatItem(developmentVested(CLIFF)) + " at month 12, " + formatItem(VESTED_ALLOCATION) + " at month 48."; +export const RELEASE_LABEL = "Total ITEM available: " + formatItem(OUTSIDE_AT_LAUNCH) + " at launch, " + RELEASES.map(r => formatItem(r.outside) + " at year " + r.year).join(", ") + ". Includes the launch allocation, liquidity rewards and unlocked development tokens, with liquidity providers staked in both reward pools throughout the four years."; diff --git a/web/validation/README.md b/web/validation/README.md index c43c346..6b6b37c 100644 --- a/web/validation/README.md +++ b/web/validation/README.md @@ -52,3 +52,5 @@ The expected values follow `web/pinned/src/BaskVault.sol`: `_value`, `_depositSh The v14 baseline is the v13 commit 2b7d55bc32d09610fed936d963d9a8c492f4eecf. check-calldata permits Docs.tsx and Flows.tsx through documented v14 pieces and the four module sources through reviewed digests; wallet.tsx and every other source equal HEAD. check-bundle's source scope adds Flows.tsx to the module's. check-visibility compares the v13 six-route phone view at 375×812, matching Docs panels by title around the five inserted ITEM panels; the Deposit phone layout is unchanged. The v14 fork keeps all 86 v13 check positions and 70 page sends, replaces the words of three Deposit checks for the desktop two-column layout, adds the Tokenomics and BASK checks and one Docs check for the five ITEM panels, and takes the same 24 screenshots at JPEG quality 18 (26 before) so that the longer Docs page keeps the delivery under 8,388,608 bytes. check-bask-guide proves the arbitrage flywheel example through the audited helpers and counts five examples, five tables and ten formula lines; check-tokenomics and check-tokenomics-live are unchanged. The v15 baseline is the v14 commit c05c174f639d352fb6184636bcc6e083031daa65. v15 changes style rules only (the end of styles.css): check-calldata has no edited script file, freezes every source whole against v14, keeps the module and source digests and pins the added style rules by one reviewed digest with a scan; check-bundle's source scope is styles.css alone with no new file. check-visibility compares the v14 six-route phone view at 375×812 with no exception: every first-screen element keeps its words and vertical place, the Deposit and Redeem titles equal v14 across the panel, and the Vault title is asserted across the panel (picture in the middle, shelf line across the panel, heading centred) against the v14 look. check-art keeps its assertions at every width from 320 to 2560px in both engines and adds the Vault phone layout, the comparison of every title with the live v14 title, and the heading's focus marker meeting the shelf line on Deposit and Redeem at seven phone widths, each with the v14 look rejected by the same check. The v15 fork keeps all 91 v14 checks under their names in order with none added, the same 70 page sends and the same 24 screenshots at JPEG quality 18. + +The v16 baseline is the v15 commit 5110ca96c434441cdffaec727413c54ede26dafe. v16 changes two display sources (Tokenomics.tsx, tokenomicsData.ts: the release chart shows one total line with reviewed words; numbers and formulas unchanged) and one check script (check-motion.mjs counts only the slogan strip's own animationiteration events, so the Deposit page's CLOSED sign no longer fails it). check-calldata permits the two sources through documented pieces, written-out call and declaration changes and new reviewed digests, and freezes every other source; check-bundle's source scope is those two files. check-visibility and check-art compare with the v15 site and expect equality everywhere they compare (the Tokenomics tab is not on the first screen of any route). The v16 fork keeps every v15 check under its name in order, the same 70 page sends and the same 24 screenshots at JPEG quality 18.