Skip to main content

Session — Bug fixing, one at a time, on master

What this session is for

The interface was rebuilt to the approved design on 21 Sep and pushed to master. It is now on staging and being used, and use is turning up bugs. This thread takes them one at a time: the developer names one, it gets fixed, proved and pushed, and then the next one.

The list below is what has been caught so far and it is incomplete on purpose — more will arrive during the session. Do not work ahead of it. Do not start a sweep.

How this thread works

  • Directly on master. No branch. Staging rebuilds from it, so a fix is live a minute after it is pushed and can be confirmed in the browser the developer already has open.
  • One bug per commit, in the house convention. A commit that fixes two is a commit that cannot be reverted when one of them turns out wrong.
  • The gate is make fmt-app, yarn lint and make build-app-testnet — the app half of make check, about fifteen seconds. Run the full make check before a push that touches contracts/, services/ or supabase/, and not otherwise: the contract suite is minutes and this thread is not going to touch it.
  • Ask before redesigning anything. Several items below are "this looks bad". That is a brief for a conversation, not a licence to rewrite a screen.

The redeploy path

make fmt-app && (cd app && yarn lint) && make build-app-testnet # the gate
git commit ... && git push origin master # staging follows

Vercel, in stealthy-town: stadium-app builds app/ and stadium-docs builds docs/. The branch picks the chain (D30) — master421614, Arbitrum Sepolia; prod → Arbitrum One, and prod does not exist yet. Each project's ignoreCommand skips the build when its own directory did not change, so an app/-only commit does not rebuild the docs.

Do not watch the deployment. Push and carry on; the developer will say if it did not land.

What must still work

Each of these was found the hard way and none of them is obvious from the code.

  • The trade panel quotes by simulating the real call (app/src/chain/trade.ts). It is not a formula and must not become one — a module runs inside the trade and the curve cannot report it from a view.
  • The gas floor. sendRaw hands over 3,000,000 because BondingCurve, LaunchHook and LaunchToken each check gasleft() and revert when short, and the floors are sequential. Do not tidy it into an estimate. A limit is a ceiling, not a price.
  • Every write names chain.id (app/src/chain/network.ts and the four call sites). Without it wagmi builds for whichever chain the wallet is pointed at, which is how a launch on Sepolia opened an Ethereum mainnet confirmation. Keep the chainId on anything new that writes.
  • The wallet picker offers the EIP-6963-discovered wallets. connectors[0] reaches whichever extension won the injection race, which is how MetaMask was unreachable.
  • The faucet appears only where the quote asset will actually mint, decided by simulating it.
  • The ETH pay option appears only where a v3 route exists. On Sepolia it never does.
  • The deadline comes from the chain's clock, not the browser's.
  • lightweight-charts parses colours itself and knows nothing of color-mix() — it throws from inside its own render loop, so the symptom is a 300x150 canvas in the corner of a correctly sized box. PriceChart resolves tokens to rgba() first; keep that.

The list so far

The developer's words, with what is already known about each underneath. Unordered within a band — they will pick.

High

  • Connect wallet flow, missing "profile" section. There is no profile anywhere today. A wallet connects, and /portfolio is the only thing that is "yours". Needs a conversation about what a profile is here before anything is built — a public page at /portfolio/:wallet already exists and is nearly one.
  • Seed data so it is easier to debug. make dev seeds 305 launches; make dev-curate adds the podium and endorsements. What is missing is a seeded wallet — positions, activity and a watchlist for the account the developer actually connects with.
  • Cannot launch a token, wrong network selected — replace with RainbowKit if possible. The wrong-network half is fixed (f42c616): every write names its chain, and the frame carries a banner with a switch button. Confirm that before doing anything else. RainbowKit is a separate question and has a trap waiting: Yarn 1 hoists a workspace's dependency to the root with its own copy of a peer, and RainbowKit landing beside a second wagmi gives two React contexts and a blank page. resolve.dedupe in the Vite config is the fix, and node_modules/.vite has to be cleared after the install. Weigh it against what the current picker already does right (EIP-6963, no WalletConnect project id, no relay).
  • Watchlist: "The signature was not given, so the list stays closed." Sign-in is broken on staging by design — see issue #3 — and the watchlist depends on it. Establish whether this is that known breakage or a second thing on top of it before changing any code.
  • Go over the FAQ text and verify it against the contracts. app/src/pages/Faq.tsx. Some of it reads live from the factory; the prose around it does not. Check each claim against contracts/src, not against notes/.
  • Add a "Launch token" button as the first item in the sidebar. Today it is in the top bar and hidden on the front page, where the hero carries it. app/src/shell/Shell.tsx.

Mid

  • Portfolio should always show something, not "Nothing held" — and a public username. A username is storage, a write path and a uniqueness rule; it is a feature, not a fix.
  • The chart could be nicer. app/src/charts/PriceChart.tsx. Read the trap above first.
  • Basic metadata and favicon. app/index.html has a title and nothing else: no description, no Open Graph, no icon. Cheapest item on the list.
  • The hero image is bad on the light theme. It is drained to mix-blend-mode: luminosity at 30% because the photograph is a dark block on cream. .hero-field in app/src/pages/discover.css.
  • Sidebar issue. Not yet specific — get the developer to say what they are seeing.
  • Content display issue. Same.
  • The lab page looks bad, could use art. Known and expected: the mechanic cards have a slot for an illustration and no illustrations exist. The approved pack builds its versions out of fifty class names of divs under 3D transforms rather than shipping files, so ours have to be drawn. app/src/assets/mechanics/README.md is the contract — drop an SVG in and it appears.

Hanging over all of it

  • "New design overhaul, current one is bad." Said after the interface was matched to the approved pack, so this is about the pack rather than about the match. Do not act on it inside this thread. It is a design conversation and it wants its own.

Low

  • Copy on the website. Text is bad in places; no list yet.
  • Deploy the docs to the docs. subdomain. Done, 21 Sep. Live at docs.stadium.money: the domain is on the stadium-docs project and Namecheap carries a docs CNAME to bb6b601a96915cb8.vercel-dns-016.com. plus a second _vercel TXT beside the existing stage one. docusaurus.config.ts names the domain so canonical links and the sitemap are right. One loose end: /internal 404s there, because the project builds with the framework default rather than docs/scripts/vercel-build.sh — so DOCS_INTERNAL=1 is never set and the branch split D30 describes is not wired up. It lands where it should by accident. Worth fixing the config so it is on purpose, and stage.stadium.money already exists for the app.

What the last thread left behind

notes/sessions/redesign.md is the full record. The parts that matter here:

  • Read the design by measuring it, never from its stylesheets. The pack is a dozen last-wins layers and what they say a colour is and what the browser paints are different things. Serving a copy of the compiled design same-origin and reading getComputedStyle is how the palette, the type and the glass were finally got right, after a first attempt from the CSS got all three wrong. The values are in app/src/theme/tokens.css with that noted.
  • Five things were removed because the design has no slot for them: the front page's totals, its trade ticker and its "Chosen by us" strip, the catalogue's 305 of 305 · indexed source line, and the lab's essay. A sixth — the token page's mechanics disclosure — was removed and then put back, because the public documentation promises it in four places. data/discover.ts still exports useCuration and useRecentTrades with no caller.
  • Responsive is measured, not eyeballed: documentElement.scrollWidth against clientWidth per route in a same-origin iframe of a fixed width. Chrome will not resize below its minimum and page zoom decouples innerWidth, so the iframe is the only honest way. Every route was clean at 320px when the thread closed.

Done in this thread

  • da69a6c — the buy that could not be quoted. Not a contract fault and not the wrong network. Arbitrum's own sepolia-rollup.arbitrum.io/rpc answers -32603 method handler crashed to eth_simulateV1 for any non-empty call list, while eth_call on the same call is fine. The quote is a simulation, so every quote failed: viem printed "An internal error was received" and the panel fell through to "This trade would fail". The CORS *,* and the 429s were downstream — a keystroke burst of doomed simulations trips the rate limiter, and its 429 duplicates the header. Proved by running the app's own approve-plus-buy bundle against three endpoints. publicnode and drpc both answer it properly; arb-sepolia.g.alchemy.com does too. The endpoint is now VITE_RPC_URL, falling back to publicnode, so a keyed one can be set in Vercel rather than committed. It still ships in the bundle — it is only out of git and rotatable.
  • 4f49427 — the quote no longer runs per keystroke. 350ms of settling, applied at once when the field is emptied. The send takes the same trade object the quote was for, because a minOut measured for a different size is slippage protection for a trade nobody made.
  • 6f003d5, b66ab0c — the edge functions' preflight refused the header the client sends. Saving launch metadata, and signing in, both failed as net::ERR_FAILED / "Failed to fetch". The client sends Authorization: Bearer <publishable key>; that header is not simple, so the browser preflights, and both functions answered Access-Control-Allow-Headers: content-type. Chrome refuses a preflight that does not name every header asked for, so the POST was never sent. The functions were healthy throughout -- a curl POST past the browser, with no auth header at all, reached the metadata function's signature check. Both now answer Supabase's documented list, because supabase-js adds apikey and x-client-info of its own accord. verify_jwt = false for both: the header is not checked, it only has to be allowed.

Correction to issue #3

Sign-in completes. Driven end to end against staging with a throwaway wallet once the preflight was fixed: /auth/nonce returns 200 and a message, /auth/verify returns 200 and an HS256 token. So jwtSecret() did not throw, which means a symmetric secret is reachable from the function.

The likely reason the issue's premise does not bite: auth/index.ts reads SUPABASE_JWT_SECRET first, and Supabase injects that into edge functions itself. The issue is right that the CLI will not reveal the secret; it does not follow that the function cannot read it.

PostgREST accepts the token. Confirmed in the browser on 21 Sep from 0x334C16436e9F3e789cA35829CA012cF4849885B6: a star filled, stuck, and the launch appeared on the watchlist -- a write and a read back through the RLS policies. So issue #3 does not bite today and its premise is wrong: the CLI will not reveal the JWT secret, but Supabase injects it into edge functions as SUPABASE_JWT_SECRET, which is the name auth/index.ts reads first.

What remains true is the direction: the legacy symmetric secret is being retired, so this works on borrowed time. signInWithWeb3({ chain: "ethereum", message, signature }) exists in the @supabase/supabase-js@2.116.0 already installed, takes a message and signature directly (so wagmi keeps choosing the wallet, not window.ethereum), and has Supabase mint the session itself -- deleting our auth function, the JWT_SECRET and the custom wallet claim. Not urgent, now that nothing is broken.

Why sign-in looked broken, and was not

Three separate faults stacked, in this order, each hiding the next:

  1. The preflight refused Authorization, so no request left the browser (b66ab0c).
  2. useSession was a plain hook, so the session never reached the code that writes (c55c46f).
  3. Issue #3 -- which turned out not to be a fault at all.

Still open in this thread

  • The Alchemy key is not set anywhere yet. Until VITE_RPC_URL is added to stadium-app in Vercel (all environments) staging runs on publicnode, which works. Redeploy after setting it — Vite reads it at build time, not at run time.
  • Quotes still run for amounts the wallet cannot afford. short is known locally from the balance, so those simulations are free waste. Two lines, not done: it is a different bug from the keystroke spam and wants its own commit.