diff --git a/ROADMAP.md b/ROADMAP.md index eb49c02..35bd5db 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -16,6 +16,10 @@ and dates aren't listed on purpose. If something here matters to you, - Light/dark themes, sound, desktop + mobile, installable PWA - Local-first: your profile lives in your browser (versioned, exportable), with an optional account if you want it on more than one device +- Written guides at `/learn`, with every figure on them computed rather than typed +- A free poker odds calculator that prints the margin of error next to the answer +- Drills: short spots with a right answer, graded by the engine, never metered +- A card at the end of a tournament that reads the run you just played, and stores nothing ## Considering next @@ -44,10 +48,17 @@ it gets metered later. That covers the ladder, the venues, the Daily, the Chip S moving your profile between devices, and every written guide. There will eventually be a **membership**, for the things that cost real money to run or -that don't exist yet: multiplayer, and a deeper learning layer with drills and coaching -across your hands. It isn't built, it isn't priced, and there's nothing to buy today. +that don't exist yet: multiplayer, and more kinds of drill. The drill that shipped free +stays free and unmetered, which is the paragraph above applied to the thing most likely to +test it. -If that ever stops being true, this section is where you'd catch us. +**Part of it is built, and this section said it wasn't until 26 August.** The membership +check is live in the app, and since 25 August one drill kind sits behind it. There is no +price, no membership page and no way to pay, so nobody is a member and nobody has given us +any money. That is the whole of the state. + +If that ever stops being true, this section is where you'd catch us. It stopped being true +once already, and this is the corrected version. ## Not planned diff --git a/src/app/play-poker-free-no-signup/page.tsx b/src/app/play-poker-free-no-signup/page.tsx index d7c88e0..4922c34 100644 --- a/src/app/play-poker-free-no-signup/page.tsx +++ b/src/app/play-poker-free-no-signup/page.tsx @@ -21,7 +21,7 @@ import { SITE_URL, contentAlternates } from '@/config/site' const TITLE = 'Play poker free, no signup' const DESCRIPTION = - 'Free Texas Hold’em against AI opponents that play a proper game. No signup, no download, no real money and nothing to buy. Type a name and you are dealt in.' + 'Free Texas Hold’em against AI opponents that play a proper game. No signup, no download, no real money, and the chips are not for sale. Type a name and you are dealt in.' const PATH = '/play-poker-free-no-signup' /** @@ -87,7 +87,10 @@ const CHECKLIST: { thing: string; answer: string }[] = [ thing: 'Where your progress lives', answer: 'Your browser, on your device. Ours only if you switch sync on, and it ships off.', }, - { thing: 'What it costs', answer: 'Nothing.' }, + { + thing: 'What it costs', + answer: 'Nothing to play. Not a trial, and nothing in it gets metered later.', + }, ] /** @@ -100,7 +103,7 @@ const FAQ: { q: string; a: string[] }[] = [ { q: 'Is it actually free?', a: [ - 'Yes. There is nothing to buy in the game, no ads, and no paid tier of anything on this page. The chips are play money and they are not for sale, which is the part that makes the rest of it hold: there is no purchase for us to be steering you towards.', + 'Yes. No ads, no signup, and no charge to play. The chips are play money and they are not for sale, and there is no advantage you can buy at any price, which is the part that makes the rest of it hold: there is no purchase for us to be steering you towards.', ], }, { @@ -198,7 +201,8 @@ export default function PlayPokerFreeNoSignupPage() {

Pip is a free Texas Hold’em game that runs in a browser tab. No signup, no download, no - real money and nothing to buy. Type a name, pick a face, and you are at a table. + real money, and the chips are not for sale. Type a name, pick a face, and you are at a + table.

The engine is open source, so how the cards are dealt is something you can read rather diff --git a/tests/priceClaims.test.ts b/tests/priceClaims.test.ts new file mode 100644 index 0000000..38db4fc --- /dev/null +++ b/tests/priceClaims.test.ts @@ -0,0 +1,67 @@ +import { readFileSync, readdirSync } from 'node:fs' +import test from 'ava' + +// Two sentences in the Product Hunt copy were pulled on 17 August for the same +// reason, and this file is that ruling turned into something that cannot be +// forgotten: **"nothing to buy" and "it can't take your money" are borrowed, +// not true.** Both were accurate the day they were written. Both go false the +// day anything optional goes on sale, and a launch thread is permanent. +// +// The general test they came from: name the feature that would falsify the +// sentence, then ask whether that feature is on the roadmap. If it is, the +// sentence is borrowed. For these two the feature is the membership, it is on +// the public roadmap, and since 25 August one drill kind sits behind its +// entitlement check. +// +// The ruling was applied to the launch copy and not to the site, so +// `/play-poker-free-no-signup` carried all three of these into production and +// served them for sixteen days. That is the third time a ruling has been +// applied to the surface it was aimed at and not to the one sixty lines away, +// so this time it is a test rather than a note. +// +// **What to say instead.** The permanent forms are the ones that survive +// whatever we ever charge for: the chips are not for sale, you cannot buy an +// advantage, nothing that shipped free gets metered later, and there is nothing +// here you can actually lose. Every one of those is a statement about how Pip +// is built rather than about what happens to exist in the shop today. + +/** Absolutes about money that a membership would falsify. */ +const BORROWED = [ + /nothing to buy/i, + /no paid tier/i, + /(can'?t|cannot|could not|couldn'?t) take your money/i, + /(can'?t|cannot) charge you/i, +] + +// Deliberately no exemption for /blog/, unlike dataClaims.test.ts. There a +// published post genuinely contained the absolute and a post is a dated record +// that gets a correction note rather than an edit. Here nothing published ever +// carried one — checked across all six posts when this file was written — so +// there is no history to protect and the cheapest thing is to hold the whole +// site to it, including posts not yet written. +function surfaces(dir: string, out: string[] = []): string[] { + for (const entry of readdirSync(new URL(dir, import.meta.url), { withFileTypes: true })) { + const path = `${dir}/${entry.name}` + if (entry.isDirectory()) surfaces(path, out) + else if (/\.tsx?$/.test(entry.name)) out.push(path) + } + return out +} + +test('no page claims there is nothing to buy', (t) => { + const files = [...surfaces('../src/app'), ...surfaces('../src/components')] + t.true(files.length > 40, 'the walk found nothing, so it is proving nothing') + + for (const file of files) { + // Flattened for the same reason as dataClaims: JSX breaks a sentence across + // lines and the phrase only exists once the whitespace is gone. + const source = readFileSync(new URL(file, import.meta.url), 'utf-8').replace(/\s+/g, ' ') + for (const claim of BORROWED) { + const match = claim.exec(source) + t.falsy( + match, + `${file}: "${match?.[0]}" is an absolute a membership makes false. Say what cannot change instead: the chips are not for sale, and no advantage is.`, + ) + } + } +})