Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
12 changes: 8 additions & 4 deletions src/app/play-poker-free-no-signup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

/**
Expand Down Expand Up @@ -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.',
},
]

/**
Expand All @@ -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.',
],
},
{
Expand Down Expand Up @@ -198,7 +201,8 @@ export default function PlayPokerFreeNoSignupPage() {
<Lead>
<p>
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.
</p>
<p>
The engine is open source, so how the cards are dealt is something you can read rather
Expand Down
67 changes: 67 additions & 0 deletions tests/priceClaims.test.ts
Original file line number Diff line number Diff line change
@@ -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.`,
)
}
}
})