| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Two comments on r/rails, both negative, and the first one was right: a counter is the worst possible lead example, because one line of SQL does it. The README opened with exactly that counter, so the first thing a skeptical reader saw was the argument against installing anything. The lead is now the ticket sale from the homepage: 100 seats, a hold, a ten-minute expiry that frees the seat, and a live count. It is the smallest example that needs three things from one number, and the three things are the actual argument. A counter needs one. The objection now gets answered where it is asked instead of at line 1245 of a 1362-line file. A new section concedes with_lock first, then makes the case on scope rather than discipline: a lock is scoped to one transaction, on one connection, in one process, and any expires_at or scheduled_at column is evidence the critical section already outlived it. What follows that column is a sweeper, and then a race between the sweeper and the next writer. Arguing that someone might forget the lock would lose, because the reader answers "so remember". A short worth-it and not-worth-it section moves into the first screen, the Comparisons table gains the row people actually reach for, and the reactive section concedes broadcast_replace_to before explaining where push-after-write drops an update. The homepage copy stored Time.current in actor state. State normalizes to JSON native types only, so that reserve never commits. The README version stores an integer.
The reactive paragraph claimed the fragment is re-rendered once per change from the saving turn. The saving turn inserts the broadcast row in the same transaction as the state change, which is the part that answers push-after-write. Delivery is a separate worker that retries, so "once per change" promised something the runtime does not. The claim now matches docs/correctness.md: the row retries until it succeeds, Cable ignores an older (instance_id, state_revision) pair after a newer one, and the guarantee is that a viewer cannot end up on an older number. Greptile raised this on the JavaScript counterpart. The same sentence was wrong here.
The README had become a manual pasted into the front door: 1466 lines, with sections that docs/ already covers in more depth than the README did. Length is not thoroughness. A reader who bounces at screen three never reaches the reference material that justified the length. Removed what docs/ owns outright. The dashboard section restated docs/dashboard.md, and Reactive ERB restated docs/realtime.md for 167 lines, so both are now the argument, the smallest working example, and a link. The Cloudflare capability subsections collapse to one paragraph each, keeping the mapping table and the Solid Queue ordering comparison, which live nowhere else. Removed what the README said twice. "When to use it" and the new "Is it worth installing here?" were the same section 1100 lines apart; the early one now carries the domain list. Invoking an object explained per-adapter lock deadlines that docs/correctness.md documents properly. Removed what was no longer true. The Status list claimed "Implemented and tested in 0.4" at version 0.14.1 and said the supervisor does not replace a crashed role, which docs/roadmap.md lists as implemented. Kept every fact that has no other home: the keyed-alarm section, the configuration defaults table, the CLI worker-count flags, connects_to for a separate actor database, the keyword-disambiguation rule, InvalidRejectionCode, and the turbo-rails dependency.
The README was still twice the size of its JavaScript sibling at 1175 lines, and the difference was not extra capability. It was reference material sitting on the front door: every reminder rule, every configuration default, every CLI flag, and two registration examples, all of which a reader passes on the way to deciding whether to install anything. Moved rather than deleted, because most of it had no other home. docs/reminders.md is new and holds the keyed-alarm rules, the composed name limit, the one-alarm-for-a-whole-queue drain, and reconciliation for self-scheduling actors; docs/architecture.md now links there instead of into a README heading. docs/operations.md gains the configuration defaults table, the worker-count flags, the upgrade sequence, the RuboCop exclusion, the Sorbet note, and the extension-component contract. docs/architecture.md gains register_effect and register_commit_action with their signatures. Deleted only what was said twice. The table of contents duplicated the headings GitHub already renders. A second example actor demonstrated what the ticket sale demonstrates. Actor identity was three paragraphs that fit in one, inside the section that defines an actor. The README is now 528 lines and keeps what only it can say: the ticket sale, the lock argument, who should not install this, the Durable Objects mapping, and one worked example per capability with a link to the guide behind it.
The lead actor declared observable :remaining and the ERB beside it rendered that value in a span while a component observed :holds. Neither worked. A scalar span raises ArgumentError unless the observable is declared broadcast: :value, and a component dependency must itself be a declared observable or ComponentRegistration raises UnknownComponentDependency. remaining now carries broadcast: :value and holds is declared, which is also the clearer teaching example: one observable opts into sending its value to every authorized subscriber, the other only invalidates, which is what the paragraph under it describes. Verified against this branch rather than read: broadcasts_observable_value? is true for remaining, holds resolves as a dependency, and reserve, duplicate reserve, and expire behave as the text claims.
| Back | FazBrowse Home | New Git URL |
Framing fix to the first screen and the fit sections. No API documentation was
rewritten, and no runtime code changed.
Why
The gem was posted to r/rails: 3.8K views, two comments, both negative. One
said the lead example was "the worst example you can come up for a gem, because
one can do that in one line of SQL." That was correct. The README opened with a
counter that incremented an integer, so the first thing a skeptical reader saw
was the argument against installing anything.
The homepage was rewritten in response. This ports what worked there.
The lead example
A counter needs one thing from one number, and SQL already does it. The ticket
sale needs three:
The first is one line of SQL. The second is an expires_at column plus a cron
job. The third is a broadcast on every path that changes the number. The
combination is the argument, and the example now shows all three in one class:
the guard, schedule(at: 10.minutes.from_now, key: buyer), and
observable :remaining.
Answering the objection where it is asked
## Why not just use with_lock? now sits in the first screen. It concedes
first, in the first sentence, because a concession is what buys the right to be
believed in the paragraph after it. Then it argues scope rather than discipline:
Arguing "you might forget the lock" would lose that exchange, because the reader
answers "so remember" and they are right.
Also
should not use this, specifically. A section at line 1245 of a 1362-line file
does not exist.
SELECT ... FOR UPDATE.
README, which was ahead of this one on honesty, and given the reason the limit
bites rather than only asserting it.
two gaps: two pushes can arrive out of order, and a push is not part of the
save.
One bug found while verifying
The homepage example stores Time.current in actor state. Serialization
normalizes to JSON native types and raises InvalidPayload for anything else,
so that reserve never commits; the caller waits out its timeout instead:
actor invocation timed out after 5 seconds for TicketSale("event-42").reserve message_id=1 sequence=1 status=ready waiting_on=not_yet_availableThe README version stores Time.now.utc.to_i. solidobjects.dev still has the
broken line and should be corrected separately.
Validation
Every example in the changed sections was executed against this branch, not
read for plausibility:
bundle exec rake (test, standard, rubocop, rbs, steep, security): pass.