Skip to content

Releases: Aaron-Wickham/dwell-duel

DwellDuel v1.2.0

Choose a tag to compare

@Aaron-Wickham Aaron-Wickham released this 26 Oct 18:04
d2a57c0

Every screen got a careful once-over. The current tab on your phone is now a bright lime tile, No and Under are no longer red, and on a phone a market's Add buttons come before its chart. The slip keeps Place in view and says plainly why a pick was refused; busy buttons say what they're doing ("Placing…"); amounts never split across lines; and light mode's cards stand out from the page. Tasks shows the whole task before you submit and keeps a link you pasted; notification choices save as you tick them; the leaderboard's This month tab shows where you stand. Reviewers get a five-second Undo on Approve, and the review queue reads on a phone. Nothing about how bets, payouts or tasks work has changed.

Features

  • Undo a single approval. A submission's own Approve in Admin › Tasks now waits five seconds, showing Approving… with Undo (and Undo in a message too), before it pays, and sits out Approve selected meanwhile. Leaving or hiding the page in that time approves it at once; only a page killed outright can lose it, leaving the submission waiting, never paid twice. Approve selected still asks first (#515).

Fixes

  • Place stays in view in the slip. The Place button, what's holding it back and "Bets are final" are pinned to the bottom of the slip while you scroll and type stakes. When a pick is refused, a line above Place says how many ("1 pick’s price moved. Check it above, then tap Place again.") and the slip scrolls to it (#511).
  • Resolve errors in plain words. A refused resolve now says what happened and what to do ("This market hasn’t closed yet. Resolve it once betting closes.") instead of database text; anything unexpected shows the general error (#511).
  • Editing a market others have bet on. Its title now shows read-only with the reason, so a description or category change saves instead of being refused along with the title; if someone bets while the dialog is open, it says to refresh for the latest title (#511).
  • Create market errors sit by their field, and focus moves to it; an over/under's line is checked for the .5 as soon as you leave the field (#511).
  • Over/Under buttons keep their chance. On a market card the percentage on an Over or Under button no longer gets cut off; only a long label does (#511).
  • Adding a pick from a card no longer greys the button out while the slip saves: it shows In slip at once (#511).
  • Quick picks make sense to VoiceOver. Each button is named for its outcome, chance and market, and the same whether or not it's in the slip, which it reports as pressed (#511).
  • A bettor's name on a market is easier to tap, and their amount stays on one line (#511).
  • The markets list's loading screen holds room for the quick picks, so cards don't grow when the page lands (#511).
  • Menus show where the keyboard is. In the avatar's menu and a market's More actions, the item you reach with the arrow keys now takes a tint and a focus ring, instead of a grey tint you could barely see (#510).
  • Placeholders you can read. The hint text inside search boxes and form fields (Search markets, the create-market form) is now the secondary text colour instead of a faded grey (#510).
  • Amounts stay on one line. "12,345 DC" no longer breaks with "DC" on the next line on bet cards, parlays, Home and a market's bets (#510).
  • Printing in dark mode. A market or the rules printed from the dark theme now prints dark text on white instead of near-white text (#510).
  • Text in cards can be selected with a mouse. On a computer you can now select and copy the words on a tappable card; on a phone a long press still doesn't select it (#510).
  • A link you typed goes with your task. Tapping Submit for review with a link still in the field now sends it with the rest, instead of dropping it; a link that can't go asks you to add it or clear it first (#514).
  • Upload problems say what to do. A photo or file that doesn't upload now says why in plain words (today's upload limit of 30 files or 60 MB, a file too big or of the wrong type, or a connection problem) instead of Storage's own message (#514).
  • Notification choices save as you tick them, like the rest of Settings, and put the box back with a message if the save fails. The Save choices button is gone (#514).
  • Blocked notifications offer Check again, so the installed app, which has no reload button, can see that you've allowed them (#514).
  • The full rules' contents list scrolls on a laptop screen, so its last sections can be reached (#514).
  • Removing an attachment keeps your place. Focus moves to the next Remove button (or Add photos) instead of the page behind the dialog (#514).
  • A way back from a missing page. "Page not found" on a market, member or parlay now also offers Back to Markets (or Leaderboard, My bets), and the top-level error and not-found pages have a main landmark for screen readers (#510).
  • "No real money" on every iPhone. On a short screen, which is most Safari tabs, the sign-in page now keeps one line, "Play money only. No real money.", and the sample card drops its bet line to make room, so the page still doesn't scroll (#516).
  • Screen readers reach the heading first on sign-in and Not invited, and skip the dimmed sample market on Not invited (#516).
  • Privacy no longer sends you to sign in. Its mentions of other parts of the rules (Roles, Tasks) are plain words, since those pages are for members (#516).
  • This month shows where you stand. The leaderboard's This month tab now has the sticky You bar on a phone and Your standing beside the rankings on a computer, and Jump to me stays on This month instead of switching to Net worth (#513).
  • A refunded parlay stops quoting odds. Its page says "Refunded" over the stake and "Every pick was called off", without a payout that will never happen (#513).
  • A called-off bet says so. My bets reads " · Called off · Refunded", and a market's Your position chip says "Called off" (#513).
  • A new photo says it isn't saved yet. The profile form says "Save profile to keep this photo." until you save (#513).
  • Your own reaction stands out by more than colour, with a thicker edge and dark text (#513).
  • The leaderboard doesn't jump while This month loads: each tab has its own loading skeleton (#513).
  • One failed read no longer blanks Home. If the weekly recap, Getting started, Needs you, Activity or your open-bet total can't load, Home shows everything else without it (Activity says it didn't load, in its usual space) (#512).
  • Needs you lands with the page. It no longer pops in after Home has drawn, so your bet cards don't jump down under your thumb on a phone (#512).
  • Getting started holds still. Its "2 of 5 done" and the notifications step wait for the device check instead of flipping to "3 of 5" a moment after the page opens (#512).
  • Back swipe follows your finger with reduced motion on. The page now moves with the swipe; only the slide after you let go is skipped (#512).
  • The economy check says what to do. When the owner's Economy card doesn't reconcile, it now says not to adjust balances to cover it and links the incident steps in the Operations guide (#515).
  • Closing-alerts warning for the right person. Reviewers and admins are told members aren't affected and to tell the owner; only the owner gets the Supabase steps (#515).
  • Admin Tasks and Ledger no longer jump on load. The skeleton draws only what everyone gets; Create task and the task catalog stream in below the queue, and the owner's Economy card streams in behind a skeleton of its own size (#515).
  • Adjusting a balance says the result. A hint says to use a minus to take coins away, a reason is required before it's sent, and the confirmation names the balance it ends at and the reason (#515).

Polish

  • The review queue reads on a phone. Approve and Reject… have a line of their own under each submission, so the member's name and "Nothing attached" no longer wrap a word a line (#518).
  • No and Under are no longer red. Both sides of a yes/no or over/under market take the same neutral chip and button, on market cards, My bets, Home and Your position; red is kept for a lost bet. Only a result is coloured (#511).
  • On a phone, a market's picks come first. While a market takes bets, its leading outcomes and their Add buttons sit right under the title, and the chart under them is shorter (160px), so you can pick without scrolling. Desktop is unchanged (#511).
  • Busy buttons say what they're doing: "Placing…" in the slip, "Creating…" on Create market, "Resolving…" and "Calling off…" in their confirm dialogs (#511).
  • The slip's balance reads "Balance 120 DC", and a pick that's gone is marked "No longer available" in grey rather than the lost-bet red (#511).
  • Empty Waiting and Resolved tabs offer "See open markets" instead of Create market (#511).
  • Clearer words in the slip ("Your slip is empty. Add a pick from a market first.", the parlay pick limits, and a failed place now says nothing was placed), the chart ("Chance of Yes"), Your position ("Waiting for a result."), Duplicate and the category picker; the empty-chart note on a card is the 13px chip size (#511).
  • Cards stand out from the page in light mode. The light page background is a step darker, so cards on Home and My bets read as cards on a sunny phone screen; hover tints moved with it (#510).
  • Waiting counts are gold, not red. The avatar's dot, the Admin tabs' badges and the menu's "3 waiting" use the same gold as Needs you, since something waiting isn't an error (#510).
  • Menus, dialogs and toasts have an edge in dark mode: their border brightens and the shadow deepens, so they no longer melt into the card under them (#510).
  • Error toasts stay 10 seconds, long enough to read what to do next; other toasts still go after 4 (#510).
  • Busy buttons can say what they're doing. Su...
Read more

DwellDuel v1.1.0

Choose a tag to compare

@Aaron-Wickham Aaron-Wickham released this 24 Oct 01:41
9ca796f

Market cards now show every answer, each with its own Add button, so you can put any pick in your slip straight from the list, and categories are one searchable list on Markets and when you create or edit a market. Behind the scenes the app moves to Node 24 and up-to-date libraries, and Safari's skipped page animations no longer show up as errors. Nothing about how you play changes.

Fixes

  • No more Sentry reports for Safari's skipped page transitions. Safari words a view transition it drops for a resized viewport differently from Chrome ("…invalid state. Viewport size changed"), so the filter missed it (DWELL-DUEL-G). The page still loaded; only the animation was dropped (#501).

Polish

  • Every answer on a market card, each with an Add button. A multiple-choice card lists all its answers, likeliest first, instead of the top two and "+4 more"; each row ends in Add (In slip once it's there), and under a mouse the pick you point at rings and fills while the card stays still, so it's clear a tap adds it. Yes and No get a plus and the same ring (#497).
  • Every category in one list. Markets' row of category chips and its More… dialog are now one "Category: All" button that opens every category with a search, busiest first, each saying how many markets are open, and the quiet ones under "Nothing open". Create and Edit market choose a category from the same list, with a row to create a new one, in place of the typed field and its six chips (#496).

Under the hood

  • Node 24. The app, CI and Vercel move from Node 22 to Node 24, the current long-term-support line: engines.node, .nvmrc and @types/node move together, so Vercel builds on the version CI tests (#502).
  • The Supabase advisors' standing findings are written down. OPERATIONS.md lists each one we keep and why, and what Query Performance's top entries are, so a new finding stands out (#501).
  • Dependency updates from Dependabot: motion 14, Next 16.4.2, React 19.3.1, Sentry 11.8, supabase-js 2.118, Base UI 1.9, lucide-react 1.60, Vitest 5.0.5, and the setup-node, upload-artifact and Claude Code actions (#436, #483, #434, #503, #504).

Full changelog: CHANGELOG.md

DwellDuel v1.0.0

Choose a tag to compare

@Aaron-Wickham Aaron-Wickham released this 17 Oct 23:24
e6a12f5

DwellDuel is out of beta. Nothing about how you play changes: this release takes the Beta label off the sign-in page and the terms, and brings every guide up to date with the app as it is after v0.11.0's refresh. Versions from here are plain v1.x.

Polish

  • No more beta. The Beta badge beside the wordmark on sign-in is gone, the terms' "A beta" section is now "As it is", and Send feedback's email subject is "DwellDuel feedback".
  • A capital after the dot on a market page. Its meta line reads "Closes Sun 12:00 PM · By Ben Turner" (or "By you").

Under the hood

  • The security review runs on a Claude subscription, not paid API credit. It uses claude-code-action with a CLAUDE_CODE_OAUTH_TOKEN from claude setup-token, reviewing only for security and keeping one comment that it updates on each push (#451, #494).
  • Every doc checked against the code. How it works (the slip's 10-pick and one-per-market limits, the stake and payout ceilings, when admins see a closed market, comments above bets), the admin guide (where Admin opens, the call-off steps, the override message, member sorting, the ledger's name box), Architecture, Security, Operations, the design handoff, AGENTS.md, the README and Getting started all match what shipped.
  • Releases drop the -beta suffix (docs/RELEASING.md).

Full changelog: CHANGELOG.md

DwellDuel v0.11.0-beta

Choose a tag to compare

@Aaron-Wickham Aaron-Wickham released this 17 Oct 22:12
6aad045

DwellDuel's biggest refresh yet. Home leads with your balance and your bets as cards, you can add a pick to your slip straight from a market's card, and a market's page opens on its chart. Cards lose their outlines, the tab bar is shorter, and colour now means something: blue for results and things of yours, and a soft colour of its own for each category and member. Links are no longer underlined unless they're in a sentence, and the words are plainer throughout, with no dashes and a capital after every dot. Find any member from the leaderboard's search, and tap the tab you're on to jump back to the top. Behind it: sign-in now needs a Google account, each member can place up to 200 bets and 100 parlays a day, a stricter Content Security Policy, every page has its own title, and the leaderboard is read once for everyone.

Security

  • A daily cap on bets. Each member can place up to 200 solo bets and 100 parlays a day, far above an honest day's betting, so a stolen session can't flood the market (0114, #450). The slip says so in plain words.
  • Only Google accounts get in. The invite check now also requires a Google sign-in on the account, so turning on another sign-in method could never let someone claim an invitee's place (0112).
  • Inline script needs a nonce now. The Content Security Policy moves from next.config.ts to the proxy, which makes a fresh nonce for every request; Next puts it on its own scripts, and the app's three pre-paint scripts (the launch screen, the sign-in intro and the standalone zoom lock) are allowed by their hash. script-src no longer has 'unsafe-inline', so an injected inline script wouldn't run. Every page was already rendered per request, so none changes how it renders (#449).
  • A Claude security review on pull requests. A new Security review workflow runs Anthropic's claude-code-security-review on same-repo pull requests and comments what it finds. It's advisory, outside ci-ok, and skips until the ANTHROPIC_API_KEY repository secret is added (#451).
  • Next 16.4.0, and two dependency advisories patched. Next moves from 16.3.8 to 16.4.0, and the lockfile takes sharp 0.35.5 and source-map-js 1.2.2, so npm audit --omit=dev is clean again (#448).

Features

  • Find a member on the leaderboard. A search box beside the tabs lists everyone whose name contains what you type, with their rank and net worth, each linking to their page (#465).
  • Tap the tab you're on to go back to the top. In the tab bar or the nav on a computer, tapping the page you're already on scrolls it to the top, as other apps do, instead of reloading it (#464).
  • A terms page. Short and plain: play money with no cash value, invite only, admins' calls are final, be kind, it's a beta. Linked from sign-in and Settings (#462).
  • Add to your slip from Markets. Each open market's card has its picks right on it: Yes and No side by side, or a multiple-choice market's two favourites. Tap one to add it, tap again to take it out (#439).
  • A Home you can read at a glance. Your balance leads in a deep teal card on a phone too, what needs you shows as two coloured counts, and your bets are cards with the pick in its colour and "Waiting for a result" once a market closes (#438).
  • The chart leads the market page. It comes first, then a small blue strip with your position, then the outcomes, comments and bets. When a market is waiting for a result you can give, Resolve still comes first (#440).
  • Home is a tab, and Admin moves into your avatar. The tabs are Home, Markets, Bets, Tasks and Leaders; the feed becomes Home's Activity, and its See all opens it. The top bar keeps the wordmark, your balance (as plain "4,886 DC", no coin icon) and your avatar, which now opens a menu: your profile, Settings, Admin for reviewers and up, and Send feedback. When something waits on you, the avatar shows a red dot and the menu says how many. Admin opens on the section with work in it, and its tabs scroll sideways on a narrow phone instead of cutting off Ledger. The BETA sticker stays on the sign-in page only (#385).
  • Market cards say where each market stands at a glance. Each card leads with the favourite's chance (Yes's, on a yes/no market), how far it moved this week ("▲ 8 this week", with an arrow as well as a colour), a cleaner chart, and when it closes with how many bets it has ("Closes Sun 12:00 PM · 42 bets"); a multiple-choice card adds a one-line legend of the top three. A resolved card says "Yes won". The status, closing-soon and Over/Under chips are gone, and Yes now comes before No (and Over before Under) on cards and market pages. The filters are one row: Open, Waiting and Resolved tabs (Open by default, no All), with the search beside them (behind a search button on a phone), which looks in every status; category chips show only when there's more than one category, beside the tabs on a computer and behind the filter button with the search on a phone, so the first card starts higher. Show more says "Loading…" while it works, and off-screen cards skip rendering until you scroll near (#389).
  • The market page puts the bet first. The outcomes come first: each shows its chance, what 10 DC on it would win ("10 DC wins 16", the same figure the slip shows) and an Add button. Then the chart ("Yes over time" on a yes/no market), Your position, comments, and the latest 10 bets with Show more. On a computer the outcomes and Your position stay beside the chart as you scroll. Share, Duplicate, Edit, Reopen and Edit history move into a ⋯ menu, and the header is just the question and one line ("Closes Sun 12:00 PM · by Ben Turner"). A resolved market says "Yes won" once, at the top of the outcomes, and a called-off one says what happened to stakes. Whoever resolves a closed market finds Resolve right after the outcomes on a phone, before the chart, with Call off market in its own, quieter card below, its form one more tap away. The page no longer shifts as its sections load. Gone: the Place a bet and No more bets cards, the DC figure and multiplier beside each chance, and the long parlay footnote (now one line, "Includes 14 DC riding in parlays"). Create market asks for the question first, then the type, outcomes, close time, category and optional details, with shorter hints (#390).
  • A new Home. "Hi, Ruth", with your rank (once you've had a bet settled) and what's riding; then Needs you, only when something does (submissions to review, markets to resolve, or a nudge to Tasks at 0 DC); Getting started for newcomers only; your three bets closing soonest (or the markets closing soonest, if you have none); and the latest activity. On a computer, your balance and Needs you sit beside them. The tiles, the balance hero and the Get the app card are gone; Settings says how to install the app, and Send feedback is in the avatar menu. Home's loading placeholder is drawn at the real sections' heights, so nothing jumps when it arrives (#388).
  • My bets says what each bet pays. An open bet reads "10 DC on Yes · pays 18 DC", with the day its market closes on the right, and one waiting on its result says so in the same line; a settled bet says "won 18 DC", "lost" or "refunded" instead of a chip. A parlay's card reads "5 DC · pays 80 DC if all win" over its picks, each with Won, Lost, Waiting or Called off beside it. On a computer bets sit two to a row, the cards in a row one height so none leaves a gap, with parlays across both. A lost parlay's page no longer sits on the green panel or says "Lost" three times: it says Lost once, what it would have paid, and "Paid: Nothing"; the multiplier reads "pays 16.00× your stake". Card and row titles are no longer underlined until you point at them (#393).
  • How it works starts with the short version, and Settings is one column. It's now four plain paragraphs (Betting, Parlays, Earning DC, When a market ends) and four questions that open in place (what if a market is called off, why the chance changed, can I take a bet back, how parlays pay), then Read the full rules, which opens the whole rulebook as plain sections under headings, with a contents list on a computer that marks the section you're reading. The slip's How parlays pay, Settings' Your data and the privacy page link straight to their section of the full rules. Settings is calmer too: one column of Appearance & motion (theme, vibration and animations, marked "On this device"), Notifications (turned on per device; what you get follows your account), Install the app on a phone that hasn't, Help, and Account with your profile and Sign out; where a device can't get notifications (the app isn't set up for them, the browser can't, an iPhone hasn't added DwellDuel to the Home Screen, or they're blocked), the choices are greyed out with one line saying why. Settings' loading placeholder is the page's real height (#398).
  • A faster review queue for admins. Admin › Tasks puts one bar at the top, which stays in view as you scroll: Select all, how many are selected and what they pay, Reject selected… and Approve selected (which still asks first, saying how many and how much). On a computer the queue is a table (member, task and reward, proof, when it was sent, Approve); on a phone each submission is one compact row. A note and proof open from the row. Reject, for one submission or several, asks for one optional reason in a small window instead of a reason box on every row. Admin › Members is a table on a computer too (name and email, role, balance, net worth, when they joined) and plain rows on a phone, and a member's Admin page shows who they are and their coin history before the owner's controls, with removing them last (#399).
  • Sort members, filter the ledger, and review more at once on a phone. Admin › Members sorts by balance, net worth or when they joined, highest or newest first, or the other way with a second tap; the order is in the link and survives a search or a tab ...
Read more

DwellDuel v0.10.1-beta

Choose a tag to compare

@Aaron-Wickham Aaron-Wickham released this 04 Oct 21:42
2017616

A fixes release. The sign-in page fits a phone with the familiar Sign in with Google button, Create market's closing-time hint tells the truth about moving it, and the daily keep-alive that stops the database pausing passes again. Behind the scenes, Sentry now gets readable stack traces, the docs were reviewed end to end, and Claude Code works in the repo behind guard rails that keep production changes going through pull requests.

Fixes

  • The daily keep-alive cron passes again. Its idempotency-key cleanup had failed every run in production with "permission denied", because the production database never gave the server read and write rights on three tables that local databases do. The server can now read and write every table there, as it does locally, and new tables get the same rights (0109, #362).
  • The sign-in page doesn't scroll on a phone. The paragraph under the headline is gone, and a phone too short for the rest drops the three facts, so the whole page fits on one screen (#366).
  • Create market's closing-time hint is right again. It said the closing time can't change later; it can be moved while the market is open, unless you bet on it, and only the outcomes (and an Over/Under's line) are fixed (#371).
  • My bets' Cancelled tab explains itself. Its empty state said "Bets you cancel…", but bets are final; it now says the tab keeps bets cancelled before October 2026 (#372).
  • Create market's Type heading has room to breathe. It sits as far above its options as Title does above its box, and Outcomes matches too (#365).

Polish

  • The old Sign in with Google button is back. It replaces Google's own button and still goes straight to Google, so Google's account chooser still says "continue to dwellduel.com". Nothing of Google's loads on the page any more (#366).

Under the hood

  • Readable production stack traces in Sentry. A Vercel build holding SENTRY_AUTH_TOKEN uploads its source maps to Sentry under the deploy's release, links the release to its commit, then deletes them from the output, so nothing public changes. Vercel never had the org and project set, so no build uploaded until next.config.ts defaulted them to dwellduel and dwell-duel. Builds without the token (CI, e2e, local) are unchanged, and an upload that fails only warns (#348, #370).
  • Less noise in Sentry from sign-in. A blocked or unreachable Google sign-in script stopped reporting errors, and the detail-free "Script error." browsers report for a cross-origin script is dropped; since #366 nothing of Google's loads at all (#363).
  • Guard rails for Claude Code in the repo. A checked-in .claude/settings.json refuses supabase db push, pushing straight to main, the Supabase connector's write tools, and the Vercel and UptimeRobot connectors' irreversible ones, and a hook refuses edits to a migration that's already on main. .mcp.json adds a read-only Supabase server scoped to production, and two project skills (new-migration, release) write down how those are done here (#368, #376).
  • The docs match the app again. A full review brought README, Getting started, the admin guide, Architecture, Operations and Security up to date with LMSR pricing, the direct Google sign-in, 0109's grants, the monitoring now in place and the Claude Code setup. How it works now says error reports go to Sentry, Operations gains a Monitoring section, Security gains an AI agents section, and the October plans moved into docs/archive/ (#369).
  • The scale seeder makes today's data. Its markets are priced by the market maker and its bets and parlays go through the slip's own function, in order, so the seed matches what the app makes and about 70 markets stay open to bet on; its parlay step, which had placed nothing since bets went final, works again (#373).
  • Tidier code and setup notes. The unused chip button size is gone, a comment on parlays riding an outcome is current, and the local Google sign-in notes agree on where the keys go (#374, #375).

Full changelog: CHANGELOG.md

DwellDuel v0.10.0-beta

Choose a tag to compare

@Aaron-Wickham Aaron-Wickham released this 03 Oct 13:59
bd309cf

A polish release from a round of phone testing. Pages open at their top, the tab bar stays at the bottom of the installed iPhone app after typing, and dark mode shows less white during a reload. The admin tabs show their count as a badge, Create market explains each market type, and the sign-in page loses the white box around Google's button and gains a beta badge.

Fixes

  • A new page opens at its top. Switching pages from one scrolled only a little, or between Admin sections, used to keep the old scroll position; going back still returns to where you were (#349).
  • The tab bar stays at the bottom after typing. In the installed iPhone app, the tab bar and top bar could stay a keyboard's height up the screen after the keyboard closed, most often after tapping a tab while a field had focus; the app now puts them back once the keyboard has gone (#350).
  • Less white between pages in dark mode. A full page load, such as the reload after an update, now starts on a dark background in dark mode instead of white (#353).

Polish

  • Admin tabs show a badge, not a number in brackets. Tasks and Markets carry the same red badge as the top bar's Admin button, so the tabs stay on one line on a phone (#351).
  • Choosing a market's type explains each one. Create market's type picker is three cards, Yes/No, Multiple choice and Over/Under, each saying in a line what it asks; stacked on a phone and side by side on a wide screen (#352).
  • A tidier sign-in page. The white box around Google's button in dark mode is gone, the page says it's a beta beside the wordmark, and on a wide screen the page starts under the wordmark instead of floating mid-window (#354).

Pull requests: #355, #356, #357, #359

DwellDuel v0.9.0-beta

Choose a tag to compare

@Aaron-Wickham Aaron-Wickham released this 02 Oct 21:52
c25834e

Every bet now pays a fixed amount you see before you place it. Markets are run by a market maker, so the slip shows exactly what a bet or parlay pays, odds move as people bet, and bets are final once placed; open markets switched over without changing anyone's payout. Markets have categories to filter by, a closed market can be reopened for more bets, lists across the app are cards, and new invitees get a sign-in page that shows what DwellDuel is.

Features

  • Every market pays a fixed amount, and bets are final. Markets are now run by a market maker (LMSR). A new market opens at even odds, a bet buys shares that pay 1 DC each if its outcome wins, and the slip says exactly what a bet pays ("Pays 18 DC if it wins", no "~"). If the price moves by more than 2% before you place it, the slip shows the new payout and asks again. Bets can't be cancelled, the chance, chart and sparklines show the market maker's price, a void refunds what each bet cost, and the owner's economy panel has a Market maker line (#325, #333).
  • Parlays pay a fixed amount and move the odds. A parlay's stake is split evenly across its 2 to 6 picks, and each part buys shares at its market's price, so the slip shows exactly what the parlay pays ("Pays 49 DC (4.96×) if every pick wins"). There's no cap on the multiplier or payout, your own markets count, and no money from others is needed first. A voided pick drops out and the rest pay at their own odds; if all are voided, the stake comes back. Best parlay uses the multiplier fixed when the parlay was placed (#334).
  • Open markets switched over, and nobody's payout changed. Markets from before fixed payouts that were still open, or closed and waiting for a result, switched over at this release. Each bet now pays exactly the "Pays ~" it showed, each outcome's chance stayed where it was, and if a market resolves to an outcome nobody had backed at the switch, the bets placed before it are still refunded, as the old rules promised. Pending parlays had their unset picks' odds set from the pools as they stood, with the old 20× and 1,000 DC caps kept. How it works is rewritten for one set of rules (#335).
  • Market categories. Every market now has one category, chosen when it's made: type to pick from the suggestions, tap one of the six most used, or type a new one (up to 24 characters; capitals and spacing don't make a new one). Markets shows a row of category chips, All, the eight busiest and More…, that scrolls sideways on a phone and stays in the link, with a "Showing … in X · Show all categories" line; cards and the market page show each market's category. The creator can change it until the market closes and an admin at any time, each change listed under Edited, and Admin › Markets gains a Categories section to rename, merge and hide them. Existing markets are in Other. The Everyone's / I bet on / I made chips are gone, and old links to them show every market (#327).
  • A sign-in page for new invitees. Sign in opens on the DwellDuel symbol growing its leaves and settling into the wordmark, then a sample market draws its chart as two bets land, above "Friendly bets. Faithful study.", what DwellDuel is in three lines, and the Google button with a reminder to use the invited account. It plays once a visit (never under reduced motion), keeps the button on screen on an iPhone SE, and splits into two columns on a desktop; Not invited gets the same look (#329).
  • Change a market's close time, and reopen a closed market. The market's creator or an admin can now move the close time from Edit, later or earlier as long as it's still to come, and a market that has closed with no result shows Reopen, which takes bets again until a new close time. Both ask first, naming the new time. A creator with money on their own market asks an admin instead. Bets already placed stay as they are, a reopened market leaves Markets to resolve until it closes again, its closing reminders go out again then, and every move is listed under Edited (#326).

Fixes

  • A settled parlay still counts as a stake. Its owner can't resolve a market their parlay has a leg on even after the parlay was settled elsewhere, since an override there could bring it back, and a creator with a bet or parlay leg on their own market now asks an admin to void it (#334).
  • A converted parlay's page explains it truly. Its "How it adds up" note says each pick's odds came from the pools and were fixed by the October 2026 switch, and that the old 20× and 1,000 DC caps still apply, instead of the split-stake wording of new parlays (#335).
  • A creator with a stake in their own market no longer sees Void. The database has refused it since #334; the market page now asks can_void_market, as it asks can_resolve_market for Resolve, and tells the creator a reviewer resolves it and only an admin can void it (#335).
  • A moved-price message clears when you change the stake. The slip's "The price moved, so this bet now pays N DC" note no longer lingers beside a stake it no longer describes (#334).
  • Category changes show without a reload. When an admin renames, merges or hides a category, Markets, Home and the other pages listing markets update within seconds, and an open market page updates its category chip (#345).

Polish

  • List items are cards across the app. Bets, tasks, the leaderboard, markets waiting to be resolved, the admin task catalog, pending approvals, admin members and the home tiles now share My bets' parlay card: a hairline border and 14px corners, spaced apart instead of divided, in two or three columns on a wide screen. The feed, ledger, coin history, invites and a market's bet list stay divided rows (#328).

Under the hood

  • The last LMSR leftovers are gone (0108). pick_quotes, kept by 0107 for the build serving during that deploy, is dropped, as are enforce_write_limit's message for the dropped bet_cancel limit and cancelled_bets' place in the realtime publication, which no page follows any more. market_categories pings the markets topic, and joins the publication so the market page can follow its own category's row (#345).
  • The pool rules' leftovers are gone (0107). With no pool market open since #335, cancel_bet, remove_bet, refund_room, place_slip, place_slip_v2, place_slip_v3, create_market and create_market_v2 are dropped, with the cancel write limit; the app loses the Cancel and Remove controls, the slip's pool estimates and parlay rules (pick_quotes, soloPayout, the leg floor note), the pool parlay fallback after placing, and its live follows of cancelled_bets. Everything history and overrides still read stays: My bets' Cancelled tab, the seed, locked parlay odds, pool_payout for re-resolving an old market, pick_quote and parlay_leg_odds for old parlays, and pick_quotes until the previous build is gone. The audit is in docs/superpowers/plans/2026-10-02-lmsr-5-cleanup.md (#332).
  • Converting open pool markets at release (0105). convert_pool_markets_to_lmsr(), which the migration calls once and refuses to run on a pool that doesn't equal its live bets, gives each live bet shares equal to its pool_payout, cost, converted and refund_outcomes, starts each market maker at the shown chance (q = 50 ln p, shifted, with q_offset = q − shares), and fixes each pending pool parlay (converted, legs locked at pick_quote, factor = locked odds, no book shares, 0074's capped multiplier and payout). resolve_market_core refunds a converted bet whose refund_outcomes has the winner, settle_parlay keeps a converted parlay's caps when a leg is voided, market_sparklines charts converted bets at their pool chance, member stats, records and awards count those refunds as refunded, place_slip_v2 and place_slip, and create_market_v2 and create_market, refuse after replaying a finished key, so no pool market can be made after release, and can_void_market is new. Tests make a pool market with createPoolMarket. The DB tests' slip calls move to place_slip_v4, and the ledger check allows a converted parlay's legs a factor without shares (#335).
  • Fixed-payout parlays in the database (0104). place_slip_v4 and the internal place_lmsr_parlay buy each leg's shares into the house parlay book and store each leg's factor and the parlay's multiplier and payout; settle_parlay pays them, a voided leg dropping its factor; parlay_limits() caps every parlay at 6 legs; a parlay_legs trigger keeps each leg's figures matching its market's pricing; the economy panel books these parlays on the market maker line; charts include parlay legs and compute their weights in double precision (about 35× faster); the DB tests' ledger check now holds each outcome's shares equal to its bets' plus its parlay legs', and activity_feed, their oracle, pays floor(shares) on new markets (#334).
  • Categories in the database (0103). market_categories with a unique slug, markets.category_id defaulting to Other so the previous build keeps creating markets, create_market_v4, update_market with p_category, admin rename, merge and hide functions logging to market_edits, and category_counts() for the chips (#327).
  • Fixed-payout markets in the database (0102). create_market_v3, place_slip_v3 and place_lmsr_bet price new markets by LMSR; place_bet, cancel_bet and remove_bet refuse them, so a cached older app can't bet on one; resolving pays floor(shares) and keeps the fractions for payout rounding; charts, the weekly recap's upset and member stats read the new rules, and the DB tests' ledger check holds each outcome's shares equal to its bets' (#333).
  • The maths for fixed-payout markets is in. The LMSR market maker that will price markets and parlays at the moment you bet (#325) exists in the database and in the app, kept equal by a test, with nothing using it yet (#331).

Tests

  • **Test...
Read more

DwellDuel v0.8.0-beta

Choose a tag to compare

@Aaron-Wickham Aaron-Wickham released this 01 Oct 21:38
7583dee

Signing in now feels like DwellDuel's own: Google's account chooser says "continue to dwellduel.com" instead of a long Supabase address, the sign-in page explains what DwellDuel is, and a public privacy page says what's stored and who to ask. Behind the scenes, every live-update channel is now private, and error reports to Sentry are clearer and quieter.

Security

  • Every live-update channel is private. The member's own balance channel and each page's row channels now join private Realtime topics only that member may open (0100), as the group-wide pings already did, so Supabase's "Allow public access to channels" can be switched off and nobody holding the public key can open channels and spend the message quota (#321).
  • rls_auto_enable() can't be called signed out. Supabase's platform function behind automatic RLS loses EXECUTE for PUBLIC as well as anon and authenticated (0099), clearing the Security Advisor's warning (#320).

Features

  • Google's sign-in says "continue to dwellduel.com". With NEXT_PUBLIC_GOOGLE_CLIENT_ID set, the sign-in page shows Google's own button in redirect mode (it works in the installed iPhone app), and Google sends the sign-in to /auth/google on our domain, which checks Google's CSRF token and a one-time nonce before Supabase signs the member in. Without the variable, or if Google's script can't load, sign-in works as before (#322).
  • A public privacy page and a public description. /privacy shows How it works' Your data section, now saying who runs DwellDuel and how to ask about your data, and the sign-in page says what DwellDuel is and links to it, ready for Google's brand verification (#322).

Fixes

  • Sentry no longer reports a skipped page transition. Rotating a phone or opening the keyboard mid-navigation made the browser skip the animation, and React left that rejection unhandled; those events are now dropped (#320).
  • A Supabase error reported to Sentry says what failed. A timed-out or refused call is sent as an error named for its step (for example "Closing alerts failed: TimeoutError: …") with the caller's stack, instead of a bare message with no frame of ours (#320).

Polish

  • The privacy page lists a contact email for questions about your data (#323).

Full changelog: CHANGELOG.md

DwellDuel v0.7.0-beta

Choose a tag to compare

@Aaron-Wickham Aaron-Wickham released this 01 Oct 17:55
e86fcdc

Finding things and knowing where you stand are much easier. Markets has a title search and "I bet on" / "I made" filters, the Feed has Results and Mine tabs, and a market you have money on now shows a Your position card with each bet, what it pays and how it ended. Each outcome shows the DC riding on it in pending parlays, without moving the odds. New members get a clearer start: How it works comes first, 0 DC points to Tasks instead of a dead end, and the slip shows your balance and what's left after you bet. Lists load faster, and live updates are lighter and stay within the free-plan limits, falling back to a refresh every minute if a connection can't be made. Signing in returns you to the link you opened, and accessibility and layout polish touches the tab bar, toasts, My bets and long names. For admins, Members and Invites have search, tabs and paging, each member has their own Admin page, and the task review queue pages instead of loading everything. Behind the scenes there are caps and retention on stored files, limits on how fast members can write, a Your data section on what DwellDuel keeps, and new Admin guide, Operations, Releasing and Security docs.

Security

  • Members' writes are rate-limited. Each member can make at most 20 markets a day, 10 comments a minute (200 a day), 60 reactions a minute (1,000 a day), 30 task submissions a day and 20 bet cancels an hour; going over shows a message saying so. Admins and the owner aren't limited, and a member keeps notifications on their ten most recently used devices (#273).
  • New database objects grant signed-out visitors nothing by default, and a schema-wide test fails on any table without RLS, any grant to anon, or any new SECURITY DEFINER function a signed-in account can call that hasn't been reviewed (#274).
  • Google accounts that were never invited are deleted a day later by the daily cron, instead of staying in Auth forever (#275).
  • Dependencies and headers tightened. Next is upgraded to 16.3.8 (with eslint-config-next), the x-powered-by header is gone and a Permissions-Policy denies camera, microphone and location (#276).

Features

  • The market page shows your own position. A market you have money on gets a Your position card, above the chart on a phone and at the top of the right-hand column on a computer: each of your bets with what it pays if it wins and its own Cancel, and each parlay with a leg there, linking to the parlay. Once the market settles it shows each bet as Won, Lost or Refunded, what you won or lost on the market overall, and where each parlay leg and its parlay stand. "No more bets" on a resolved market now says solo bets have been paid and parlays pay once every pick has settled (#262).
  • Parlay money shows on each outcome, without moving the odds. Each outcome on a market page shows the DC riding on it in pending parlays ("+45 DC riding in parlays"), each parlay's full stake on every pick, updated live, with a note that parlays are paid by DwellDuel and never move the pool. It's a total only, never whose; the pool, chance, payouts and charts don't count it (#279).
  • Admin › Members and Invites work at a thousand members. Both have a search box (Members by name or email, Invites by email), tabs (Members: Active · Removed; Invites: Waiting · Claimed) with counts, and "Show more" paging, so nobody past the 1,000th row is silently dropped. Members are compact read-only rows; each opens the member's own Admin page with Adjust balance, Role, their last coin movements ("Open in Ledger", which filters Admin › Ledger to them with ?member=) and Remove from DwellDuel. New-market notifications now reach every opted-in member past the first 1,000 (#254).
  • Lists can be narrowed. Markets has a title search and "Everyone's · I bet on · I made" chips (a search or a chip lists matches as one flat list, newest first), the Feed has All, Results and Mine tabs (a void counts as a result, and Mine shows voids of markets you bet on or made), and on a phone the Net worth board has a compact standing card whose "Jump to me" opens the list ten ranks above your own row, so a member ranked 600 no longer pages down twelve times (#264).
  • Getting started has five steps, How it works first. New members are pointed to How it works and to turning on notifications on this device, before the photo, first bet and first task. Once the card is gone, the installed app without notifications asks once to turn them on, in place of Get the app (#260).
  • 0 DC isn't a dead end. At 0 DC, Home and the slip point to Tasks, Home with what tasks pay. The slip shows your balance and what's left after it (or how many DC short it is), and a line under Place says why it can't be tapped (#260).
  • How it works opens with the short version, has a collapsible "On this page" list on phones, keeps the formulas in "The maths", and says "shared pot" and "Monday–Sunday weeks" instead of jargon (#260).
  • A link survives signing in. A shared market link opened while signed out lands on that market after sign-in, not Home; only a same-site app path is accepted. Google always asks which account to use, so "Try another account" really offers another, and the not-invited page says which account was refused (#263).
  • Settings links to what DwellDuel keeps about you. How it works has a new Your data section: what's stored, who can see it, where it's kept, how long (proof 30 and 90 days, backups about 60) and how to leave or ask for deletion; Settings' Help card links straight to it (#286).
  • Admin links to the new Admin guide (docs/ADMIN-GUIDE.md): roles, inviting, resolving, overriding and voiding, reviewing tasks, members, balances and the ledger, for reviewers, admins and the owner (#283).

Fixes

  • Live updates no longer die on a fresh page load. The live channels could join before the app had read your session, as a signed-out visitor, and the server then refused them, so your balance and the page stopped updating until a reload. They now wait for your session, and a refused channel falls back to refreshing every minute (#310).
  • The Markets tab opens on markets you can bet on. Open markets now come first on the All tab however many are waiting on a result, with Awaiting resolution as its own list and its own Show more; before, 50 or more awaiting markets filled the first page. Home's open-market count leaves out markets past their close (#261).
  • The task review queue pages. Admin › Tasks lists the oldest 50 pending submissions with "Show more", signs proof files only for the rows on screen, and takes its "waiting" badge from the whole queue instead of the page shown (#255).
  • Removed members are no longer ranked. They drop off both leaderboards, out of "Rank X of N", the month's champion and the weekly recap's best call and top tasker; their profile still shows their net worth, marked "Not ranked". Admin › Members marks them Removed, and the owner can invite them again from their page, after a confirmation (#265).
  • Creator stakes stay within the URL limit. A deep feed window asks about each chunk of 50 markets with only that chunk's creators, not every creator on the page (#272).
  • The void copy says what happens to parlays: every bet is refunded, and parlays drop the voided leg and carry on with the rest. The resolve and override confirmations no longer say parlay legs are paid out: winning solo bets are paid, and a parlay pays once every pick has won (#266).
  • Home's Admin tile counts what the Admin badge counts, approvals and markets to resolve, and opens the queue that has work in it (#266).
  • Create market explains the close time: betting stops then, so set it before the answer is known, and the outcomes, close time and line can't change later; the preview notes that a reviewer resolves a market you bet on (#266).
  • Approved repeating tasks say when they open again, such as "Again Monday, midnight ET" (#266).
  • The slip's "How parlays pay" link lands on its section of How it works (#260).
  • A link to a section of How it works from another page now scrolls to it. Following one from inside the app (the slip's How parlays pay, Settings' Your data) used to open the page at the top, because the section wasn't there yet while the page loaded (#286).

Polish

  • Code nits. One GROUP_TIME_ZONE for seasons, the recap week and the economy month; one shared labelClass for field labels and legends; exports nothing else uses are no longer exported (#272).
  • Accessibility fixes (#268). Error toasts use the app's loss colours (5.65:1 in light, 7.18:1 in dark, where Sonner's own red was 4.35:1), the phone tab bar's active pill, Admin button and profile ring, and SubNav's selected pill, now carry an edge that reaches 3:1 on the page in light mode, and the Leaders tab's accessible name is "Leaders, leaderboard", so voice control hears the word on screen.
  • My bets on desktop (#269). Parlay and bet tiles tint on hover like every other row inside a card, instead of lifting, and the grid aligns tiles to the top, so a tall parlay no longer stretches its neighbours.
  • Smaller fixes (#270). A proof link longer than 2000 characters is refused in the picker (and by the task and resolve actions) with a plain message, instead of failing the whole submission; market, outcome and slip titles share the row title size; the Preview and race cards put their line in the description slot; more tap targets press; removing outcomes in Create market keeps keyboard focus; the global error page follows your theme; unknown URLs show the 404 inside the app's header and tab bar, with copy that no longer blames a removed market; /admin opens its first section and /members the leaderboard; long names on the podium wrap over up to three lines instead of cutting off at one.

Under the hood

  • Live updates fit the free tiers at 1,000 members. Group-wide changes (ma...
Read more

DwellDuel v0.6.0-beta

Choose a tag to compare

@Aaron-Wickham Aaron-Wickham released this 01 Oct 15:07
2a0f484

Parlays are now priced at close from other members' real money, with new caps (at most 5× a leg, 20× a parlay and 1,000 DC paid), winners split exactly the real pool instead of the seed, and every void says why. Behind the scenes there are nightly encrypted backups and a restore point before every migration, deploy guardrails that push whatever production is missing and run only from main, error monitoring with a health route and cron heartbeat, a cron that finishes every step even when one fails, more reliable notifications, retry-safe market, comment and task actions, a stricter rule that removing a member ends their access, and a database test suite that checks the money after every test.

Security

  • Removing a member takes away everything they could do, and signs them out. A creator's powers over their own markets (resolving, voiding, editing, attaching resolution proof) and deleting their own comments now need a current invite, as roles already did, and remove_member ends the member's sessions on every device. A schema test fails on any member-callable security definer function that writes without checking the invite or a role (#288).
  • Only an unclaimed invite can be revoked, in the database as well as the app. An admin's delete of an invite someone has already signed in with removes nothing; taking out a member stays the owner's, through Remove member. A new invite records the admin who added it and always starts unclaimed (#289).

Features

  • Parlay legs are priced from real money when their markets close. Parlays are still paid by the house and stay out of the pools, but each leg's odds are now set when its market closes (or is resolved, if that comes first), from other members' money on the market divided by their money on the pick, with your own money and the seed left out, and at most 5× a leg. The slip, My bets and the parlay page show ~ estimates until then. A leg needs at least 50 DC from at least 2 other members on its market when it's placed, can't be on a market you created, and counts 1.00× if its market no longer has that at close or nobody else backed the pick. A parlay multiplies to at most 20× (was 100×) and pays at most 1,000 DC; one that staked more than that before the cap still gets its stake back on a win. One member's pending parlays on any one market can pay at most 1,000 DC between them. Parlays still pending from before keep their locked odds under the 20× and 1,000 DC caps (#287).
  • Winners split exactly the real pool. The seed now only shapes the chance and charts a thin market shows; it's never paid out, so a winner nobody bet against gets their stake back. Each outcome's "× payout per DC", the slip's "Pays ~" and My bets show what will really be paid. Results from before keep what they paid (#287).
  • Voids follow the same rule as results, and say why. Until a market closes, its creator or an admin can void it; once it has closed, only an admin can. Every void needs a reason (up to 500 characters), which shows on the market page and, with the void itself, in the feed (#290).

Fixes

  • A lost response no longer duplicates a market, comment or task. Next replays a server action whose response never arrived, even when it had already committed, so a phone switching networks while creating a market could make it twice. Creating a market, posting a comment and creating a task now send an attempt key, and a repeat returns the first result, without a second new-market push, and a replayed resolve, void or review that already went through reads as done instead of an error (#258).
  • Editing a balance adjustment after a lost response applies the edit. The form used to replay the first amount and still say "Balance adjusted"; it now starts a new attempt when the amount or reason changes, and the toast names the amount applied (#267).
  • One broken notification device no longer raises the closing-alerts alarm, and a systemic failure still does. A failed push is logged and counted instead of failing the run, so one dead device can't keep the Admin warning on or make the backup workflow email. A run that delivers nothing while any failure is systemic (every device answering 401/403 because our VAPID credentials are wrong, our own network errors, 429, 5xx) returns 502 and leaves the heartbeat alone, even for a one-device group. Only a 4xx the device caused counts (not 429, and not 404/410, which delete at once; 401/403 count when the run delivered something else, so a subscription left over from an older key is pruned) against a device, which is pruned after five such failures spanning more than a day, or after 60 days without a delivery once it has also been failing for a day (#257).
  • Closing alerts run one at a time, and stop retrying a market nobody can be reached about. A lease stops pg_cron and the GitHub backup sending the same alert twice, and a market whose pushes have failed for 24 hours is given up on instead of retried for ever (#257).
  • Notifications re-sync themselves. On load, a device that turned notifications on re-makes a subscription the browser dropped or made with an old key, and saves a rotated one; the service worker also handles pushsubscriptionchange (#257). A device subscribed before this ships re-syncs after its member next opens Settings; if the server already deleted its row, the member taps "Turn on" there.
  • The daily cron runs every step even when one fails. A Storage error in proof cleanup no longer skips key pruning, the champion post or the reminders; the failures are listed at the end with a 502 (#259).
  • Unexpected database errors in resolve and slip placement are logged and shown as "Something went wrong" instead of raw text, and the sign-in callback logs why it failed (#256).
  • Error pages show an "Error code" members can quote, and /api/cron/closing-alerts pings an optional healthchecks.io check (HEALTHCHECKS_CLOSING_ALERTS_URL) too (#256).
  • A failed read of the closing-alerts health no longer takes down Admin. The banner says it couldn't check and every Admin page still loads (#256).
  • A balance can't run past the ledger's integer ceiling. A credit that would take a balance past 2,147,483,647 DC is cut to fit, so it can never block a resolution (#287).
  • The economy panel shows payout rounding apart from seed payouts. With the seed no longer paid, a result only ever leaves the fractions that rounding down keeps, now their own "Payout rounding" row; "Seed payouts (older results)" shows only in a month an older, seeded result was paid or overridden (#287).
  • This month's awards leave out removed members, so an award goes to the next member still in (#287).
  • The owner can remove a bet only until its market closes, the same moment members stop being able to cancel one (#272).
  • The feed skips an event kind it doesn't recognise instead of failing, so a new kind can reach the database before the build that shows it (#290).

Under the hood

  • Production errors are captured, and there's a health route and a cron heartbeat. Uncaught server errors and browser crashes go to Sentry (errors only, PII scrubbed) when NEXT_PUBLIC_SENTRY_DSN is set; /api/health answers 200 only while Supabase is reachable, for an uptime monitor; and the daily cron pings healthchecks.io (HEALTHCHECKS_KEEP_ALIVE_URL), with a fail ping when a step failed. All three are off when unset (#256).
  • Nightly encrypted backups. A new Backups workflow dumps the production database every night (roles, schema, and data including sign-ins and Storage records) and copies the proof and avatars buckets every Sunday, encrypted with age into a private backups repo that keeps 60 days; docs/OPERATIONS.md is the restore runbook, rehearsed once against a local copy (#248).
  • Every migration has a restore point. Deploy Production takes the same encrypted dump just before it applies migrations, and doesn't migrate if the dump fails (#248).
  • Deploys push whatever production is missing. Deploy Production asks production which migrations it lacks on every run instead of reading the merge's diff, so one a failed run left behind goes out with the next; waiting runs queue instead of replacing each other; and a run that main has moved past no longer fails waiting on a build it never started (#249).
  • CI fails a migration numbered out of order. A PR's new migration must be numbered after main's newest (#249).
  • Deploy Production runs only from main, with its secrets in the Production environment, and ci-ok is required for every merge (#291).
  • Backup jobs register secret masks before running. Each job masks its backup secrets in a step of its own, the backup scripts blank them from every tool's output, and the workflows check that a backup printed only its file paths before pushing them. restore.sh now takes the database URL from RESTORE_DB_URL or a hidden prompt instead of an argument.

Tests

  • DB tests cover parlay pricing and real-pool payouts: legs on your own markets and on markets without the floor, copies of one market held to the caps, the 5× leg cap, the per-market exposure cap, a bet cancelled after placement, a market that loses the floor before close, a parlay placed before the change, payouts that split exactly the real pool (pool_payout and poolPayout kept equal), and credits and refunds at the integer ceiling (tests/db/parlay-pricing.test.ts, seeded-odds.test.ts). backers() and backLeg() give a test the other members' money a leg needs (#287).
  • The DB suite is typed, names what each refusal was for, and checks the money after every test. Test clients carry the Database type, so a renamed RPC argument fails the typecheck. 86 negative assertions that accepted any error now check the message or SQLSTATE (expectError). After each DB test, tests/db/setup.ts checks that every balance equals its ledger, every outcome's pool equals it...
Read more