{"assessments":[],"deployments":[],"fuzz":[],"identity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"interpretation":"Records acceptance and evidence. Neither completion nor an AI assessment establishes correctness, safety, or independent review.","jobId":"a2bf7a9b-56a5-4425-948f-3092d92d3914","kind":"skill:build-website","nodes":[{"acceptedSubmissionHash":"21e9df3282863b784ab161ed588fc66a9650856162560a5f4a55e643f4247ab8","dependsOn":[],"execution":{"mustProduce":["dist/index.html"],"network":true,"optionalTools":["browser"],"profile":"none","requires":["network"],"skillHash":"d09d5c017130fd8c9114daa8383d99944c68d9819cc4bab4554508c9ce6d73c9","skillId":"build-website","tools":[]},"key":"build_website","kind":"code","role":"implement","skillHash":"d09d5c017130fd8c9114daa8383d99944c68d9819cc4bab4554508c9ce6d73c9","skillId":"build-website","state":"accepted"},{"acceptedSubmissionHash":"a73e333556d554dd73ed5764aec993122575769454d9d742a7afebbdebeaff7c","dependsOn":["build_website"],"execution":{"network":true,"optionalTools":["browser"],"profile":"none","requires":["network"],"skillHash":"4a5e9fb083a47bea78572ce4262f6ed18e7af57736a86ec2bbb7d17a8c72c62e","skillId":"design-critic","tools":[]},"key":"design_critic","kind":"code","role":"review","skillHash":"4a5e9fb083a47bea78572ce4262f6ed18e7af57736a86ec2bbb7d17a8c72c62e","skillId":"design-critic","state":"accepted"}],"objective":"https://ambidex.trade/","parentJobId":"71f41f7b-f238-4185-a761-79e9fd832dc1","planHash":"9a238e2a85a2ba779e743969d9332c3d12bc54f17b47cb999dacccdf61058fc9","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"71f41f7b-f238-4185-a761-79e9fd832dc1","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1328-ambidex"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51043","feedbackHash":"eb0cb21234b49f6d47699da95456b2fafabf391c6f4e23efd682e0e49665d890","nodeKey":"build_website","submissionHash":"a4a901fe2b161eb13e50eede9ccf6d942f67600a5ad3bd7cc8a8199d3e3d366e","tag1":"verification:structural","tag2":"acceptance-v2","value":1},{"agentId":"51168","feedbackHash":"08d69af30eec33caee74323d02a006862f8b72ae1d332b6d61a5c475fe2d4a57","nodeKey":"build_website","submissionHash":"21e9df3282863b784ab161ed588fc66a9650856162560a5f4a55e643f4247ab8","tag1":"verification:structural","tag2":"acceptance-v2","value":1},{"agentId":"51166","feedbackHash":"179b3c752793550a2637ae36cd331a8bc41c94d93de63d7544c0f1b5facaefe8","nodeKey":"design_critic","submissionHash":"76fc866a0a1898e2b2ecf3c46d9124f6ae322b7bc603cec77ce36b18a0cdaa6b","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51319","feedbackHash":"7a0bc8c59ee5fd3f6ae7aee1a034018fca1e68a0cd656d222dd6e3baf1eb9cf6","nodeKey":"design_critic","submissionHash":"a73e333556d554dd73ed5764aec993122575769454d9d742a7afebbdebeaff7c","tag1":"review:submission","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"c8f3d9099b83e86af5df844bf54bcc0cf11c42118a376102351213a6a1a3aa33","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"e039175eb279502ac665e5978c08fc10981ba21b0e181ee05c70978d88ba1655","device":"6208734cdf5317a1","findings":[],"hash":"21e9df3282863b784ab161ed588fc66a9650856162560a5f4a55e643f4247ab8","nodeId":"f6ea667f-333c-497e-b7a2-8f9de5f85784","outcome":"completed","summary":"All eight findings reproduced and are fixed; the final export is rebuilt, probed, and documented. Nothing else is pending.\n\n**What I found and did**\n\n- **Domain mismatch (high):** ambidex.trade serves an unrelated Robinhood Chain product, and its og.png, favicon and token.png all return 404. Publishing there is impossible from this repository, so I removed every claim to it. The README now says the site lives wherever the plane pins it, index.html dropped canonical and og:url and uses a relative share image, and the hard-coded origin left config.ts. A new Vite plugin emits absolute share tags only when the publisher rebuilds with a real origin in `VITE_SITE_URL`, and rejects non-http(s) values.\n- **320 header overflow (high):** reproduced at scrollWidth 359. Below 23rem the wordmark hides and nav padding tightens. Re-measured at 320 with no overflow and the nav ending at 304.\n- **Dead wallet icon:** the token icon is now resolved at run time against the page's own URL. A mock EIP-6963 wallet received a URL that fetched with status 200.\n- **Weights, footnote, pending Refresh, live region, em dash:** all applied as reported, with one deliberate deviation. Refresh uses `aria-disabled` instead of native `disabled`, because the pinned guide warns native disabled drops keyboard focus. The probe confirmed focus stays on the button during a delayed read.\n\n**Verification on the final export**\n\n| Check | Result |\n| --- | --- |\n| typecheck, unit tests, ABI hash | pass, 5 of 5, match |\n| production build | exit 0 |\n| Chromium probe at 320/390/1440 | no overflow, weights 400/600/700, 58 first-screen words at 390, no console errors |\n| mock wallet watchAsset | image resolved against the served page, fetched 200 |\n\n**Limitations recorded:** WebKit cannot launch on this host because of missing system libraries, so Safari rendering is unverified. No screen reader, native zoom, or real wallet extension was used.\n\nDeliverables are in place: rebuilt dist/, updated README and DESIGN.md, artifacts/validation.md with screenshots, and .imd-responses.json answering all eight findings as fixed. Bundle size is about 0.8 MiB. Changes are left uncommitted for the plane to pick up, matching the previous round.","treeHash":"7a0a857c7182e81326bf59bf687b95cc69743feb","usage":{"cachedInputTokens":4074873,"inputTokens":1570,"model":"claude-fable-5-1","outputTokens":41723,"runtime":"claude","turns":50,"wallClockMs":769963}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"45aa937328087de3","findings":[{"citation":"resolved","description":"Now: the URL the review names, https://ambidex.trade/, returns a Next.js page titled \"AMBIDEX | Trade both ways\" for \"Permissionless margin on Robinhood Chain\", whose only token address is 0x41deeba2249153e6e5fd0950e83d568ddee30fe8 on \"Robinhood Chain (4663)\", bought \"on pons\" and settled in USDG. None of the deployment's facts appear there: not AmbidexToken 0x81b56b0da7bf1b553123a55da538515aa6e06941, not Ethereum chain id 1, not the IMD pair or the Uniswap v4 pool key. The export in dist/ that this repository built (and that README says is the site at this domain) is not what the domain serves; its assets /og.png, /favicon.svg, /favicon.png and /token.png all return 404 from that host. Change to: publish the committed dist/ at https://ambidex.trade/ (the README's own instruction: \"point that domain at the published export\"), or, if that domain belongs to the other product and will keep serving it, change README line 4, web/index.html's canonical, og:url and og:image, and SITE.url in web/src/config.ts to the host that actually serves this export, so no page claims the domain. Why: the brief says \"Connect the site to those contracts ... the verified chain, addresses\" and the review bar says \"whether the contract addresses and chain the page uses are the ones the brief or the deployment names (a mismatch is a missing requirement, so high)\". A visitor sent to the named URL reaches a site for a different token on a different chain.","line":4,"path":"README.md","reproduction":"Any width. 1) curl -s https://ambidex.trade/ | grep -o '<title>[^<]*' → \"AMBIDEX | Trade both ways\"; grep -o '0x[0-9a-fA-F]{40}' finds only 0x41deeba2249153e6e5fd0950e83d568ddee30fe8 (6 times) and never 0x81b56b0da7bf1b553123a55da538515aa6e06941; grep 'Robinhood Chain (4663)' matches. 2) curl -sI https://ambidex.trade/og.png → 404; same for /favicon.svg, /favicon.png, /token.png. 3) diff <(curl -s https://ambidex.trade/) dist/index.html → the two files share no line. 4) taste.mjs run against the live URL: external host api.ambidex.trade, 16 checks with 10 failures (15 type sizes at 390, 8 weights, 11 kicker labels, 31 tracked labels, 50 all-caps boxes, a ticker, transition: all on 17 elements, 11 text boxes cut by overflow: hidden) — none of which exist in dist/, confirming it is a different build. Screenshot test/scratch/taste-live/shots/390.png shows the hero \"LONG OR SHORT ANY COIN.\" with \"Start with 10,000 paper USDG\".","severity":"high","snippet":"static website for it at https://ambidex.trade/.","title":"fidelity: ambidex.trade serves another product on another chain, not this export"},{"citation":"resolved","description":"Now: at 320 wide the brand (logo + \"AMBIDEX\", right edge at 136px) and the three nav links (\"Live\", \"Trade\", \"Contract\", each with 12px side padding and a 4px gap, 207px wide) do not fit the 288px content width, so .nav ends at 359px in Chromium (331px in WebKit) and the whole document scrolls horizontally; \"Contract\" is cut to \"Cont\" at the right edge. Change to: let the header close up below 23rem: add `@media (max-width: 23rem) { .brand__name { display: none; } }` (the link keeps its aria-label \"AMBIDEX home\" and the logo tile, so the home link still looks like one) and reduce `.nav a { padding-inline: var(--space-2); }` so the three links need 183px and sit inside the 288px row with the 32px logo. Alternative with the same effect: `.site-header__inner { flex-wrap: wrap; }` so the nav drops to a second row at 320. Why: the bar is \"No horizontal scroll at 320, 390 or 1440\"; horizontal scroll at 320 is high.","line":282,"path":"web/src/styles.css","reproduction":"320×700, dist/ served locally, Chromium and WebKit. Probe line (layout): \"320px: page is 359px wide — #root > header.site-header > div.container.site-header__inner > nav ends at 359px … li:nth-of-type(3) > a ends at 359px\". Own measurement: Chromium clientWidth 320, scrollWidth 359, nav left 152 right 359, brand right 136; WebKit scrollWidth 331, nav right 331. Screenshot test/scratch/mine/chromium-320-header.png shows \"Live Trade Cont\" with \"Contract\" clipped by the viewport. At 390 the nav ends at 374px and nothing scrolls.","severity":"high","snippet":"  padding-inline: var(--space-3);","title":"bug: the header nav runs off the page at 320 and the page scrolls sideways"},{"citation":"resolved","description":"Now: og:image and twitter:image are the absolute URL https://ambidex.trade/og.png, and \"Add AMBI to wallet\" sends wallet_watchAsset with image https://ambidex.trade/token.png (SITE.url in web/src/config.ts line 8, used in WalletPanel.tsx line 139). That host serves the other product and answers 404 for both files, so a posted link unfurls with title and description but no card image, and the token lands in the wallet without its icon. The export itself uses relative URLs so it can live on an IPFS gateway or ENS name, where these absolute links still point at the dead host. Change to: once the export is published, set SITE.url and the og:image, og:url, canonical and twitter:image values to the origin that actually serves dist/ (an og:image must be absolute, so it cannot simply be made relative); if that origin is unknown at build time, drop the three absolute tags rather than ship ones that 404, and pass no image in wallet_watchAsset (the wallet then shows its default icon instead of a broken fetch). Why: the share bar asks for a real og:image \"so the link unfurls when someone posts it\"; an image URL that 404s is a share card that does not render.","line":24,"path":"web/index.html","reproduction":"Any width. curl -s -o /dev/null -w '%{http_code}' https://ambidex.trade/og.png → 404; same for https://ambidex.trade/token.png → 404 (both return the other site's HTML 404 page, 15091 bytes). With a mock EIP-6963 wallet connected on chain 0x1 at 390 wide, clicking \"Add AMBI to wallet\" issues wallet_watchAsset with options.image = \"https://ambidex.trade/token.png\" (captured in test/scratch/mine run, both Chromium and WebKit). The probe's share check passes only because it tests presence, not reachability.","severity":"medium","snippet":"    <meta property=\"og:image\" content=\"https://ambidex.trade/og.png\" />","title":"bug: the share image and the wallet token icon point at URLs that return 404"},{"citation":"resolved","description":"Now: the page renders weights 400, 500, 600, 650 and 700. 650 is used by .brand (line 257), .section-title (line 637) and .stat__value (line 665); 500 by .nav a, .button, .field label, .link-button and .stat__placeholder; 600 by .card__title and .address-row__label; 700 by the h1. 600 and 650 are not distinguishable at 14–28px with a system face, and 500 reads as 400 on most system fonts, so the two extra steps add nothing a reader can see. Change to: replace every `font-weight: 650` with 700 (section titles, stat values and the brand join the h1's weight, which keeps the display hierarchy by size) and every `font-weight: 500` with 600 (buttons, labels, nav and the placeholder read as the same UI weight as card titles). The scale becomes 400 / 600 / 700, no other values change. Why: the probe's type-faces bar is three weights (\"At most three weights and three families\"); five is the same failure on every section.","line":257,"path":"web/src/styles.css","reproduction":"390 and 1440, dist/ served locally. Probe line (type-faces): \"families (2): ui-sans-serif, ui-monospace — weights (5): 400, 500, 600, 650, 700 — weight 500 outside mono (noted, not counted): 20, e.g. nav > ul.nav > li:nth-of-type(1) > a 'Live'\". From taste.json: weight 650 on span.brand__name (16px), #stats-heading, #trade-heading, #contract-heading (28px) and dd.stat__value (20px); weight 500 on nav links, 'Check balance', 'Deployment transaction', the 'Ethereum address' label and 'Etherscan' buttons.","severity":"medium","snippet":"  font-weight: 650;","title":"taste: five font weights across the page where the bar allows three"},{"citation":"resolved","description":"Now: at 390×844 the first screen carries the hero lede (20 words), the card lede (15), the no-wallet notice (14) and the card footnote \"Read-only data comes from public Ethereum RPC. Transactions are signed only in your wallet.\" (14), 63 words of prose against a bar of 60. The footnote says what the footer already says in its own words: \"This site reads public chain data and sends transactions only through your own wallet.\" Change to: delete the footnote paragraph (lines 94–96) and its `.card__footnote` rule if nothing else uses it; the card closes 40px shorter and the \"Live on Ethereum\" section moves up by that much. First-screen prose drops to 49 words. Why: the bar is \"At most 60 words of prose on the first phone screen\", and \"a line that repeats\" is on the cut list; the explanation a transaction needs (\"Transactions are signed only in your wallet\") survives in the footer and in each action's own status copy.","line":95,"path":"web/src/components/WalletPanel.tsx","reproduction":"390×844, dist/ served locally. Probe line (copy-budget): \"prose on the first screen: 63 words at 390 (bar 60), 69 at 1440 (bar 90)\". From taste.json texts390 with y < 844: p.hero__lede 20 words at y=180, p.card__lede 15 at y=374, div.notice > p 14 at y=452, p.card__footnote 14 at y=626. Screenshot test/scratch/taste/shots/390.png shows the footnote inside the first screen under the Swap button.","severity":"medium","snippet":"        Read-only data comes from public Ethereum RPC. Transactions are signed only in your wallet.","title":"copy: the first phone screen holds 63 words of prose because the wallet card repeats the footer"},{"citation":"resolved","description":"Now: refresh() only bumps a counter; token and pool keep status 'ready' with the old data while the new RPC calls run (up to the 15 s timeout per URL), and Stats shows \"Reading the chain…\" only when updatedAt is null, so after a click the line still reads \"Updated 09:10 PM · Refresh\", the button stays enabled and nothing tells the visitor a read is in flight or has failed quietly. Change to: in the effect's first line set `setToken((prev) => ({ status: 'loading', data: prev.data }))` and the same for pool, and in Stats.tsx render \"Reading the chain…\" when `data.pool.status === 'loading'` (before the updatedAt branch at Stats.tsx lines 17–21) and set `disabled={data.pool.status === 'loading'}` on the Refresh button. The tiles keep showing the last values because data is preserved. Why: \"Walk every state it reaches (loading, empty, pending, success, failure, retry): each one says only what the app knows\"; a pending read that looks finished is a state the app knows and does not say.","line":24,"path":"web/src/lib/useChainData.ts","reproduction":"1440×900, dist/ served locally, Chromium and WebKit. Wait for \"Updated HH:MM PM · Refresh\". Route every request to ethereum-rpc.publicnode.com and eth.drpc.org through a 6 s delay, click Refresh, read the line 1.5 s later: before \"Updated 09:10 PM · Refresh\", during \"Updated 09:10 PM · Refresh\", Refresh button disabled: false (test/scratch/mine run). Source: useChainData.ts lines 26–60 never set status back to 'loading' on a tick; Stats.tsx lines 17–21 key the pending text on updatedAt.","severity":"low","snippet":"  const refresh = useCallback(() => setTick((n) => n + 1), []);","title":"bug: Refresh gives no pending state and stays clickable while the chain is read"},{"citation":"resolved","description":"Now: the \"Updated 09:04 PM · Refresh\" paragraph has role=\"status\" and useChainData re-reads the chain every 60 s, so every minute the time changes and assistive technology announces \"Updated 09:05 PM Refresh\" unprompted, for as long as the tab is open; the region also contains a button, which is announced as part of the status text. Change to: remove role=\"status\" from this paragraph (keep it on the error paragraphs at lines 59 and 63, which are the states a visitor needs to hear) and leave the Refresh button where it is. Why: WCAG 4.1.3 status messages are for changes the user needs to know; a clock tick is not one, and a live region should not wrap a control.","line":16,"path":"web/src/components/Stats.tsx","reproduction":"Any width, dist/ served locally. In Stats.tsx the role=\"status\" paragraph renders data.updatedAt.toLocaleTimeString; useChainData.ts line 16 sets REFRESH_MS = 60_000 and line 55 schedules refresh on that timer, and line 44 sets updatedAt to a new Date on every successful pool read. Observed: at 390 the line read \"Updated 09:04 PM · Refresh\" at load and \"Updated 09:08 PM\" in the later mock-wallet run; each minute boundary changes the text node inside the live region.","severity":"low","snippet":"        <p className=\"stats__updated\" role=\"status\">","title":"accessibility: the timestamp line is a live region, so screen readers hear it every minute"},{"citation":"resolved","description":"Now: the <title>, og:title (line 18) and twitter:title (line 29) read \"AMBIDEX (AMBI) — fixed-supply token on Ethereum\", so the one em dash on the site sits in the text that appears in every tab, bookmark and unfurled link. Change to: \"AMBIDEX (AMBI): a fixed-supply token on Ethereum\" in all three tags. Why: the house bar cuts \"em dashes and emoji in interface text\"; the probe's glyphs check only reads the body, which is why it passed.","line":6,"path":"web/index.html","reproduction":"Any width. grep -n '—' web/index.html → lines 6, 18 and 29 only; grep -c '—' web/src/**/*.tsx → 0. Probe share line: title \"AMBIDEX (AMBI) — fixed-supply token on Ethereum\". The browser tab in every screenshot run carried the dash.","severity":"low","snippet":"    <title>AMBIDEX (AMBI) — fixed-supply token on Ethereum</title>","title":"copy: the page title and share title carry an em dash"}],"hash":"76fc866a0a1898e2b2ecf3c46d9124f6ae322b7bc603cec77ce36b18a0cdaa6b","nodeId":"ee29fcb8-59b8-4a87-baa5-4cd0e0ef6a05","outcome":"completed","summary":"Review complete. The findings file holds 8 substantiated findings, every citation checked against the tree. No repository file was changed.\n\n| Area | Export in `dist/` (what this tree built) | https://ambidex.trade/ (what the URL serves) |\n| --- | --- | --- |\n| Brief requirements met / missing / faked | 6 / 0 / 0 | 0 / 6 / 0 |\n| Broken features | 0 | not this project |\n| Product on the first screen at 390 | yes | no (hero slogan, different product) |\n| taste@1 checks passed | 12 of 16 | 6 of 16 |\n| Copy findings | 2 | not reviewed |\n| Share card | present, but image URL returns 404 | present |\n\n**The headline problem.** The URL named for this review does not serve this export. It serves a Next.js margin-trading site for a token at 0x41de…0fe8 on \"Robinhood Chain (4663)\", settled in USDG. Nothing from the deployment appears there: not the AmbidexToken address, not Ethereum chain id 1, not the IMD pool. Its /og.png, /favicon.svg and /token.png return 404. The export in the tree is a separate, working site, so the counts above are split in two columns.\n\n**Requirements, judged on the export** (served locally from `dist/`, Chromium and WebKit):\n- \"read their state\": met. The stat tiles read name, symbol and totalSupply from 0x81b5…6941 and slot0 from StateView, showing 1B AMBI, 0.000002548 IMD, 2,548 IMD, 1.25%.\n- \"offer their main actions through the visitor's wallet\": met. With a mock EIP-6963 wallet, connect, wrong-network notice, switch, add-token and send all worked. Send produced transfer calldata to the token address with the correct amount.\n- \"Derive ABIs from the pinned source and check them against the abiHash\": met. I recomputed the committed ABI's canonical hash and it equals the launch record's 94a29d95….\n- \"Never hard-code an address that is not in deployment.json\": met. Every address in the bundle is from the two records or is a viem internal constant.\n- Token name AMBIDEX, symbol AMBI, supply one billion, 18 decimals: met and read live from the chain, which returned exactly those values.\n- Chain facts from network.json: met. Chain id, RPC URLs and explorer match.\n\nAt the live URL all six are missing, because it is a different product.\n\n**The idea.** A fact sheet for a fixed-supply token, carried by one device: a near-white page where the only colour is the indigo of the single action per section, and the live numbers are the design.\n\n**Findings in the file, most severe first:**\n1. High, fidelity: ambidex.trade serves another product on another chain, not this export.\n2. High, bug: at 320 wide the header nav runs 39px past the viewport in Chromium (11px in WebKit) and \"Contract\" is cut off, so the page scrolls sideways.\n3. Medium, bug: og:image and the wallet_watchAsset icon point at the named host, which returns 404 for both.\n4. Medium, taste: five font weights across every section against a bar of three. Collapse 650 to 700 and 500 to 600.\n5. Medium, copy: 63 words of prose on the first phone screen. Delete the wallet card footnote, which repeats the footer.\n6. Low, bug: Refresh gives no pending state and stays clickable while the chain is read.\n7. Low, accessibility: the \"Updated HH:MM\" line is a live region that re-announces every minute and wraps a button.\n8. Low, copy: the title and share titles carry an em dash.\n\nThe probe's contrast failure is a false positive. Chromium reports the oklch tokens unresolved, so the script treats them as transparent. Computed by hand, white on the accent is 6.41:1 and muted text on the page is 5.98:1. WebKit ran after installing its system libraries. Every flow matched Chromium there, with no console errors.","treeHash":null,"usage":{"cachedInputTokens":2490421,"inputTokens":580,"model":"claude-fable-5-1","outputTokens":38133,"runtime":"claude","turns":44,"wallClockMs":725020}},{"artifacts":[],"attempt":1,"bundleHash":"22d52060ae7ba79bd7e0a3ac1e6dad056867708d537a2ed548a00b641bb9f50f","device":"2565f234b0a569e9","findings":[],"hash":"a4a901fe2b161eb13e50eede9ccf6d942f67600a5ad3bd7cc8a8199d3e3d366e","nodeId":"f6ea667f-333c-497e-b7a2-8f9de5f85784","outcome":"completed","summary":"The ambidex.trade website is built, verified, documented and committed.\n\n**What was delivered**\n\n- `web/` holds a Vite, React and TypeScript single-page site using `viem` for chain reads. It shows live supply, the AMBI price in IMD from the Uniswap v4 StateView, market cap and pool fee, a balance lookup for any address, a wallet panel (connect over EIP-6963, switch to mainnet, add AMBI to the wallet, send AMBI with inline validation and receipt tracking), a Uniswap swap link, and the pool key, hook and contract addresses with copy and Etherscan actions.\n- `dist/` is the committed production export with relative asset URLs, a title, description, favicon, og:title and an absolute og:image. The ABI is the Foundry build of the deployed commit and its canonical hash matches the launch record.\n- `DESIGN.md` documents the implemented tokens, type scale, layout primitives, components and responsive behavior. The root README gained a Website section covering install, preview, rebuild, publish and the checks that ran.\n- `artifacts/validation.md` and `artifacts/screenshots/` hold the Better Interface review and browser evidence. That directory is excluded from Git by the workspace's own exclude rule, so it ships as an artifact rather than in the commit.\n\n**Verification results**\n\n| Check | Result |\n| --- | --- |\n| Typecheck | clean |\n| Unit tests | 5 of 5 pass |\n| ABI hash vs launch record | match |\n| Production build | ok, JS 542 kB (167 kB gzip) |\n| Playwright run on dist served at a subpath | no console errors, no failed requests, no overflow at 320 to 1536, lookup, copy, nav and mock-wallet flows pass |\n| Rendered contrast pairs | all text 6.0:1 or better |\n\nThe review fixed a squashed logo at 320px, a missing solid fallback for the sticky header, small link hit areas, and a font stack that named an unshipped font.\n\n**Limitations recorded**\n\nSwapping is an outbound link to the Uniswap app, not an in-page swap. Checks not performed include a real wallet extension, a real transfer, screen readers, native zoom, forced colors, and Safari or Firefox rendering. The git bundle is about 0.8 MB, well under the limit. Node modules were removed after the commit, so a rebuild starts with `npm ci` in `web/`.","treeHash":"e2f933ba442e3b7d44b9840bbcda7f75cac95718","usage":{"cachedInputTokens":3417674,"inputTokens":900,"model":"claude-fable-5-1","outputTokens":91075,"runtime":"claude","turns":82,"wallClockMs":1244535}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"8eebc53449bafe7b","findings":[],"hash":"a73e333556d554dd73ed5764aec993122575769454d9d742a7afebbdebeaff7c","nodeId":"ee29fcb8-59b8-4a87-baa5-4cd0e0ef6a05","outcome":"completed","summary":"| Area | Last review | This review |\n|---|---|---|\n| Brief requirements (met / missing / faked) | 4 / 1 / 0 | 5 / 0 / 0 |\n| Broken features | 1 | 0 |\n| Product on the first screen at 390 | yes | yes |\n| taste@1 checks passed | 13 of 16 | 15 of 16 (the one failure is a probe artefact, see below) |\n| Copy findings | 2 | 0 |\n| Share card | image URL 404 | title, favicon, og:title, og:image, description present; image relative to the export |\n\n**Verdict:** all seven findings from the last round are fixed. I found nothing new that reproduces, so `.imd-findings.json` holds an empty list. No file in the tree was changed; my scratch script and screenshots live only in `test/scratch/`.\n\n**Requirements**\n\n- \"Token name: AMBIDEX\", \"symbol: AMBI\", \"1,000,000,000 with 18 decimals\": met. The TOKEN record in `web/src/config.ts` matches deployment.json, and the live tiles read 1B AMBI, 0.000002548 IMD, 1.25% fee from the StateView contract.\n- \"use the verified addresses in deployment.json and the chain facts in network.json\": met. Token 0x81b5…6941, hook 0x784f…6000, pair IMD 0xd34a…63b7, chain id 1, pool key fee 12500 and tick spacing 60, all in config.ts.\n- \"read their state and offer their main actions through the visitor's wallet\": met. Live stats, balance lookup, connect, add to wallet, send and swap link, verified with a mock EIP-6963 wallet at 390.\n- \"Never hard-code an address that is not in deployment.json\": met. The only extra addresses are the Uniswap v4 core contracts from network.json.\n- Publish at the named domain: no longer claimed. README line 6, index.html and config.ts contain no reference to ambidex.trade.\n\n**Idea:** a one-line ledger page for a fixed-supply token, carried by one indigo accent on a plain white sheet.\n\n**Settling the prior findings**\n\n1. **Fidelity, ambidex.trade serves another product (high): fixed as offered.** The domain still serves \"AMBIDEX | Trade both ways\" on Robinhood Chain and returns 404 for og.png, favicon.svg and token.png, so the author took the second option in my finding and removed every claim to it. Grep over web/, dist/ and README finds no ambidex.trade except the README sentence that says it is not this site. The vite plugin adds canonical, og:url and an absolute image only when VITE_SITE_URL is supplied. One note for the requester, not a finding against the repo: the review task still names https://ambidex.trade/ as the site, and that URL is not and cannot be made this export from inside the repository.\n2. **Bug, header overflow at 320 (high): fixed.** Chrome at 320×844: scrollWidth 320, brand name display none, aria-label \"AMBIDEX home\" kept, no element past the viewport. Probe layout line: \"no horizontal scroll at 320, 390 or 1440\". Screenshot shows the logo tile and \"Live Trade Contract\" on one row.\n3. **Bug, share image and wallet icon 404 (medium): fixed.** og:image and twitter:image are `./og.png`, which exists in dist/. The mock wallet received image `http://127.0.0.1:<port>/preview/token.png` and fetching it returned 200, so the relative resolution works from a subpath.\n4. **Taste, five font weights (medium): fixed.** Probe type-faces line: \"weights (3): 400, 600, 700\". My own scan at all three widths agrees. dist CSS contains only font-weight 600 and 700 declarations.\n5. **Copy, 63 words on the first phone screen (medium): fixed.** Probe copy-budget line: \"49 words at 390 (bar 60), 55 at 1440 (bar 90)\". No `.card__footnote` element remains in the DOM or the CSS.\n6. **Bug, Refresh had no pending state (low): fixed.** With publicnode and drpc delayed 6 s at 1440, 1.5 s after clicking Refresh the line read \"Reading the chain… · Refresh\", the button carried aria-disabled=\"true\", kept keyboard focus, the section was aria-busy, and the tile still showed \"1B AMBI\". Afterwards it returned to \"Updated 02:32 PM · Refresh\" with aria-disabled removed. The aria-disabled choice over native disabled is sound.\n7. **Accessibility, timestamp live region (low): fixed.** The only role=\"status\" regi","treeHash":null,"usage":{"cachedInputTokens":1673320,"inputTokens":738,"model":"claude-fable-5-1","outputTokens":19312,"runtime":"claude","turns":24,"wallClockMs":321806}}],"verification":[{"checks":[],"detail":"paths and tree verified; no suite was run for this kind of work","evaluation":"structural","profile":"none","status":"accepted","submissionHash":"21e9df3282863b784ab161ed588fc66a9650856162560a5f4a55e643f4247ab8","verifiedTreeHash":"7a0a857c7182e81326bf59bf687b95cc69743feb","verifierVersion":"0.1.0+fb8278c5"},{"checks":[],"detail":"paths and tree verified; no suite was run for this kind of work","evaluation":"structural","profile":"none","status":"accepted","submissionHash":"a4a901fe2b161eb13e50eede9ccf6d942f67600a5ad3bd7cc8a8199d3e3d366e","verifiedTreeHash":"e2f933ba442e3b7d44b9840bbcda7f75cac95718","verifierVersion":"0.1.0+fb8278c5"}]}