Commit Graph
100 Commits
Author SHA1 Message Date
jheapsandClaude Opus 5.5 798e8c2e57 Socialism: compare co-op taxes honestly with partnerships and S corporations, explain why members can't take the 20% pass-through deduction on the same ownership theory as the payroll-tax argument, and count the small-business stock exclusion in the founder gap
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-27 14:54:44 -06:00
jheapsandClaude Opus 5.5 fd04ea3886 Socialism: work out how the return on members' accounts is taxed, pay the two-point premium every year so it isn't a share of profit to capital, and credit returns after the co-op's tax so all-cash members don't pay it
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-27 14:24:57 -06:00
jheapsandClaude Opus 5.5 89f73a58e4 Socialism: test the "lower-paid work" rule on 2024 federal wage data, add a national check so consultancies in rich counties don't qualify, hold half the set-aside for the lowest-paid industries, and close code, county and timing games
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-27 11:27:35 -06:00
jheapsandClaude Opus 5.5 afb5330e08 Socialism: give the bank a working legal basis to ask loan applicants' race and sex, since the federal small-business data rule won't reach it, with a separate custodian, deletion and small-number limits
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-27 10:53:01 -06:00
jheapsandClaude Opus 5.5 f8c1a1e813 Socialism: stop balancing the founding lottery on race and gender, set the rule that race and sex measure results but never decide who gets anything, and apply it to loan data and layoff checks
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-27 10:23:30 -06:00
jheapsandClaude Opus 5.5 3b7bde0568 Socialism: sort out what a worker is owed in notice pay and severance when a co-op closes, have the insurance pool pay notice pay after unpaid wages with limits against padding, and reprice wage cover to match
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-27 09:56:08 -06:00
jheapsandClaude Opus 5.5 eaac398c0f Socialism: say plainly when co-op members and officers can personally owe unpaid wages (federal law, New York, Massachusetts, California), stop ordinary work after a missed payroll, and narrow the model state law's shield so workers who aren't members keep their claims
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-27 09:22:41 -06:00
jheapsandClaude Opus 5.5 a983b7b6a8 Socialism: price the savings insurance pool from Census closure rates by firm age, write out the formula that cuts limits when the reserve falls behind, and say what a young pool would pay in a bad recession
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-27 05:58:04 -06:00
jheapsandClaude Opus 5.5 9dd06ff181 Socialism: make whoever carries on a failed co-op's business answer for people its products hurt later, honor warranties and household deposits, and put members' plans to keep a co-op through a market test
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-27 05:25:01 -06:00
jheapsandClaude Opus 5.5 12905d5db1 Socialism: stop leases, licenses and sold invoices from carrying a co-op's business around the lock: essential leases must allow a co-op successor, sale-leasebacks count as sales, and cash advances are limited
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-27 04:51:57 -06:00
jheapsandClaude Opus 5.5 acaf7f0adc Socialism: keep checking insider deals after the bank loan is repaid, as part of the lock, on the model of the federal rules for charity insiders
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-27 04:19:52 -06:00
jheapsandClaude Opus 5.5 2ac3f59695 Socialism: size what the lock can cost creditors who never chose to lend, say who's covered, keep the appraisal honest, and have the co-op that buys the business pay it back
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-27 00:51:18 -06:00
jheapsandClaude Opus 5.5 d96cec00c7 Socialism: say how a workers' bid for a failed co-op gets paid for, cap the bank's part at the break-up value so it can't hide its losses, and set the insurance pool's repayment terms
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-27 00:20:24 -06:00
jheapsandClaude Opus 5.5 cc343b7d0c Socialism: justify the long-run rules' numbers: severance only for people let go, equipment measured at original cost, buyouts judged on the seller's book values, and cheaper second opinions for small co-ops
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 23:52:48 -06:00
jheapsandClaude Opus 5.5 f43f418ff3 Socialism: set the bonus limits and the dismissal panel: 10% of pay in total, 15% for anyone, gain sharing even without a profit, and four of five panelists drawn by lot
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 23:20:16 -06:00
jheapsandClaude Opus 5.5 ab39dd4a27 Socialism: set the care-work supplement at a first-draft $3 an hour with no per-worker fade-out, spell out its tax treatment, and say who can get it
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 20:20:02 -06:00
jheapsandClaude Opus 5.5 a90ce03297 Socialism: set the spin-off numbers: a 15% stake cap counting every exposure, a real consequence for captive spin-offs, and an honest power estimate for the growth comparison
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 19:49:50 -06:00
jheapsandClaude Opus 5.5 05c8b21632 Socialism: compare the labor-market effect with wage boards and unions, and say a wage board is the faster way to raise pay
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 19:20:50 -06:00
jheapsandClaude Opus 5.5 cf0d45684e Socialism: test co-ops' hiring with paired fake applications, as a group, and make discrimination a loan breach at any size
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 18:47:58 -06:00
jheapsandClaude Opus 5.5 ce844851ca Socialism: work out the local labor-market test's numbers, and say that on public data it fails its own rule
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 18:17:19 -06:00
jheapsandClaude Opus 5.5 80501e21fd Socialism: say what members can deduct when a failed co-op wipes out their accounts, and ask for a general refund of the tax paid
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 14:47:03 -06:00
jheapsandClaude Opus 5.5 36afaac20a Socialism: give every co-op worker an ordinary retirement plan, with a flat co-op contribution and the new federal match
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 14:18:05 -06:00
jheapsandClaude Opus 5.5 774ea901e5 Socialism: weigh the pension-law cases on accounts like these, and pause untaxed credits until the Labor Department answers
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 13:48:50 -06:00
jheapsandClaude Opus 5.5 e5bc9f2ba2 Socialism: say how lower-income members' untaxed credits follow the deferred-pay rules
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 13:16:00 -06:00
jheapsandClaude Opus 5.5 f8c6b524ac Socialism: say where securities law reaches members' accounts and outside investors
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 09:47:23 -06:00
jheapsandClaude Opus 5.5 bfca27ac66 Socialism: say how salaried staff share a cut, and keep the legal minimum wage out of workers' bids
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 09:17:25 -06:00
jheapsandClaude Opus 5.5 22de1f885f Socialism: recompute the founder and member examples under the current rules, and pay the founding share as pay
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 08:47:39 -06:00
jheapsandClaude Opus 5.5 fb0eede6a6 Socialism: say how a co-op cuts back when work runs short
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 08:14:14 -06:00
jheapsandClaude Opus 5.5 c41c88f979 Socialism: say how the lock handles companies a co-op controls or buys
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 04:44:45 -06:00
jheapsandClaude Opus 5.5 ffe6ff2963 Socialism: set the going rate for people who work remotely from another area
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 04:14:18 -06:00
jheapsandClaude Opus 5.5 d87c8d3633 Socialism: count the old capital gains break on buyouts against the buyout track
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 03:46:31 -06:00
jheapsandClaude Opus 5.5 7cda37be0d Socialism: give the board the last word on loans, and make every use of it public
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 03:14:20 -06:00
jheapsandClaude Opus 5.5 9a5648b638 Socialism: draft the model state law that closes the lock's exits under state law
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-26 00:14:42 -06:00
jheapsandClaude Opus 5.5 7973cb2e0c Socialism: say who can take the bank to court, what a judge can order, and where that runs out
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-25 23:43:11 -06:00
jheapsandClaude Opus 5.5 7ef29c8b5a Socialism: draw the line for when turned-away applicants make the verdict "not tested"
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-25 23:14:01 -06:00
jheapsandClaude Opus 5.5 45384a0709 Socialism: set the cost ceiling for help before the first loan, and count every cost against it
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-25 22:41:44 -06:00
jheapsandClaude Opus 5.5 16d42cc0ef Socialism: count what buyout help really costs, and make sellers pay for their own checks
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-25 22:12:25 -06:00
jheapsandClaude Opus 5.5 6dbb637c3b Socialism: update the buyout page for the SBA's October 2026 rules, and admit the gap they leave is narrow
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-25 18:41:01 -06:00
jheapsandClaude Opus 5.5 c1570f0680 Socialism: say what the rules cost a small co-op, and scale the checks to size
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-25 18:15:07 -06:00
jheapsandClaude Opus 5.5 8ec9cc0976 Socialism: hold buyout co-lending to real practice, and use state and rural programs without letting them hide risk
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-25 17:41:59 -06:00
jheapsandClaude Opus 5.5 2747aae0d1 Socialism: firm up the buyout numbers against the SBA's own rules
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-25 17:10:10 -06:00
jheapsandClaude Opus 5.5 2b31f5c219 Socialism: spell out what worker buyouts are compared against, and stop the tests flattering them
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-25 13:42:16 -06:00
jheapsandClaude Opus 5.5 a196614fb4 Socialism: say how a care co-op meets Medicaid's 80% home care pay rule
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-25 13:12:40 -06:00
jheapsandClaude Opus 5.5 adf8046773 Socialism: keep membership from costing care workers their public benefits
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-25 12:40:54 -06:00
jheapsandClaude Opus 5.5 855f9e099d Socialism: say plainly whether a member's profit share is pay, law by law
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-25 12:07:07 -06:00
jheaps 139e2b831c Merge pull request 'Socialism: make the lock hold, and close its back doors' (#125) from feature/socialism into master
Build and Deploy / build-deploy (push) Successful in 1m25s
Reviewed-on: #125
2026-09-25 11:08:48 -06:00
jheaps 96d2dbe5ad Merge branch 'master' into feature/socialism 2026-09-25 11:08:31 -06:00
jheapsandClaude Opus 5.5 2c269d075d Socialism: pay capital accounts on a fixed schedule so they stay clear of pension law
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-25 07:42:01 -06:00
jheapsandClaude Opus 5.5 7baa6e9810 Socialism: say where labor law leaves worker-owners out and how the co-op fills the gaps
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-25 07:08:43 -06:00
jheapsandClaude Opus 5.5 f76b766d0e Socialism: measure founders on both sides so a pilot result says how much was down to who applied
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-25 03:08:51 -06:00
jheapsandClaude Opus 5.5 aad548cd07 Socialism: name the data that decides which low-paid care work qualifies for public help
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-25 02:38:30 -06:00
jheapsandClaude Opus 5.5 f4271831f4 Socialism: price the savings insurance pool from real failure data and size it for a long recession
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-25 02:03:24 -06:00
jheapsandClaude Opus 5.5 1e82052324 Socialism: fit profit sharing to co-op tax law so members are taxed once
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-24 21:44:16 -06:00
jheapsandClaude Opus 5.5 a9efaa2725 Socialism: keep the co-op bank from drifting toward only the safest co-ops
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-24 21:07:30 -06:00
jheapsandClaude Opus 5.5 19bba58f7c Socialism: draft the bankruptcy amendment that keeps a failed co-op a co-op
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-24 17:39:10 -06:00
jheapsandClaude Opus 5.5 4006764a23 Socialism: stop high pay and worn-out equipment from draining the reserve, and check big bets
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-24 17:09:11 -06:00
jheapsandClaude Opus 5.5 a0b16e7d48 Socialism: work through the 1/N problem, with team bonuses and a fair dismissal process
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-24 16:33:57 -06:00
jheapsandClaude Opus 5.5 059299db74 Socialism: let a formula, not lobbyists, decide which low-paid work gets public help
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-24 15:59:16 -06:00
jheapsandClaude Opus 5.5 20d0ee8910 Socialism: let co-ops grow by starting new co-ops, with rules against misuse
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-24 11:35:54 -06:00
jheapsandClaude Opus 5.5 e6befaff5e Socialism: work through scale after a pass, and a local test of the labor-market effect
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-24 11:05:38 -06:00
jheapsandClaude Opus 5.5 8305c72843 Socialism: test help before the first loan, and ask founders for a stake
Add before-launch.md: a founding lottery testing whether serious
pre-launch help (a founder's stipend, a feasibility study, legal setup)
creates more co-ops that last, measured independently of the bank, and
a 5% founder stake payable in verified unpaid work. Fund it from the
loan budget, and correct the pilot's growth arithmetic.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-24 06:31:46 -06:00
jheapsandClaude Opus 5.5 1f297c244d Socialism: decide pay in the open, with the lower-paid protected
Add pay.md: pay bands open to every worker, a ratio each co-op must
choose and measure honestly, a double majority for high pay and for
raises to jobs most members hold, and no one voting alone on their own
pay. Stop the reserve rule from charging co-ops for raising the bottom,
and close its gaps for perks, non-members and related companies.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-24 05:59:02 -06:00
jheapsandClaude Opus 5.5 a77d985135 Socialism: make membership work where people don't stay long
Add high-turnover.md: in home care, a 12-month probation and the
three-quarters rule could fail a co-op doing everything right. Make
membership automatic after a short probation, take the buy-in from
profit shares rather than pay, count agency hours and push-outs, and
phase the rule in. Align the lock, the pilot's safeguards and measures,
worker ownership, the index and open problems.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-24 02:03:06 -06:00
jheapsandClaude Opus 5.5 78650c8ee7 Socialism: work through Conservice as a co-op counterfactual
Add conservice.md: the company as it is (from public sources only),
the same business founded as a locked co-op, what the profit split
could mean at its scale under labeled assumptions, whether it would
have grown, and who would have received less. Link it from the
thesis, worker ownership and the index.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-24 01:33:27 -06:00
jheapsandClaude Opus 5.5 d4f12c2b47 Socialism: lend for voluntary sales to workers, on a separate track
Add buyouts.md: owners who choose to sell a healthy firm to its workers
can borrow on a separate $100M track with its own test (money plus a
pay floor), a capped independent price, a seller note that shares the
risk, a vote of the whole workforce and the lock. Restate the total
worst case as $450M and answer open problem #12.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-24 00:59:20 -06:00
jheapsandClaude Opus 5.5 037e29055a Socialism: build the bank to survive a hostile government
Add "Making It Last": how public programs get killed (Ex-Im, CDFI Fund,
GGRF, NCCB) and survive (Bank of North Dakota, electric co-ops, Farm
Credit), and rules that assume any president can replace the board
after Trump v. Slaughter. Lending standards fixed in law, a "not tested"
verdict if good applicants are turned away, no freeze from board
vacancies, honest limits on where the money sits, and federal test with
state copies.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-23 21:03:11 -06:00
jheapsandClaude Opus 5.5 27dd0ccb3e Socialism: insure workers' final wages and savings, without taxpayers
Add savings-guarantee.md: a co-op-funded insurance pool covering final
unpaid wages, former members' payouts and a protected slice of current
accounts, priced for near-zero recovery by an independent actuary, with
a credit-union-style deposit, phased coverage, pro-rata payment when
short, and rules against phantom profits, early quitting and fraud
rings. Co-ops may no longer take member deposits or sell members bonds.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-23 20:28:16 -06:00
jheapsandClaude Opus 5.5 4c1c81fd2f Socialism: make the lock hold, and close its back doors
Explain why a permanent lock has precedent (UK CICs, steward ownership,
US nonprofit conversions) and what it prevents (building-society
demutualisation). Enforce it in layers: an independent veto-only lock
share, articles, statute, and a backstop that sends the value to the
federation fund if the lock is ever broken. Treat piecemeal asset sales,
hire-backs and subsidiaries as sales; adapt Argentina's bankruptcy rule,
noting it needs a Bankruptcy Code amendment; state the lock's costs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-23 19:54:25 -06:00
jheaps 7cc964380b Merge pull request 'Socialism: capital accounts, the founder problem, and the horizon problem' (#124) from feature/socialism into master
Build and Deploy / build-deploy (push) Successful in 1m15s
Reviewed-on: #124
2026-09-23 16:33:31 -06:00
jheapsandClaude Opus 5.5 3e072dbd5d Socialism: study the precedents and test whether the help matters
Add precedents.md: Mondragon's Caja Laboral, the US National Consumer
Cooperative Bank, Italy's Coopfond and Marcora Law, each with what went
wrong. Add a support lottery to the pilot (intensive help for a random
half, advisers walled off from lending, all help counted against the
bank), count distressed mergers as closures, and replace unsourced
precedent claims on the bank and argument pages.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-23 16:02:27 -06:00
jheapsandClaude Opus 5.5 4e686aaf72 Socialism: answer the horizon problem with reserve rules, not share sales
Add the-long-run.md: the Furubotn-Pejovich worry, the French and
Italian evidence, the plywood "failure of success", and four rules:
the reserve takes its share of pay above the going rate, asset-sale
gains go to the reserve, qualifying development can be spread over
five years with abandonment charged to those who chose it, and
accounts earn Treasury+2 only while at risk. Update capital accounts,
worker ownership, the pilot's measures, open problems and the index.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-23 15:32:19 -06:00
jheapsandClaude Opus 5.5 a75c24fcf2 Merge unmerged socialism work back onto master
PR #123 was merged before 56c041f was pushed; bring it back in.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-23 15:22:12 -06:00
jheapsandClaude Opus 5.5 56c041f405 Socialism: member capital accounts and the founder problem in numbers
Add capital-accounts.md (profit split, cash share, formula interest,
payout rules, solvency test, savings protections, worked outcomes).
Rewrite founder-problem.md with a discounted co-op vs conventional
comparison, the risk evidence (Hall-Woodward, Hamilton), and bounded
founder tools. Add founder-related measures and safeguards to the
pilot pages, and renumber open problems.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-23 14:51:56 -06:00
jheaps f9cf2752d2 Merge pull request 'Add unlisted /socialism pages for the worker-ownership model' (#123) from feature/socialism into master
Build and Deploy / build-deploy (push) Successful in 1m21s
Reviewed-on: #123
2026-09-23 12:17:16 -06:00
jheapsandClaude Opus 5.5 54f951520a Socialism: spec the pilot in numbers, and guard it against gaming
Add "The Pilot, in Numbers": a one-time $350M, ~400-co-op pilot with
a power-calculated sample size, loan limits, a checked definition of
market rate, two pre-registered deciding measures (fair survival with
a pay and membership guardrail, and a Treasury-rate hurdle for the
loan book), a matched SBA comparison plus a lottery at the margin, a
year-12 review, a fast-track vote on success and a self-executing
wind-down on failure. Built around the FONDES and Venezuela failures.

Add "Guarding the Pilot": rules against hidden-boss shells, capital
claim drains, split borrowers, relabeled rescues, zombie survival,
sabotage, a rigged review, and political favoritism.

Link both from the index, bank, test, capital and lock pages, and add
the pilot's weak points and the federal-vs-state question to Open
Problems.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWPs69B8nQZjtPEUXMKzZx
2026-09-23 10:00:03 -06:00
jheapsandClaude Opus 5.5 64852cf6b7 Socialism: rebuild on the v3 model (co-op bank and lock, no forced conversion)
Replaces the chapters with the v3 foundation: new chapters for the bank,
the lock, the founder problem, the test and intellectual property; removes
conversion, conversion-tax and valuation. Rewrites the overview to lead
with "nobody is forced" and rewords draft-history references for
first-time readers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-23 08:20:05 -06:00
jheapsandClaude Opus 5.5 0340c33bfb Socialism: collect the conversion tax so it stays a tax
Adds The Conversion Tax chapter. The tax is withheld in cash from each
buyout payment, the rate schedule is locked on conversion day, brackets
run on cash actually received, pre-bill gifts are added back to the
lifetime ledger, and above-market side payments to former owners count
as proceeds. It explains why taking the tax in kind was rejected
(Koontz, Horne, Tyler). It also corrects the 1950s precedent (capital
gains were capped at 25%) and states plainly how the recycled tax
lowers what workers pay. Open problems now cover proceeds vs gains,
treaty overrides and recession cyclicality.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GsSJ98117VXm9t7rBra5NX
2026-09-23 08:10:48 -06:00
jheapsandClaude Opus 5.5 b1ed1b9b13 Socialism: set conversion prices by a rule ladder, not dueling experts
Adds a Valuation chapter. It covers market price, control-size arm's-length
sales, and a formula whose industry multiples come from real acquisitions
and whose profit figures are cleaned of related-party games. Disputes stay
in court (Monongahela), but each side files a final number, costs follow
reasonableness, inflated claims draw a penalty, and workers get an
independent public valuation counsel. Conversion brackets now apply to
lifetime proceeds and look through foundations and trusts. Open problems
are updated for the anticipation discount and conversion-tax durability.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GsSJ98117VXm9t7rBra5NX
2026-09-23 05:01:56 -06:00
jheapsandClaude Opus 5.5 e8f930e9fc Add unlisted /socialism pages for the worker-ownership model
Renders the markdown documents in Content/Socialism: a landing overview at
/socialism and chapters at /socialism/{slug}, with a sectioned sidebar,
on-page table of contents, prev/next paging and cross-document links.
Pages are noindex and nothing on the site links to them. Underscore-prefixed
files (founding draft, progress log) are never served, deployed or committed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-23 03:10:35 -06:00
jheaps 3326557043 Merge pull request 'Hold'em: give the seat holding the dealer button its own middle back' (#121) from fix/holdem-dealer-chip-eats-its-seat into master
Build and Deploy / build-deploy (push) Successful in 1m12s
Reviewed-on: #121
2026-09-09 15:18:47 -06:00
jheapsandClaude Opus 5 edf159325b Give the seat holding the button its own middle back
On a fresh table the button is on seat 1, and seat 1 is where the first player
goes. Tapping it did nothing.

The dealer chip is a 22px disc, and a 22px disc is half the 44px a fingertip
needs, so it carried an invisible pad to catch the tap. The pad was moved twice
looking for somewhere to grow that wasn't already spoken for: outward first,
where it hung over the seat next door and gave that seat the button; then
inward, over "its own seat, which is a large target that can spare the corner".

The seat could not spare it. Measured on a 390px phone, a 72x46 puck: the pad
ran from the puck's own centre to its right edge and from its top to 3px off its
bottom, and the chip's handler calls stopPropagation. The seat holding the
button kept 52% of its face — 130 of 276 sampled points went to the chip, its
exact middle among them. Every other seat kept 97%. The button moves every hand,
so the dead puck went round the table with it.

And the chip it went to had nothing to say. Its only answer is "give the button
to this seat", and this is the seat that has it. The seat menu already knew that
and hides "Give the button" there; mid-hand the same no-op would have taken the
blinds off the felt and re-posted the same chips to announce that nothing
happened.

So the pad is gone rather than moved again — a fixed pad cannot be safe at every
puck size, and no finger reaches any chip now: the ghosts are pointer-events:
none without hover, and the one live chip is this one, which is now inert to the
pointer and still there to read. Giving the button is the long-press menu on
touch and the revealed ghost on a mouse, both unchanged. makeDealer returns
early on the seat that already has it, so the keyboard can't re-post blinds to
move the button nowhere either.

The sweep had a check for exactly this — "a seat's centre belongs to that seat",
written when the pad was reaching outward — and it ran clean through all of it,
because `seat.contains(hit)` waves through anything inside the seat and the chip
is inside the seat. The check written for this bug could not see the second half
of it. A control inside a seat is a rival for the tap now, and against the old
stylesheet it fails seven scenarios.

982 unit tests, 6 of them new; the layout sweep is 320 audits with the one
pre-existing failure (a $1,300 chip behind seat 11 on a twelve-handed 360x640
all-in).

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-09 15:10:55 -06:00
jheaps 6e7b047d3e Merge pull request 'Hold'em dealer: a table that reads its own showdown, and a tool that is there with no signal' (#120) from polish/holdem-game-night-ready into master
Build and Deploy / build-deploy (push) Successful in 1m14s
Reviewed-on: #120
2026-09-09 14:23:50 -06:00
jheapsandClaude Opus 5 171e878262 Give the felt back the sixth of the phone the title was holding
The header stood 120px tall on a 390x844 phone and only 46 of that was a
control. The other 74 were a 22px page title with a gold rule under it, a back
link, and a gold "Hand #1" pill — three things read once, above a screen whose
whole subject is the green table underneath. Sixteen percent of the window,
spent before the felt started.

A phone held sideways had already solved this. The landscape block clips the
title and puts everything on one row, because at 390px tall there was no other
option. Upright never got the same answer, and upright is how this tool is
held. It is 44px now, the table starts at 78px instead of 152, and the felt
went from 496px to 570 — about fifteen percent more table on the same screen.

Nothing left the page:

- The title is still the document's <h1>, clipped rather than removed, so it is
  still the heading a screen reader lands on and still what the browser tab and
  the installed app are called.
- The back link keeps a 44px box and loses only the word "Casino", which is what
  every phone does with a back link. The name a screen reader gets is on the <a>
  either way, and it contains the visible word on the sizes that still show it.
- The hand number moved to the action bar's status row, in front of the street.
  That row already says what hand you are in — the street, the pot, the player
  count — and this is the fourth fact of the same kind; a counter read after the
  fact had no business holding a line of the header all night. It costs the bar
  nothing: the five audit scenarios that report the bar's height report the same
  249, 227, 203, 227 and 227 pixels as before.

The three buttons take the width the title gave up rather than shrink-wrapping
to the right of it, which is 26px of extra target each. `flex: 1 1 0` was wrong
here — it divides the row into equal thirds and squeezes "Next Hand ⟳", 105px of
text, into 99, and these are nowrap, so what gives is the label. An `auto` basis
makes the label the floor and the spare width the bonus. On the narrowest phone
in the sweep the arrow and all three buttons come to 323px of 336.

The arrow was 32px on the first pass and the layout sweep caught it at 31x43 on
every touch viewport there is — 56 scenarios, one control. The word had been
carrying the width; with the word gone the arrow is the whole target. 44 square.

The sweep is down from five failures to one. Four of the five were seats and bet
chips colliding on a 360px Galaxy S8, and 76px more felt was all they needed.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-09 08:11:21 -06:00
jheapsandClaude Opus 5 0f7213ec2d Open a seat on what is on it, and put the sheet in front of the table
The seat sheet is the second-most-used surface here — every buy-in, every
rename, every correction goes through it — and it was the least finished one.
Seven things were stacked flat at one weight and all open at once. Measured on
a 390px phone it came to 594px and scrolled, and the three controls a dealer
touches on nearly every visit were 132px of that. The chip counter alone was
174px, the tallest single thing in the sheet, for a job most tables do once a
night or never; the two hole-card slots were 62px of dashed grey on every table
that never asked to be shown a card.

Cards, the contribution and the custom fields are behind a fold now, and which
of the three stand open is a question about the seat rather than a setting: a
seat holding cards opens its cards, a seat with chips counted opens its count,
a seat with a note on it opens its notes. A shut section says the answer on its
own row — the cards, the total, how many notes — so nothing already entered is
further away than it was. An empty chair is 419px and does not scroll; a seat
with everything filled in is 625 and opens all three.

The fields have the width the sheet always had. `.fieldRow input` caps at 140px,
which is right in a 320px sidebar of settings and wrong in a sheet holding a
whole phone: "Name" sat against the left edge with 203px of nothing between it
and a field a third of the row wide. Same 44px control, 300px instead of 140.

The chip counter's values are text. They were a `title` attribute, which is a
tooltip, which is nothing on the device this is used on — five identical number
boxes told apart by five coloured dots. It also left the wrapping <label> with
no text in it, so they had no accessible name either.

And the sheet has a scrim. It was the only one of the tool's seven overlays
without one, while covering the felt from 250px down and the action bar with
it — modal in everything but appearance. What stayed lit above it was the
header, with Misdeal, Next Hand and Resolve Pot live under a thumb reaching for
a name field. It dismisses on a tap and on Escape now, like the other six; a
card picker opened from inside still takes Escape first. In landscape the scrim
stops where the sheet stops, so Fold, Call and Raise keep the rail.

It is put away with visibility rather than opacity, which is the same lesson the
sheet above it already learned about transforms — and the layout sweep caught it
the same way. At `opacity: 0` a fixed, full-window box is invisible and still
entirely there: bottomFurniture() read it as furniture standing 640px tall at
the bottom of a 640px phone, in every state and on every screen, and asked
scroll-padding to reserve the whole window for it. Ninety-eight scenarios failed
on it; hidden is hidden, and the sweep is back to the five it started with.

The toast steps over the sheet the way it already steps over the action bar.
Both are fixed to the bottom of a phone, and a "Keeping the screen on" toast
was landing squarely on the seat form with its Undo and its × live above the
fields. The height is measured and published, because it changes the moment a
section is unfolded.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-09 07:29:03 -06:00
jheapsandClaude Opus 5 853fcedea0 Measure the text the way an eye reads it, not the way a ratio scores it
The sweep has checked WCAG AA contrast for a long time and the page passed. That
result was worth less than it looked.

WCAG 2's ratio is a luminance gap, and a luminance gap overstates readability once
one colour is near black — which is every reading on a page whose ground is #141414.
Tuning a colour until it clears 4.5:1 there lands on text that passes the audit and
still reads as a smudge across a table in a dim room. Several rules in this
stylesheet said as much in their own comments, one of them "measured 4.5:1 at
#a5a5a5 exactly, so it sits above that": the right instinct through the wrong lens.

The check also could not see half the backgrounds it scored against. It reads
background-color and takes the best contrast of any candidate ancestor, so text on
the felt was scored against the dark page behind the felt. Two plain AA failures sat
in a clean report for as long as the check has existed — the chair number on the
brown rim at 2.65:1, and the pot's own label, gold on green, at 2.66:1.

runContrastAudit.js scores with APCA and gets the background by screenshotting the
page with its text made transparent and reading the pixels, which no gradient or
shadow can fool. The first version walked the DOM and called the gold dealer button
black-on-black at Lc 0.4; that false positive is why it works this way.

Found: 65 of 92 pieces of text below their level, 54 passing AA at the same moment,
worst Lc 28. Now nothing is below Lc 60, nothing fails AA, and the worst is Lc 60.4.

Three fixes worth naming, none of them "make the grey lighter":

- A player's stack was secondary text at Lc 44, under a name in --text at a heavier
  weight. That says a dealer needs the name more than the number; during a hand the
  opposite is true. Hierarchy comes from size and weight now, which is free, rather
  than from fading, which on a dark ground is the most expensive way to say it.
- An empty seat had no background of its own, so the felt decided how readable its
  number was: Lc 28 on the rim, 30 on the green, 33 further in. It has a ground now,
  at 0.62 and not the 0.82 first written — contrast is nearly flat across that range
  and the heavier wash put four dark rectangles on the felt like holes.
- The pot's label sat on the one green in the design. The disc is deeper rather than
  the label bleached, because gold on dark green is the pairing this page is built
  on. Deepening only its outer stop moved it two points: the highlight is at 32%/28%
  and that is exactly where the label sits.

apca.js is checked against the eight published reference pairs, png.js against
round-tripped fixtures for every filter. Four fault injections caught; a fifth
turned out to be a no-op — the Paeth predictor is symmetric in its first two
arguments over all 16,777,216 byte triples, so that swap is untestable, which is
written down rather than papered over with a fixture that appears to catch it.

Still below the strict APCA level: 55, all of them 10-12px labels that would need
white type or a bigger seat puck. Reported, not chased, and said out loud in the
sweep's own notes.

Layout sweep unchanged: 5 FAILs, 14 offender lines, no regressions. 950 tests pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-09 06:40:28 -06:00
jheapsandClaude Opus 5 295d9eff4a Stop a scroll being an edit, and a stack changing without being touched
A focused <input type="number"> is a spinbutton, and a browser lets the mouse
wheel drive it. Measured on the running page at 1280x900 with the sidebar
open: focus the small blind and scroll, $5 becomes $5.50. Focus a seat's stack
and scroll, $500 becomes $501. Both go straight through to localStorage,
because these fields save on `input` like everything else here.

Nothing is announced and nothing looks wrong. The tool has invented a chip, on
a table whose whole purpose is that the chip count is right, and the gesture
that did it was a dealer reaching for another panel.

The rule is narrow on purpose. Every other way of driving a spinbutton is
deliberate -- the arrow keys, the stepper arrows, typing -- so scrolling is
the only one taken away. Verified after: the value holds, one ArrowUp still
moves it, typing still works, and the page still scrolls.

Blur rather than preventDefault. Cancelling the event stops the value moving
and stops the page moving with it, which trades a silent bug for a sidebar
that mysteriously will not scroll -- a problem the dealer can see and cannot
explain, instead of one they cannot see at all.

The decision lives in Money as a predicate beside selectsOnFocus, so the rule
is testable without a browser and main.js only does the blurring.

Two things checked rather than assumed. The tagName guard in the predicate is
decoration: deleting it passes every test, because nothing but an <input>
carries type="number". That is now said in the comment instead of being left
to look load-bearing. And the page's one <select> -- which way the blinds move
-- is not vulnerable: focused, hovered and scrolled 300px in Chromium it stays
where it was, so it is left out of the rule rather than defended against.

916 tests pass, 6 of them new. Four fault injections into the predicate are
caught; a fifth was applied, not caught, and turned out not to be a fault.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-09 05:46:20 -06:00
jheapsandClaude Opus 5 fafbd8df32 Stop the things that actually move for anyone who asked them not to
The stylesheet had honoured prefers-reduced-motion for a while, in the three
places it mattered least: a 2px press-down on a button, a checkbox tick, and
the acting seat's pulse. What none of them covered was motion that moves
something across the screen, which for anyone with a vestibular disorder is
the part that matters. A button dipping under a thumb is not what makes people
ill.

Four things were doing it, and the three largest were in JavaScript where a
stylesheet cannot reach: the seat editor sheet, which slides the full height
of itself up from off-screen every time a dealer taps a chair; the toast; and
three scrollIntoView({ behavior: "smooth" }) calls, the most provocative of
the lot because what moves is the whole viewport rather than one control.

The scrolls go through Render.bringIntoView now, which reads the setting per
call rather than at page load -- a dealer who turns it on mid-session should
be taken seriously without reloading a table they cannot afford to reload. The
toast keeps its fade and loses its travel, because something that appears
already in place reads as a rendering glitch rather than as news. The sheet's
visibility delay flips at once, or it would leave a sheet that has already
jumped away still painted for a quarter of a second.

The press-down block held nine :active selectors and looked deliberate. It was
written to stop a stuck press state when a tap turns into a scroll, and had
reached for the reduced-motion query as the nearest proxy -- so it covered
.dealerChip and .raiseQuickBtn while missing .actionBtn, the class on Fold,
Call, Raise and Post blinds & deal. Read as its own rule the membership is not
a judgement call: every :hover and :active transform in the file belongs
there. There are sixteen. Nine were listed.

The scan that found the other seven took two goes. A line-by-line version
walked straight past a deliberately broken build, because the injected rule
was written on one line and neither "opens a rule" nor "is a transform"
matched. Parsing blocks caught it -- and then failed on the clean file, which
is how .toastAction, .toastClose and .potWizardChoice came to light: a
multi-line group had been truncated to its last line, so nothing had ever
looked at them.

910 tests pass, 16 of them new. Six script and stylesheet fault injections are
each caught, and the reduced/no-preference split is verified in a real browser
against both context settings.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-09 05:21:07 -06:00
jheapsandClaude Opus 5 0d878a176d Say in the sweep's own notes where the last five failures actually live
315 of 320 audits pass. The five that do not are one root cause on the two
smallest screens: twelve seats want about 60px of footprint each and the ring
has 44px of rim to give them, in the two states that make the bar tallest.

Written down rather than left to be rediscovered, because closing it is a
design decision -- drop the bet chips at extreme crowding the way the tight
tier already drops the hole cards, cap the seat count by viewport, or let the
felt scroll -- and each trades something a dealer can see.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-09 05:03:33 -06:00
jheapsandClaude Opus 5 7216f3da4d Stop a wrapped label costing twelve seats their room
The sweep had carried the same residual for rounds: on the two smallest
phones with twelve players, seats painting over each other's names and
stacks. It read like a geometry problem. It was a wrapped label.

"ALL IN" is the only thing that ever lands on the stack line with a space in
it. A stack figure has no spaces, so it can only overflow, and the
puckAmountLong/Huge sizes exist to stop that; nothing had asked what the line
does when it can break. Measured at 360x640 with twelve players all-in, it
wanted 36px and had 34, broke onto a second line, and an 11px line became 22.

That is not a typography complaint, which is why it survived: it looks fine.
The puck went from 55px tall to 66, and puck height is what the ring is short
of -- at the ends of an ellipse consecutive seats are separated vertically, so
a puck 11px taller is a puck 11px further into its neighbour. The layout
search then did exactly what it is written to do: tried every width it has,
found none that cleared, and fell back to the narrowest, which is also the one
that wraps hardest.

So the rule every design system gives a badge applies here too: one line,
always. Not wrapping is only half of it -- a line that cannot wrap and still
does not fit paints over the seat next door instead -- so on the tight tier
the mark gives up its letter-spacing, 3.6px of the 36 it wanted, and keeps the
warm colour and the bold weight that do the same job.

Halving the overlap did not clear it, and the rest of the shortage was not on
the felt. The sweep had been saying so in a warning rather than a failure: at
360x640 the bar was 267px of a 640px window, 42%, against its own "persistent
chrome under 30%". Two things in it were being shown when they could not be
used. The side-pot list is decision support, and during a run-out there is no
decision to support -- it collapses to the main pot and one line for the rest.
The status row said the player count twice in the same breath, as a chip and
again inside the run-out sentence beside it.

Measured: bar 267 -> 249 on a Galaxy S8 and 267 -> 227 on an iPhone SE, felt
243 -> 283 there, worst seat overlap 34px -> 4px. Across the sweep, overlap
offender lines 33 -> 9, chips behind seats 30 -> 5, failing audits 6 -> 5, and
the iPhone SE's seat-text overlap gone entirely. No new failures.

894 tests pass, 9 of them new. Seven fault injections are each caught.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-09 05:01:56 -06:00
jheapsandClaude Opus 5 e2bb935b43 Be there when the signal, the screen and the storage are not
Three failures that only ever happen at a table, none of which show up on a
desk with a good connection and a plugged-in laptop.

The offline promise depended on how the URL was spelled. Razor Pages routing
is case-insensitive, so /Casino/TexasHoldem serves the identical page; a
service worker's scope is matched as a plain string prefix, so a worker scoped
to /casino/ does not control it. The registration succeeded, the worker
activated, nothing was logged, and the cache stayed completely empty --
because the precache list was handed to navigator.serviceWorker.ready, which
resolves with the registration controlling the page and so never resolves at
all outside its scope. A dealer who opened that URL to load the tool for the
night had loaded nothing.

One spelling wins now, via a 301 scoped to /casino rather than applied
site-wide: every file under wwwroot/casino and wwwroot/images/casino is
already lowercase, so lowercasing there cannot miss a real file on a
case-sensitive filesystem. That guarantee does not hold for the rest of the
site, and /chessHub would break. The handshake is fixed rather than left to be
saved by the redirect -- it takes the worker off the registration itself.

The manifest icons were never cached, because the shell list is read off the
DOM and the DOM never mentions them. They are read out of the manifest now,
cache-first so an offline launch stops logging ERR_INTERNET_DISCONNECTED, and
the link sweep picks up apple-touch-icon, which is the only one iOS reads.

The screen slept mid-hand. A wake lock is held while betting is open and
dropped the moment it is not: through a whole street of renders it takes
exactly one, two syncs in a tick take exactly one, a hand that ends inside the
request leaves nothing held, and the browser taking it back leaves us able to
take it again. Announced once with a Turn off button rather than a permanent
header indicator; a setting after that.

And localStorage is evictable, which the Save & reset panel now says out loud.
persist() is asked for on Post blinds & deal, because every browser wants that
request to come from something the dealer did.

885 tests pass, 18 of them new. Five fault injections into the wake lock are
each caught. One of the new tests was green for the wrong reason first:
globalThis.navigator = fake does nothing, because Node has defined navigator
as a getter since v21.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-09 04:31:45 -06:00
jheapsandClaude Opus 5 75ec028c3e Ask for the hands at the showdown, the way the streets are already asked for
The card feature had two halves and only one of them ever asked a question. Turn the
setting on and every street opens a sheet for the cards coming off the deck — that
worked, on a phone and on a desk, in a normal hand and in an all-in run-out. The
other half never asked at all. A dealer who entered all five community cards, pressed
"Showdown — resolve pot", and got a wizard saying

    Christopher, Mike from work, Alexandra and Bob haven't turned over, so who
    won is still yours to say.

with nothing in that sheet to turn anybody over with. The only route in was to close
the wizard, tap a seat puck, find the two card slots in the seat editor, do it four
times and re-open the wizard, with the pot on the table and four people waiting. The
one moment the tool is certain to know the hands is the moment it stopped asking.

So the showdown button asks. The sheet opens on whoever the bar has just named as
showing first, takes their two cards, and moves round the table from there; hands
already entered are skipped, because a hand entered is a question already answered.
Four sheets later the wizard opens reading "Chris wins $40 with three of a kind,
nines" and its button says "Pay Chris" instead of "Confirm Winner(s)".

Leaving the sheet stops the asking rather than skipping a player: closing it says the
tool has the wrong end of the stick, and the answer to that is to get out of the way.
It isn't a dead end — the wizard still names whoever didn't turn over, the bar still
says "Showdown — resolve pot", and pressing it again resumes at the player the dealer
stopped on, because the list is read off the cards on the table rather than remembered.

CardPicker.close() takes a reason so the chain can tell "that sheet filled its last
slot" from "the dealer left". Two callers pass nothing and don't care.

The setting kept its behaviour and lost its jargon: "Track the board and read the
showdown" was the feature's name rather than what turning it on does to your evening.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-09 04:10:53 -06:00
jheapsandClaude Opus 5 e90bcccada Spend the accent on the one thing to do next, not on two at once
The green is a promise: this is the step. Measured over a whole hand — seventeen
steps — it was on two controls at thirteen of them, the bar's Call and the header's
Resolve Pot painting the identical gradient at opposite ends of a phone, and on the
header alone at the other four while the bar held the only button there was to press.
At showdown the most consequential tap in the hand, "Showdown — resolve pot", was
blue-grey while the corner shortcut was green.

So the accent moves to .actionAdvance, which is always the next step by definition,
and comes off the header shortcut, which never is. Twenty-two measured steps of a
real hand now show exactly one.

The sweep checks it: oneNextStep() counts the accent-coloured controls that are
actually on screen, scoped to an open sheet when there is one. Six broken versions
were run at it, and the two that took three goes to catch were both the same mistake
in the injection rather than a hole in the check — a button painted green with the
panel shut isn't rendered, and one painted green below the fold is deliberately
ignored. Scrolled into view it is named alongside the bar's Call, and goes clean
again when the green comes off.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-09 04:10:53 -06:00
jheaps 14fb68dc04 Merge pull request 'Hold'em dealer: making the table and the bar fit the screen they are on' (#119) from polish/holdem-round-next into master
Build and Deploy / build-deploy (push) Successful in 1m40s
Reviewed-on: #119
2026-09-08 19:08:12 -06:00
jheapsandClaude Opus 5 4a1cb842cb Say on each shut sidebar panel the one thing you'd open it for
The sidebar is eight <details>, and every one measured exactly 60px shut with
nothing on it but its own label — 480px of a phone's screen, and 480px of a 660px
desktop column, spent saying "Standings", "Hands", "Waiting list". Whether the box
balances, whether anyone is waiting for a seat, how many hands have been played:
the page had all three worked out already, and charged a tap for each of them, on
rows two thirds of which were empty.

That is the disclosure pattern's named failure — a header you have to open to find
out whether it was worth opening — and the answer the design systems give is the
same one: put the status on the header. The panel keeps the sentence. "Off by
$300" is the alarm; which seat is holding chips nobody wrote down is a paragraph,
and that is the half that belongs behind the tap.

Two rules, and panelGlance.test.js checks both. A glance appears only when it has
something a dealer would act on — eight rows that always say something are eight
rows that say nothing again, and a row reading "Nobody waiting" all night is the
clutter this went in to remove. And a glance never says something the panel under
it would contradict: it is read off the same state, never off the panel's DOM,
which for a shut <details> isn't rendered at all.

Absolutely positioned rather than floated. A <summary> only draws its disclosure
triangle while it is display: list-item, so it cannot be a flex row; the float
that replaced it kept the row at 60px but aligned to the label's line box, and a
12px string in a box sized for 15px text rode 2.5px low on all six panels. Out of
flow is also the smaller promise — an absolutely positioned box cannot make its
row taller at all.

Three paths deliberately skip renderAll, because it would take the field out from
under whoever is typing in it, and all three move something a glance reports. The
notes textarea, the chip legend's value fields and commitSeatChange each call
renderPanelGlances on their own instead; without that the note you had just typed
was summarised by the note before it until the next tap landed. A space between
the label and the glance, too: the summary is announced as one button, and without
it a screen reader was given "Table Setup9 seats".

Costs 0.08ms of a 4.79ms renderAll on a twelve-handed phone. Four injected faults
in the wording are caught one test each.

glancesFitTheirRows took three goes. The first asked only whether a row had grown
taller than a bare one, and a glance floated back into flow and allowed to wrap
sailed straight through it, because a float does not expand the box it sits in.
The second was caught by the sweep on its first clean run — 94 failing configs, 91
of the offenders this check's own: it takes the shortest row as its idea of a bare
one, and the Keyboard panel is display: none under pointer: coarse, so on every
viewport visited with a thumb the bar was a row that wasn't drawn, and all seven
real rows read as grown. Rows that aren't rendered aren't rows. The four broken
layouts are re-run with that panel hidden and each is still caught.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-06 01:56:58 -06:00
jheapsandClaude Opus 5 e7ddff2fed Let a large felt have larger seats, and the type that goes with them
The felt grows with the window and the pucks did not: 96px was the only
ceiling there was. Nine-handed, the seats covered 19% of the felt on a phone,
11% on a 1280px desktop and 8% at 1440px and beyond — and the stack figure a
dealer reads sat at 10px on a 2560px monitor, the same 10px as on a 360px
phone, well under every published floor for text meant to be read.

Two things stop this costing more than it buys.

Every candidate width is a forced layout. Simply raising the ceiling took the
layout pass from 24ms to 73ms twelve-handed on a desktop, so a screen that
cannot use the wider widths must not measure them: the extra pass is offered
only when the arithmetic already says the seats have room to spare, and a phone
measures exactly the ten widths, in the same order, that it measured before.
Timed with the order alternated so neither carries the warm-up: 10.7ms against
10.4ms on a phone, and +3 to +6ms where the widths are actually used.

And a wide puck on a narrow felt is worse than a small one. The first version
of this let phones through, and a 144px puck on an iPhone's 278px ring is half
the width of the table: six-handed, all six seats sat over the rail by 15px
against 5px before, reading as a stack of cards rather than a table with people
around it. So the ceiling is a fifth of the felt's shorter span, capped at 144.
Five is measured rather than chosen — the shorter span is 233-303px on every
phone in the sweep including sideways and 617-714px on tablets and desktops, so
any divisor between 3.2 and 6.4 separates them — and the test pins both ends.

Above the ordinary ceiling the puck takes the larger type as well, which is the
point of the extra width. Nine-handed: an iPad goes 96px/12px to 134px/15px, a
1440px desktop 96px/12px to 140px/15px, and the seats go from 8% of the felt to
12%. Every phone, at every seat count, is untouched.

The sweep is byte-identical to before this change: same six configurations,
same 63 offenders, empty diff.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-06 00:56:59 -06:00
jheapsandClaude Opus 5 f8ed4abc85 Stop two empty rows in every seat puck setting the size of the table
A seat puck carries the name, the hole cards, the amount that player has put
in, and their stack. Two of those are empty most of the time and both held
their space anyway.

The amount pill was worse than empty space. It has a background, a pill radius
and side padding, so with nothing in it, it painted an 8x11px green lozenge —
measured on seven of nine seats at an ordinary table. One of those is a
component holding its place; nine is a rash, and a dealer has to learn to
ignore a colour that on the other two seats means "this player has money out".

The space was not free either. seatRingRadii sizes the ring from the tallest
puck on it, so two rows nobody had filled were setting how far apart the whole
table had to sit. Across 21 tables — seven viewports, six, nine and twelve
handed — every single puck comes back shorter and six come back wider: an
iPhone 14 nine-handed 78x88 -> 78x63, twelve-handed 44x65 -> 66x55, which is the
one name in the set that was clipped and now is not.

The collapse is only safe because the layout cache moved with it.
resolveSeatLayout measures ten candidate widths, each a forced layout, against
seatShapeSignature() — which listed the names and the dealer seat, everything
that could wrap a name onto a second line. It did not mention these two rows,
because while they always held their space they could never change a puck's
height. Now they can, so the signature names them: booleans over the whole
table, because measuredSeatBox takes the tallest puck on the ring, and because
a per-seat key would be correct and would throw the cache away on every tap.

The ring settles by 24-26px when the blinds go in — and it settled by 23-25px
there before this round too, since the action bar changes height at that moment
and the felt is measured against it. The one place it moves is a place it
already moved.

uiAudit gains emptyDecoration(): nothing on the felt may paint a background or
a border with no content in it. Its one exemption is on the list because the
check reported it rather than because it was predicted — .betChipDisc is a
circle filled with the denomination colour of the chip beside it, so the colour
is the content. Three others were written from memory first and matched no
element on the page at all.

The sweep is unchanged at 6 failing configurations and 63 offenders. All four
table failures are the all-in shape, where every pill is filled and the
collapse buys nothing; the ordinary tables this helps were already passing, so
what moved is room a name has rather than whether anything was covered.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-06 00:09:25 -06:00
jheapsandClaude Opus 5 942919f3f7 Stop the side-pot list growing the action bar over the felt
The bar is fixed over the table, so its height is the felt's budget — and it
carried a pot breakdown that drew one row per all-in with no ceiling. On a
360x640 phone: five players idle put the bar at 151px, six all-in at 303px,
twelve all-in at 411px. That is 64% of the screen, leaving 96px of felt to draw
twelve seats in, and at that size the seats stop being a table: five pairs
collapse onto each other's centres, ten hang past the rail and a dozen labels
sit completely covered.

Past four layers the bar now draws the main pot, one line for everything
between, and the newest side pot. The two ends are the live ones for the same
reason — side pots are paid from the newest back down to the main pot, so the
top layer is the next thing that happens and the main pot is the last, and
those are the two a dealer says out loud while the chips are stacked. The
middle is the rest of that count-down, wanted at showdown, which is the pot
wizard: same builder, no cap, in a dialog that scrolls. The elided line carries
the count and the money, so the visible rows still add to the announced pot.

Nothing in the sweep could see any of this, and the reason is worth keeping.
Every check asks whether an element is covered or clipped; none asked how much
room the covering element had taken. But the deeper miss was the sweep itself —
all 294 audits drove their table with Call and Check, so no table it ever
measured had a player all-in, and the one thing on the bar whose height depends
on the table was empty in every measurement. There is now a fourth table shape,
twelve players shoved in for twelve different amounts, and furnitureBudget()
measures the fixed band against the window: failing over half of it, warning
over the 30% that persistent chrome is usually held to.

Galaxy S8, twelve all-in: bar 411px -> 267px, felt 96px -> 192px, 91 layout
offenders -> 25. iPhone SE: 73 -> 10. The rest is the seat ring on a short
phone, which the sweep can now see.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-05 23:34:51 -06:00
jheapsandClaude Opus 5 02c2a034cb Stop the fixed bar and the toast sitting on the keyboard's focus
WCAG 2.2 SC 2.4.11 asks that a focused control not be completely hidden
by something the page has put on top of it. The bar is fixed over the
bottom and is not in the flow, so the browser does not know it is there:
Tab to a control near the bottom and it is scrolled flush against the
edge of the window, under the bar, and the browser considers it done.

Tabbing the whole page measured 16 stops resting completely covered on a
390x844 phone and 27 more partly, plus four under a toast on the desktop.
It is now 0 and 0 on desktop, phone and a phone on its side — clean
against 2.4.12 (AAA) as well, which allows no overlap at all.

Three things were wrong and they needed different answers.

scroll-padding-bottom on the scroll container, sized from the bar's own
published height and the toast's, handles the case where the browser has
to scroll to reach the control. That is half of it, and the half that
looks like all of it: a control the browser already considers visible is
one it will not scroll to, and a fixed overlay on top does not enter into
that decision. Measured — tabbing to Common rulings with a toast up left
it 100% covered and scrollIntoView({block:"nearest"}) moved nothing. So a
focusin handler measures the overlap and scrolls by it, and when the page
has no room left (26px against 94px wanted) the toast gives way instead.
Sizing the page padding for the toast would have fixed that by making the
page grow and shrink 64px whenever anything was announced.

The page's room to scroll past the bar was 9rem on the sidebar alone,
which left the table's column — most of a desktop — with none. It is now
on the whole app and taken from the bar's real height, so it is also 0 in
the layout where the bar is a rail down the side.

And the seat editor's bottom sheet was put away with a transform, which
moves a thing without putting it away: its Done button sat at y=850 on an
844px screen, focusable, announced, and impossible to scroll to, because
a fixed element does not move when the page does. visibility:hidden puts
it away, transitioned so the slide-out still runs.

uiAudit.js gained focusHasRoom() and focusablesReachable() for the two
static halves. Audit unchanged at 2 failing configurations and 3
offenders, both pre-existing. 805 unit tests pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-05 22:57:58 -06:00
jheapsandClaude Opus 5 cd2db6346e Stop a chip colour taking the table's money with it, quietly
Deleting a chip colour was one click of an unlabelled 24px button, and it
was never a change to a legend. computeContribution() prices a seat by
running the legend over that seat's chip counts, so a colour that leaves
takes every chip counted in it off the table.

Measured in a browser on a three-handed table: deleting the $500 row took
$22,500 of $26,874 with it. No confirmation, nothing said afterwards, and
no way back — a colour added again is a new colour with a new id, while
the counts stay keyed to the one that left. The counts were left behind
too, so the saved file carried money nothing could price and nothing
could show.

Now: a colour nobody has counted still goes on one click, and one that is
holding money asks first, naming the amount, the chips and the seats, and
saying that Undo will put it back while adding the colour again will not.
Conditional on purpose — a confirmation that fires every time is one
nobody reads by the third night, and four others already compete for that
attention here. Removal goes through runUndoable, and the counts go with
the colour so the file matches the screen.

The number in that warning is the difference between pricing every seat
with the colour and without it, not chips times value. The obvious
arithmetic is wrong both ways: a seat with a typed amount is never priced
from its counts, and a seat whose chips all leave falls through to the
table's standing ante rather than to zero. A randomised check over 2,000
tables asserts the warning equals what the table then does, and it found
a third case — fourteen $1 chips against a $200 ante, where removing the
colour puts the table UP $186. The wording says so in that direction too.

The × also had no accessible name, so uiAudit.js gained controlNames():
every control needs a name containing a letter or a digit, resolved the
way the accessibility tree resolves it. A symbol is not a name. Taking
the new aria-label off again makes it report all five buttons.

Audit unchanged at 2 failing configurations and 3 offenders, both
pre-existing. 805 unit tests pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-05 22:26:16 -06:00
jheapsandClaude Opus 5 6eb97c8a35 Give the sidebar eight names instead of five and a cupboard
"Session" was a container word rather than a name. It held the standings,
a waiting list, the hand log and the save/load/reset buttons — a topic, a
task, a log and three utilities under one label, which is a navigation
layer with more than one logic in it.

It measured 1206px and 103 visible elements, against 555 and 29 for the
next biggest, and "The table itself" sat 1052px down inside it. Reaching
Reset table meant opening a panel called Session and scrolling past the
money and the log to get to it.

It is now Standings, Waiting list, Hands and Save & reset, each named for
the thing a dealer came looking for, in the order of a night: set the
table up, watch the money, fill the seats, look back at a hand — and,
last and apart, the two buttons that end it. Reset table goes from 1052px
inside a panel called Session to 62px inside one whose label says what it
does. The biggest panel drops from 1206px to 607px. The four subheads
that used to carry those names are gone, because the summary says it now.

Every panel name is also a heading. A <summary> is announced as a button,
so the sidebar's names — its whole navigation — were unreachable by
heading, and splitting Session would have removed the only four headings
down there. The outline is now h1 page, h2 per panel, h3 for the two
subheads that remain. Visually identical: the h2 gives up everything a
heading brings with it.

The cost is 216px more collapsed summaries at rest, three more one-line
labels. Audit unchanged at 2 failing configurations and 3 offenders, both
pre-existing. 795 unit tests pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-05 21:52:34 -06:00