Skip to main content

max / makenotwork

Stop the example seed paywalling most of the demo catalog A non-free project renders ProjectPaywallTemplate to anyone without access, and that template lists no items at all. testnot is a public no-login demo, so every visitor is permanently without access: three of the five seeded projects were storefronts nobody could see inside, and 9 of the 11 seeded items were unreachable. Spreading the four PricingKinds across projects is what did it. Move the BuyOnce and Pwyw demonstrations to the items, which already carry those prices and stay visible from the storefront. Subscription is project-level by construction, so marginalia keeps it and is the one paywall on the box. Test asserts only the subscription project is non-free, so the next roster edit cannot quietly re-hide the catalog.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-05 17:44 UTC
Signed with PGP, not checked
Commit: f4b2f472b321115671d0e4f1af31704e704a1dbf
Parent: 1cdc41f
2 files changed, +56 insertions, -2 deletions
@@ -78,6 +78,20 @@
78 78 /// Enabled features; `create_project` derives `project_type` from these.
79 79 pub features: &'static [&'static str],
80 80 /// Headline pricing model for the project.
81 + ///
82 + /// Keep this `Free` unless the point of the project *is* the paywall.
83 + /// A non-free project renders `ProjectPaywallTemplate` to anyone without
84 + /// access, and that template lists no items at all
85 + /// (`Project::from_db(db_project, 0)`). testnot is a public no-login demo,
86 + /// so every visitor is "without access" forever: a paid project there is a
87 + /// storefront nobody can see inside. Spreading the four `PricingKind`s
88 + /// across projects hid 9 of the 11 seeded items this way until 2026-08-05.
89 + ///
90 + /// Price the *items* instead. Item pricing is visible from the storefront
91 + /// (the card links to `/purchase/{id}`), so BuyOnce and Pwyw demonstrate
92 + /// themselves without closing the door. `Subscription` is the exception: it
93 + /// is project-level by construction, so marginalia keeps it and is the one
94 + /// paywall on the box.
81 95 pub pricing: ProjectPricing,
82 96 /// Subscription tiers (only non-empty for a `Subscription` project).
83 97 pub tiers: &'static [TierSpec],
@@ -146,7 +160,10 @@
146 160 share; name your price if you'd like to support the \
147 161 restoration work.",
148 162 features: &["audio"],
149 - pricing: ProjectPricing::Pwyw { min: 100 },
163 + // Free at the project level so the storefront is browsable; the Pwyw
164 + // demonstration lives on the items below. See the pricing note on
165 + // `ProjectSpec::pricing`.
166 + pricing: ProjectPricing::Free,
150 167 tiers: &[],
151 168 items: &[
152 169 ItemSpec {
@@ -203,7 +220,10 @@
203 220 everything locally, and stays out of your way. One \
204 221 download, buy it once.",
205 222 features: &["downloads", "license_keys"],
206 - pricing: ProjectPricing::BuyOnce(900),
223 + // Free at the project level so the storefront is browsable; the four
224 + // items below are BuyOnce at $3-$12 and carry the paid demonstration.
225 + // See the pricing note on `ProjectSpec::pricing`.
226 + pricing: ProjectPricing::Free,
207 227 tiers: &[],
208 228 items: &[
209 229 ItemSpec {
@@ -225,6 +225,40 @@
225 225 );
226 226 }
227 227
228 + /// Only the subscription project may be non-free at the project level.
229 + ///
230 + /// A paid project renders `ProjectPaywallTemplate` to anyone without access, and
231 + /// that template lists no items at all. Every testnot visitor is anonymous and
232 + /// therefore permanently without access, so a paid project is a storefront nobody
233 + /// can see inside. Spreading the four `PricingKind`s across projects hid 9 of the
234 + /// 11 seeded items until 2026-08-05; this is the guard against doing it again.
235 + /// Paid *items* are fine and are how BuyOnce and Pwyw get demonstrated.
236 + #[tokio::test]
237 + async fn only_the_subscription_project_is_paywalled() {
238 + let db = TestDb::new().await;
239 +
240 + seed::run(&db.pool, &testnot_opts(), &SeedMedia::none())
241 + .await
242 + .expect("seed should succeed");
243 +
244 + let paywalled: Vec<String> = sqlx::query_scalar(
245 + "SELECT p.slug FROM projects p JOIN users u ON u.id = p.user_id \
246 + WHERE lower(split_part(u.email,'@',2))='example.test' \
247 + AND p.pricing_model IS NOT NULL AND p.pricing_model <> 'free' \
248 + ORDER BY p.slug",
249 + )
250 + .fetch_all(&db.pool)
251 + .await
252 + .expect("paywalled project slugs");
253 +
254 + assert_eq!(
255 + paywalled,
256 + vec!["the-marginalia-reader".to_string()],
257 + "only the subscription project may be paywalled; the rest must be \
258 + project-level free so their items are reachable anonymously"
259 + );
260 + }
261 +
228 262 #[tokio::test]
229 263 async fn seed_is_idempotent() {
230 264 let db = TestDb::new().await;