Activity Feed

  Dennis Hackethal criticized idea #3597.

How Do Bounties Work?

Bounties let you invite criticism and reward high-quality contributions with real money.

Starting December 23, 2025, select users can run bounties. Anyone can participate.

Bounties are in beta. Expect things to break.

How do I participate?

First, log in or sign up.

Next, browse the list of bounties. Click a bounty’s dollar amount to view its page, review the bountied idea and the terms, and submit a criticism on that idea.

That’s it – you’re in.

How do I get paid?

Each bounty enters a review period roughly five days after it starts (the exact date is shown on the bounty page). The review period lasts 24 hours. During this time, the bounty owner reviews submissions and rejects only those that don’t meet the stated terms.

To be eligible for a payout, all of the following must be true:

  1. Your submission is a direct criticism of the bountied idea.
  2. Your submission has no pending counter-criticisms when the review period begins.
  3. Your submission meets the bounty terms and the site-wide terms.
  4. You’ve connected a Stripe account in good standing before the review period ends.

The bounty owner is never eligible to receive payouts from their own bounty.

Note that counter-criticisms are not constrained by the bounty-specific terms. Only direct criticisms of the bountied idea are.

How much will I get paid?

The bounty amount is prorated among all eligible submissions.

For example, if there are ten eligible criticisms and you contributed two of them, you receive 20% of the bounty.

Fractions of cents are not paid out. Amounts below USD 0.50 are not paid out.

How do I run a bounty?

Click the megaphone button next to an idea (near bookmark, archive, etc.).

Set a bounty amount and write clear terms describing the kinds of criticisms you’re willing to pay for. Then enter your credit card details to authorize the amount plus a 5% bounty fee.

Your card is authorized, not charged, when the bounty starts.

The bounty typically runs for five to seven days, depending on your card’s authorization window. After around five days, a 24-hour review period begins. During this time, review submissions and reject those that don’t meet your terms. Submissions you don’t reject are automatically accepted at the end of the review period and become eligible for payout. Your card is then charged.

If no eligible criticisms are accepted, your card is never charged.

Start a bounty today. Terms apply.

#3597​·​Dennis HackethalOP revised 9 months ago

Superseded by #3841.

  Dennis Hackethal revised idea #5672 and marked it as AI-assisted.

Fix the pending-criticism and Stripe deadlines, explain starting a bounty (⋯ menu, badges), and tidy wording


How Do Bounties Work?

Bounties let you invite criticism and reward high-quality contributions with real money.

Bounties are in beta. Expect things to break.

How do I participate?

Only users with a Beginner, Engager, and Defender badge can participate in bounties. Find a list of all badges and how to get them here.

To participate in a bounty, start by logging in or sign up.

Next, browse the list of bounties. Click a bounty’s dollar amount to view its page, review the bountied idea and the terms, and submit a criticism of that idea.

That’s it – you’re in.

How do I get paid?

The bounty owner reviews submissions for eligibility against his bounty terms.

To be eligible for a payout, all of the following must be true:

  1. Your submission is a direct criticism of the bountied idea.
  2. Your submission has no pending counter-criticisms by the deadline. (For temporary bounties, that’s when the review period ends; for standing bounties, it’s seven days after submission.)
  3. Your submission meets the bounty terms and the site-wide terms.
  4. You’ve connected a Stripe account in good standing before the deadline.
  5. You’ve not contributed funds to the bounty.

The bounty owner is never eligible to receive payouts from their own bounty.

Note that counter-criticisms are not constrained by the bounty-specific terms. Only direct criticisms of the bountied idea are.

How much will I get paid?

For temporary bounties, the amount is prorated among eligible participants based on contribution. For example, if there are ten eligible criticisms and you contributed two of them, you receive 20% of the amount when the bounty ends.

For standing bounties, amounts are assigned on a per-submission basis. For example, funders may indicate that they will pay a total of USD 100 for the first eligible submission, a total of USD 50 for the second eligible submission, and so on. Each eligible submission has its own payout date.

Fractions of cents are not paid out.

How do I run a bounty?

Click the megaphone button next to an idea (near the buttons to bookmark, archive, etc.).

Set a bounty amount and write clear terms describing the kinds of criticisms you’re willing to pay for. Then enter your credit-card details to authorize the amount plus a 5% bounty fee.

Your card is at most authorized, but not charged, when the bounty starts.

A temporary bounty typically runs for five to seven days, depending on your card’s authorization window. You may review submissions during the entire bounty period. Toward the end, a 24-hour grace period begins during which no new submissions can be made but you may continue your review. Reject any submissions that don’t meet your terms. Submissions you don’t reject are automatically accepted at the end of the review period and become eligible for payout. Your card is then charged the full authorization.

A standing bounty runs for as long as funds last. Each submission has its own seven-day review period. Again, reject any submissions that don’t meet your terms. Submissions you don’t reject are automatically accepted seven days after submission. Your card is then charged as indicated in your funding allocation.

If you reject all submissions, your card is never charged.

What’s the difference between a temporary and a standing bounty?

A temporary bounty has a fixed duration, typically between five and seven days. The bounty amount is prorated among eligible participants at the end. Standing bounties, on the other hand, don’t have a fixed duration; they run as long as funds last. Funds are paid out continuously and on a per-submission basis, as described above.

Temporary bounties are ideal when you have limited time and a smaller budget. Standing bounties are ideal for the long term with a larger budget. However, you can mix and match based on your own unique preferences and circumstances: for example, it’s possible to use a larger budget on a temporary bounty.

Can I fund someone else’s bounty?

Yes. Review the bounty terms. If you agree with them, click the ‘Add funding’ button on the bounty page and follow the next steps. At this point, your card is at most authorized but not charged.

The bounty owner will expect you to join them in enforcing their terms. Don’t fund a bounty if you’re not prepared to enforce its terms. Run a separate bounty with your own terms instead. By funding someone else’s bounty, you agree that your card may be charged in full if you’re found unwilling to enforce the bounty terms, with all proceeds going to Veritula.

Your card is charged for any submissions the bounty owner does not reject. If he rejects all submissions, your card is not charged.

Funders are never eligible to receive payouts from a bounty they funded.

Start a bounty today. Terms apply.

How Do Bounties Work?

Bounties let you invite criticism and reward high-quality contributions with real money.

Bounties are in beta. Expect things to break.

How do I participate?

Only users with a Beginner, Engager, and Defender badge can participate in bounties. Find a list of all badges and how to get them here.

To participate in a bounty, start by logging in or signing up.

Next, browse the list of bounties. Click a bounty to view its page, review the bountied idea and the terms, and submit a criticism of that idea.

That’s it – you’re in.

How do I get paid?

The bounty owner reviews submissions for eligibility against his bounty terms.

To be eligible for a payout, all of the following must be true:

  1. Your submission is a direct criticism of the bountied idea.
  2. Your submission has no pending counter-criticisms by its deadline. (For temporary bounties, that’s when submissions close and the review period begins; for standing bounties, it’s seven days after submission.)
  3. Your submission meets the bounty terms and the site-wide terms.
  4. You’ve connected a Stripe account in good standing before payouts are made. (For temporary bounties, that’s when the review period ends; for standing bounties, it’s seven days after submission.)
  5. You’ve not contributed funds to the bounty.

The bounty owner is never eligible to receive payouts from their own bounty.

Note that counter-criticisms are not constrained by the bounty-specific terms. Only direct criticisms of the bountied idea are.

How much will I get paid?

For temporary bounties, the amount is prorated among eligible participants based on contribution. For example, if there are ten eligible criticisms and you contributed two of them, you receive 20% of the amount when the bounty ends.

For standing bounties, amounts are assigned on a per-submission basis. For example, funders may indicate that they will pay a total of USD 100 for the first eligible submission, a total of USD 50 for the second eligible submission, and so on. Each eligible submission has its own payout date.

Fractions of cents are not paid out.

How do I run a bounty?

Open the menu (⋯) next to an idea and choose Start a bounty. You need the Beginner and Defender badges to start one.

Choose a temporary or standing bounty and write clear terms describing the kinds of criticisms you’re willing to pay for. Then set an amount and enter your credit-card details to authorize it plus a 5% bounty fee.

Your card is at most authorized, but not charged, when the bounty starts.

A temporary bounty typically runs for five to seven days, depending on your card’s authorization window. You may review submissions during the entire bounty period. Toward the end, a 24-hour grace period begins during which no new submissions can be made but you may continue your review. Reject any submissions that don’t meet your terms. Submissions you don’t reject are automatically accepted at the end of the review period and become eligible for payout. Your card is then charged the full authorization.

A standing bounty runs for as long as funds last. Each submission has its own seven-day review period. Again, reject any submissions that don’t meet your terms. Submissions you don’t reject are automatically accepted seven days after submission. Your card is then charged as indicated in your funding allocation.

If you reject all submissions, or none are eligible, your card is never charged.

What’s the difference between a temporary and a standing bounty?

A temporary bounty has a fixed duration, typically between five and seven days. The bounty amount is prorated among eligible participants at the end. Standing bounties, on the other hand, don’t have a fixed duration; they run as long as funds last. Funds are paid out continuously and on a per-submission basis, as described above.

Temporary bounties are ideal when you have limited time and a smaller budget. Standing bounties are ideal for the long term with a larger budget. However, you can mix and match based on your own unique preferences and circumstances: for example, it’s possible to use a larger budget on a temporary bounty.

Can I fund someone else’s bounty?

Yes. Review the bounty terms. If you agree with them, click the ‘Add funding’ button on the bounty page and follow the next steps. At this point, your card is at most authorized but not charged.

The bounty owner will expect you to join them in enforcing their terms. Don’t fund a bounty if you’re not prepared to enforce its terms. Run a separate bounty with your own terms instead. By funding someone else’s bounty, you agree that your card may be charged in full if you’re found unwilling to enforce the bounty terms, with all proceeds going to Veritula.

Your card is charged for any submissions the bounty owner does not reject. If he rejects all submissions, your card is not charged.

Funders are never eligible to receive payouts from a bounty they funded.

Start a bounty today. Terms apply.

  Dennis Hackethal commented on idea #5858.

Make Hiccdown reactive, like Reagent: a helper method as the first element marks a component, as in [method(:product_card), @product]. A Method knows its module and name, so a later request can call ProductsHelper#product_card again for the same records.

The server keeps track of which components each page rendered, with which records, and gives each component's element the id dom_id(record), as turbo streams do. When a record changes (after_commit, checking that it actually changed), the server tells the pages showing it, over Action Cable. Each page then requests just that component, which renders in the viewer's own request, with their user, session, locale, time zone and CSRF token, and swaps it in with a turbo stream.

Rendering the component in the request that changed the record would save that round trip, but that request has none of the viewers' context: it would render for the wrong user, or have to rebuild each viewer's context and render once per viewer.

A first pass can skip checking whether a change affects what a component shows, as when a product's name changes on a page that only shows its price.

#5858​·​Dennis HackethalOP revised 3 days ago​·​AI-assisted

Measured on Veritula locally, on a discussion of 213 ideas:

  • The whole discussion page, which a Turbo refresh re-renders: 211 ms and 506 KB, of which loading the whole tree (Idea.tree) takes 18 ms and Hiccdown.to_html 35 ms.
  • One idea's own markup, without its replies: 0.4 ms (median; 0.9 ms at the 90th percentile).
  • One idea's HTML with its replies: 3.3 KB (median).

A component request would cost the request's fixed overhead, loading that idea's part of the tree (18 ms at most) and about a millisecond of rendering: an estimated 10–30 ms and a few KB, about ten times faster and a hundred times smaller than the whole page. The whole page grows with the discussion while a component stays about the same, and the browser morphing 500 KB of DOM, which I didn't measure, comes on top.

  Dennis Hackethal revised idea #5853. The revision addresses idea #5856.

Mark components with Method objects, which later requests can call again, instead of procs


Make Hiccdown reactive, like Reagent: a proc as the first element marks a component, as in [->(product) { [:li, product.name] }, @product].

The server keeps track of which components each page rendered, with which records, and gives each component's element the id dom_id(record), as turbo streams do. When a record changes (after_commit, checking that it actually changed), the server tells the pages showing it, over Action Cable. Each page then requests just that component, which renders in the viewer's own request, with their user, session, locale, time zone and CSRF token, and swaps it in with a turbo stream.

Rendering the component in the request that changed the record would save that round trip, but that request has none of the viewers' context: it would render for the wrong user, or have to rebuild each viewer's context and render once per viewer.

A first pass can skip checking whether a change affects what a component shows, as when a product's name changes on a page that only shows its price.

Make Hiccdown reactive, like Reagent: a helper method as the first element marks a component, as in [method(:product_card), @product]. A Method knows its module and name, so a later request can call ProductsHelper#product_card again for the same records.

The server keeps track of which components each page rendered, with which records, and gives each component's element the id dom_id(record), as turbo streams do. When a record changes (after_commit, checking that it actually changed), the server tells the pages showing it, over Action Cable. Each page then requests just that component, which renders in the viewer's own request, with their user, session, locale, time zone and CSRF token, and swaps it in with a turbo stream.

Rendering the component in the request that changed the record would save that round trip, but that request has none of the viewers' context: it would render for the wrong user, or have to rebuild each viewer's context and render once per viewer.

A first pass can skip checking whether a change affects what a component shows, as when a product's name changes on a page that only shows its price.

  Dennis Hackethal addressed criticism #5855.

Turbo 8 already does most of this. With broadcasts_refreshes and morphing, each viewer's page re-renders in their own request when a record changes, keeping scroll position and form state. Components would only save rendering and sending the rest of the page.

#5855​·​Dennis HackethalOP, 3 days ago​·​AI-assisted

On large pages, like Veritula's discussions, that's too expensive: a refresh re-renders the whole page for every viewer, sends all of it, and morphs all of it, for a change to one idea. Components re-render, send and morph only what changed, so they'd give the same result as morphing on pages where morphing the whole page is too slow to use.

  Dennis Hackethal criticized idea #5853.

Make Hiccdown reactive, like Reagent: a proc as the first element marks a component, as in [->(product) { [:li, product.name] }, @product].

The server keeps track of which components each page rendered, with which records, and gives each component's element the id dom_id(record), as turbo streams do. When a record changes (after_commit, checking that it actually changed), the server tells the pages showing it, over Action Cable. Each page then requests just that component, which renders in the viewer's own request, with their user, session, locale, time zone and CSRF token, and swaps it in with a turbo stream.

Rendering the component in the request that changed the record would save that round trip, but that request has none of the viewers' context: it would render for the wrong user, or have to rebuild each viewer's context and render once per viewer.

A first pass can skip checking whether a change affects what a component shows, as when a product's name changes on a page that only shows its price.

#5853​·​Dennis HackethalOP revised 3 days ago​·​AI-assisted

Procs can't be found again in a later request. The lambda a helper built while rendering the page is gone by the time the page asks for the component, so the server has no way to call it again.

  Dennis Hackethal criticized idea #5853.

Make Hiccdown reactive, like Reagent: a proc as the first element marks a component, as in [->(product) { [:li, product.name] }, @product].

The server keeps track of which components each page rendered, with which records, and gives each component's element the id dom_id(record), as turbo streams do. When a record changes (after_commit, checking that it actually changed), the server tells the pages showing it, over Action Cable. Each page then requests just that component, which renders in the viewer's own request, with their user, session, locale, time zone and CSRF token, and swaps it in with a turbo stream.

Rendering the component in the request that changed the record would save that round trip, but that request has none of the viewers' context: it would render for the wrong user, or have to rebuild each viewer's context and render once per viewer.

A first pass can skip checking whether a change affects what a component shows, as when a product's name changes on a page that only shows its price.

#5853​·​Dennis HackethalOP revised 3 days ago​·​AI-assisted

Turbo 8 already does most of this. With broadcasts_refreshes and morphing, each viewer's page re-renders in their own request when a record changes, keeping scroll position and form state. Components would only save rendering and sending the rest of the page.

  Dennis Hackethal revised idea #4332 and marked it as AI-assisted.

Spell out how components update, and why each page fetches its own


Some Reagent-like way to make things reactive using proc as first element? And then the server keeps track of which procs have been rendered, which items have changed, and re-renders that part of the template in a turbo stream?

Make Hiccdown reactive, like Reagent: a proc as the first element marks a component, as in [->(product) { [:li, product.name] }, @product].

The server keeps track of which components each page rendered, with which records, and gives each component's element the id dom_id(record), as turbo streams do. When a record changes (after_commit, checking that it actually changed), the server tells the pages showing it, over Action Cable. Each page then requests just that component, which renders in the viewer's own request, with their user, session, locale, time zone and CSRF token, and swaps it in with a turbo stream.

Rendering the component in the request that changed the record would save that round trip, but that request has none of the viewers' context: it would render for the wrong user, or have to rebuild each viewer's context and render once per viewer.

A first pass can skip checking whether a change affects what a component shows, as when a product's name changes on a page that only shows its price.

  Dennis Hackethal archived idea #1984 along with any revisions.
  Dennis Hackethal addressed criticism #1984.

Hiccdown should have support for ids and class names in the tag symbol. Like Hiccup.

ruby
[:'div#my-id.my-class.another-class']
# => <div id="my-id" class="my-class another-class"></div>

It should also allow mixing:

ruby
[:'div#my-id.my-class.another-class', {id: 'override', class: 'additive'}]
# => <div id="override" class="my-class another-class additive"></div>

In other words, the id from the hash would override the id from the symbol, and the class from the hash would be added to the classes from the symbol.

#1984​·​Dennis HackethalOP, about 1 year ago

Done as of 3a304be, to be released in version 1.6.0, as described here. The id and classes can come in any order after the tag name, and two ids in one tag raise ArgumentError.

  Dennis Hackethal archived idea #5847 along with any revisions.
  Dennis Hackethal addressed criticism #5847.

A helper method that calls another action's helper method by name can get another helper's. Rails mixes every helper into every view, so calling index from ItemsHelper#create runs whichever helper's index Rails included last, such as PagesHelper#index:

ruby
module ItemsHelper
def index
[:ul, @items.map { |i| [:li, i.name] }]
end
def create
index # PagesHelper#index, not ItemsHelper#index
end
end

Action names like index, show and edit are in many helpers, and the Readme encourages helper methods calling one another.

#5847​·​Dennis HackethalOP, 3 days ago​·​AI-assisted

Known since #301, and accepted in #333: the alternatives, helper class methods (#1980) and separate display classes (#1982), were worse. Qualifying a helper method's name, like items_page rather than calling index, is the way around it.

  Dennis Hackethal archived idea #4331 along with any revisions.
  Dennis Hackethal commented on idea #4331.

They already do: turbo-rails gives frame requests its own minimal layout, and Hiccdown leaves it be. As of c5ce76f, to be released in version 1.6.0, a test checks that frame requests keep Turbo's frame layout even where ApplicationHelper#layout exists, and a system test (e71df95) loads a frame in a browser.

  Dennis Hackethal archived idea #4330 along with any revisions.
  Dennis Hackethal archived idea #859 along with any revisions.
  Dennis Hackethal addressed criticism #4330.

Redirects result in two additional requests, the first of which is a turbo-stream request that renders nothing, thus (presumably) prompting the browser to make another request for the same resource.

This? https://stackoverflow.com/a/74071278

#4330​·​Dennis HackethalOP, 8 months ago

Doesn't reproduce as of e71df95. In the app in test/dummy, a form whose action redirects takes one POST and one GET in a headless browser, which a system test now checks.

  Dennis Hackethal addressed criticism #859.

Could the errors around layouts be related to this?

#859​·​Dennis HackethalOP, almost 2 years ago

Probably, and they're fixed. Hiccdown used to force a layout (layout: true, then layout: !request.format.turbo_stream?), overriding the layout method turbo-rails relies on, and turbo-stream requests failed with "no default layout" (fef00cb). cf92f06, in version 1.5.2, stopped forcing a layout. As of c5ce76f and 4a087e4, to be released in version 1.6.0, tests check that frame requests keep Turbo's frame layout and that turbo streams get none, and the app in test/dummy shows it in a browser.

  Dennis Hackethal posted criticism #5847.

A helper method that calls another action's helper method by name can get another helper's. Rails mixes every helper into every view, so calling index from ItemsHelper#create runs whichever helper's index Rails included last, such as PagesHelper#index:

ruby
module ItemsHelper
def index
[:ul, @items.map { |i| [:li, i.name] }]
end
def create
index # PagesHelper#index, not ItemsHelper#index
end
end

Action names like index, show and edit are in many helpers, and the Readme encourages helper methods calling one another.

  Dennis Hackethal archived idea #5845 along with any revisions.
  Dennis Hackethal addressed criticism #5845.

On Rails 8.1, Hiccdown's capture hands keyword arguments to the block as key-value pairs rather than as keywords. capture(text: 'foo') { |text:| text } raises ArgumentError: missing keyword: :text instead of returning 'foo', and a block that takes them as a hash, { |options| … }, gets [:text, 'foo'] instead of {text: 'foo'}.

Earlier versions of Rails pass keywords on to the block as a hash, which Hiccdown leaves alone.

#5845​·​Dennis HackethalOP, 3 days ago​·​AI-assisted

Fixed as of 99b6280, to be released in version 1.6.0.

  Dennis Hackethal posted criticism #5845.

On Rails 8.1, Hiccdown's capture hands keyword arguments to the block as key-value pairs rather than as keywords. capture(text: 'foo') { |text:| text } raises ArgumentError: missing keyword: :text instead of returning 'foo', and a block that takes them as a hash, { |options| … }, gets [:text, 'foo'] instead of {text: 'foo'}.

Earlier versions of Rails pass keywords on to the block as a hash, which Hiccdown leaves alone.

  Dennis Hackethal archived idea #4329 along with any revisions.
  Dennis Hackethal commented on idea #4329.

Is there a way to teach user-built helpers how to process Hiccdown? Or maybe intercepting capture already took care of this?

#4329​·​Dennis HackethalOP, 8 months ago

Mostly, intercepting capture already took care of it: helpers of your own that pass a block on to Rails' helpers, or return Hiccdown, worked. Hiccdown given to Rails' helpers as content didn't, as in content_tag(:div, yield), tag.div([:span, 'foo']) or link_to([:span, 'foo'], path). As of 134deb9, to be released in version 1.6.0, it does.

  Dennis Hackethal archived idea #4328 along with any revisions.