All deployments
Peter Theill
deployed a feature
2 months ago
Peter Theill
deployed a bug fix
2 months ago
Peter Theill
deployed a refactoring
2 months ago
Peter Theill
deployed a security update
2 months ago
2 months ago
🔒️ Live Stripe only in production
With live keys now in the shared credentials file, configured? became true
in development and test — checkout/cancel/mode gate on live_mode?
(production, or explicit STRIPE_LIVE=1) so local runs and the test suite
always simulate. Caught when a test stubbing production hit live Stripe.
in development and test — checkout/cancel/mode gate on live_mode?
(production, or explicit STRIPE_LIVE=1) so local runs and the test suite
always simulate. Caught when a test stubbing production hit live Stripe.
Peter Theill
deployed a refactoring
2 months ago
Peter Theill
deployed a refactoring
2 months ago
Peter Theill
deployed a documentation update
2 months ago
Peter Theill
deployed a feature
2 months ago
2 months ago
✨ Press assets page + rubocop fix
(the upvote/bookmark/hot-ranking product they described is gone), styled
to the monochrome direction
the four docs it linked no longer existed
(name + project + status was public), keeping the page as a sales asset🔥 Purge the directory-era product-discovery stack
AutoDiscoveryJob and SourceCrawlJob (WebScrapingJob stays — /launch uses
it for enrichment)
upvote/bookmark helper methods
in production) with idempotent directory CSV import + launch package
seeds only🔊 Add Sentry error tracking (inactive until a DSN is configured)🔧 Align Ruby to 3.4.7 everywhere, fix onboarding landing spots
tested on .ruby-version (3.4.7) — all three now agree
back to the just-created package🔒️ Close the pre-launch security gaps
only, and no hosts resolving to private/loopback/link-local space
creation, and the metadata endpoint (sessions already had one)
script hosts, and frame_ancestors none
stored unencrypted, only fed the paused automation tier
docker builds — note: it remains in git history🔒️ Gate the extension API behind per-user tokens
credential (a Resend API key); delivery errors now raise so failures
surface instead of silently defaulting to localhost:25
commanigy.com leftover
Remaining manual steps: create the mail.launchto.win sending domain in
Resend, add its DNS records, and put the API key in credentials as
smtp.password.🔒️ Make billing production-safe: add stripe gem, refuse simulated checkout in production
(stripe.secret_key, webhook_secret, prices.maker/pro) are added
free when Stripe is unconfigured in production
(webhook payloads omit them)
subscription deleted, payment failed🔒️ Enforce plan limits server-side
was reachable past the hidden dashboard button)
automation_enabled honored only for plans with auto_submit
flag + plan check, and no longer defaults to checked
not on every attempt
🚑️ Stop hourly AutoDiscoveryJob, fix admin 500 and dead links, add admin:promote task
ownerless projects from crawled sources (and exposing them via the API)
Stats now shows directory submission count
💄 Freemium pricing display, pricing-fields UX, form field class normalization
packages#assets now renders a download grid (logo in every directory size,
screenshots) with the JSON manifest still available — the Maker feature is
visible instead of a bare JSON endpoint.
📝 Rewrite the pre-pivot copy — about, help, README; strip public submission feedscreenshots) with the JSON manifest still available — the Maker feature is
visible instead of a bare JSON endpoint.
- About and help pages now tell the package-once-launch-everywhere story
(the upvote/bookmark/hot-ranking product they described is gone), styled
to the monochrome direction
- README rewritten as the launch pitch + accurate dev/production setup;
the four docs it linked no longer existed
- /directories/:slug no longer lists other users' recent submissions
(name + project + status was public), keeping the page as a sales asset
- Delete the 11 crawlers, orchestrator, ProductSource model, its admin UI,
AutoDiscoveryJob and SourceCrawlJob (WebScrapingJob stays — /launch uses
it for enrichment)
- Drop the product_sources table
- Remove empty bookmarks/comments/upvotes helpers and the orphaned
upvote/bookmark helper methods
- Replace the 241-line demo seeds (sample users with password123 — harmful
in production) with idempotent directory CSV import + launch package
seeds only
- Dockerfile built 3.4.2 and the deploy workflow pinned 3.3.9 while CI
tested on .ruby-version (3.4.7) — all three now agree
- New signups land on /launch instead of the marketing homepage
- The unauthenticated /launch fallback stores a return-to so sign-in leads
back to the just-created package
- SSRF guard (SafeUrl) on metadata scraping and favicon downloads: http(s)
only, and no hosts resolving to private/loopback/link-local space
- Rate limits on sign_up, password resets, unauthenticated project
creation, and the metadata endpoint (sessions already had one)
- Enforced Content-Security-Policy with per-request script nonces, pinned
script hosts, and frame_ancestors none
- Drop users.hn_username / hn_password_encrypted: no UI ever wrote them,
stored unencrypted, only fed the paused automation tier
- Untrack data.dump (a production pg_dump!) and ignore *.dump in git and
docker builds — note: it remains in git history
Implements the recommended call from the launch plan: /api/packages/:slug,
/api/directories/lookup and /api/directories/map now require
Authorization: Bearer <api_token> from a plan with extension access, and
packages/mappings are scoped to the caller's own projects — packages are no
longer publicly enumerable and the Maker tier's headline feature is actually
gated.
401/403 messages; stale subdomain copy removed
source (was a stale hand-made 1.6.0); rebuilt at 1.7.0
:email: Wire production SMTP through Resend and fix the from-address/api/directories/lookup and /api/directories/map now require
Authorization: Bearer <api_token> from a plan with extension access, and
packages/mappings are scoped to the caller's own projects — packages are no
longer publicly enumerable and the Maker tier's headline feature is actually
gated.
- users.api_token (has_secure_token) + lazy backfill via User#api_token!
- Token shown on /extension when signed in with extension access
- Extension v1.7.0: token setting in ⚙, Bearer header on API calls, clear
401/403 messages; stale subdomain copy removed
- rake extension:build regenerates public/launch-to-win-extension.zip from
source (was a stale hand-made 1.6.0); rebuilt at 1.7.0
- production.rb delivers via smtp.resend.com:465 using the smtp.password
credential (a Resend API key); delivery errors now raise so failures
surface instead of silently defaulting to localhost:25
- From-address is Launch to Win <hello@mail.launchto.win> instead of the
commanigy.com leftover
Remaining manual steps: create the mail.launchto.win sending domain in
Resend, add its DNS records, and put the API key in credentials as
smtp.password.
- gem 'stripe' so live mode can actually engage once credentials
(stripe.secret_key, webhook_secret, prices.maker/pro) are added
- PaymentsService raises NotConfiguredError instead of granting plans for
free when Stripe is unconfigured in production
- detect_plan re-fetches the checkout session with line_items expanded
(webhook payloads omit them)
- Webhook lifecycle tests: activation from metadata, price-id fallback,
subscription deleted, payment failed
- ProjectsController new/create check can_create_package? (the /launch form
was reachable past the hidden dashboard button)
- Directory submissions: free-tier directory_copy_limit enforced per project;
automation_enabled honored only for plans with auto_submit
- retry_automation requires can_auto_submit?
- Automation toggle hidden behind the (off) directory_submissions_enabled
flag + plan check, and no longer defaults to checked
- DirectorySubmissionJob emails failure once after retries are exhausted,
not on every attempt
- Fix ensure_owner redirects that pointed at the removed GET project route
- Tests for the new enforcement paths
- Remove the hourly production AutoDiscoveryJob schedule that was creating
ownerless projects from crawled sources (and exposing them via the API)
- admin/projects index no longer calls the removed bookmarks_count column;
Stats now shows directory submission count
- Admin 'view' links point at the package (project_path only routes DELETE)
- bin/rails admin:promote EMAIL=... mints the first real admin
Also save docs/road-to-prime-time.html — the launch-readiness plan.
Peter Theill
deployed a styling change
2 months ago
Peter Theill
deployed a styling change
2 months ago
Peter Theill
deployed a styling change
2 months ago
Peter Theill
deployed a dependency update
2 months ago
2 months ago
⬆️ Upgrade libraries🔥 Remove domain verification and project claiming
Projects are now owned by their creator from the moment they are made.
The claim/verify flow (meta tag, DNS TXT, file upload, page content),
the ProjectClaim model, the verification columns on projects, and the
verified-owner gate on brand pages are all gone. Existing submitted-but-
unclaimed projects were handed to their submitters in the migration.
The claim/verify flow (meta tag, DNS TXT, file upload, page content),
the ProjectClaim model, the verification columns on projects, and the
verified-owner gate on brand pages are all gone. Existing submitted-but-
unclaimed projects were handed to their submitters in the migration.
Peter Theill
deployed a styling change
2 months ago
2 months ago
💄 Inter typography and a headerless sidebar
throughout with a larger type scale — body 14px, labels 13px,
hints 12px — replacing the system-font 11-13px mix
header already identifies the extension. The ⚙ settings toggle
moves into the Package slug section's header row, with the settings
panel opening directly beneath it. Bumps to 1.6.0
- Bundle Inter (variable, latin woff2 from Google Fonts) and use it
throughout with a larger type scale — body 14px, labels 13px,
hints 12px — replacing the system-font 11-13px mix
- Drop the topbar (logo + name + subtitle): the sidebar's own Chrome
header already identifies the extension. The ⚙ settings toggle
moves into the Package slug section's header row, with the settings
panel opening directly beneath it. Bumps to 1.6.0
Peter Theill
deployed a dependency update
2 months ago
Peter Theill
deployed a bug fix
2 months ago
2 months ago
🐛 Repair templates broken by the directory teardown
Brakeman's parser caught three views with orphaned markup left by the
block removals — the admin dashboard's reports card, the admin user
page's comments/reports sections (plus a dead @comments query), and
the profile page's bookmarks tail — all of which would 500 when
rendered. Removed the dangling fragments; Brakeman and RuboCop are
both clean.
block removals — the admin dashboard's reports card, the admin user
page's comments/reports sections (plus a dead @comments query), and
the profile page's bookmarks tail — all of which would 500 when
rendered. Removed the dangling fragments; Brakeman and RuboCop are
both clean.
Peter Theill
deployed a bug fix
2 months ago
Peter Theill
deployed a bug fix
2 months ago
2 months ago
🔥 Remove the public directory — Launch to Win is package-first
(8MB cap, up to 5 screenshots) and the page fill hands the input
real File objects via DataTransfer, firing input/change
widget and clicking the option matching one of the package's
categories (verified against Huzzler's Alpine combobox)
named hidden inputs, skipping csrf/honeypot names. Bumps to 1.5.0✨ Map logo, screenshots and custom category pickers
(file inputs) and allows category on hidden inputs backing custom
dropdowns; accept/multiple pass through to the model
extension can fetch the bytes from our own host to fill uploads
🐛 Clear error when the API base lacks the map endpoint
curated mappings fill directly, otherwise the sidebar scrapes the
form's field structure (labels, names, attributes — never values),
posts it to /api/directories/map and fills from the answer
class, nth-of-type path) in the page, so the AI only picks by index
self-healing when a site redesigns its form
Autofill per step maps and remembers each step separately
✨ Learn form mappings on demand with AI
paragraphs and fires an input event so the editor syncs
'no autofill mapping yet, copy fields below' with autofill disabled
and the copy panel opened, instead of the misleading 'your package
fields for this site are empty'. Bumps to 1.3.0✨ Add tailored Huzzler field mapping
stays open while browsing; src/popup.* renamed to src/panel.*
clears the previous page's fill report on site change
first autofill there (no activeTab grant exists in a sidebar)
✨ Promote the Chrome extension on the site
now), and a download of the packaged zip
✨ Add copy panel to the extension popup
src only) with optional MAJOR.MINOR.PATCH version bump
privacy policy URL⚡️ Cache directory host lookup
mirroring PackageFieldResolver#rows_for)
contact email after filling
explicit Load bypasses the cache, 404 lookups are never cached
The listing/browsing product is gone, no legacy fallbacks kept:
queued, lifetime deals, free, video reviews, user profiles (public),
my-projects, bookmarks; nav collapses to Directories + Extension and
footers drop the community stats and links
reports — models, controllers, views, admin screens, and tables
(DropBrowsingTables migration, irreversible)
original_price/deal_url columns dropped, pricing_display no longer
falls back, admin + launch forms lose the pricing_type radio,
autofill derives freemium as the default
metadata; claim and verify land on the package hub; project sitemap
and seed-time community data removed
Projects remain solely as the record behind a launch package.
✨ Upload every screenshot, not just five- Public browsing routes and pages deleted: browse/index, project show,
queued, lifetime deals, free, video reviews, user profiles (public),
my-projects, bookmarks; nav collapses to Directories + Extension and
footers drop the community stats and links
- Community features deleted outright: upvotes, comments, bookmarks,
reports — models, controllers, views, admin screens, and tables
(DropBrowsingTables migration, irreversible)
- Legacy deal pricing removed: is_free/is_lifetime_deal/deal_price/
original_price/deal_url columns dropped, pricing_display no longer
falls back, admin + launch forms lose the pricing_type radio,
autofill derives freemium as the default
- ProjectsController keeps only new/create/destroy/claim/verify/
metadata; claim and verify land on the package hub; project sitemap
and seed-time community data removed
Projects remain solely as the record behind a launch package.
Drops the per-count cap on screenshot uploads — the only limit left
is a 40MB total payload guard for the injection message (8MB per
file, as before). Bumps to 1.5.2
🔥 Retire the listing editor — the package editor is the editoris a 40MB total payload guard for the injection message (8MB per
file, as before). Bumps to 1.5.2
Launch to Win pivots from being a directory of projects to packaging
products for launch everywhere, so one editor remains:
(old links and bookmarks keep working); the 454-line tabbed listing
view is deleted
and a danger zone with project deletion; contact_phone is permitted
editor; deleting redirects to the packages dashboard
🐛 Click the real option, not its listbox wrapperproducts for launch everywhere, so one editor remains:
- /projects/:slug/edit and its update redirect to the package editor
(old links and bookmarks keep working); the 454-line tabbed listing
view is deleted
- Package editor gains contact phone (private, like contact email)
and a danger zone with project deletion; contact_phone is permitted
- All links and post-create/submission redirects point at the package
editor; deleting redirects to the packages dashboard
fillCombobox matched '[role=option], [role=listbox] li' in document
order, so an inert <li> wrapper with the same text won as the exact
match and its click set nothing. Prefer [role=option] elements, click
the inner button when a wrapper is all we have, and verify the hidden
input's value actually changed — retrying with the widget opened when
the closed-state click doesn't take. Verified live on Huzzler's
category picker. Bumps to 1.5.1
✨ Fill uploads and custom dropdowns from the packageorder, so an inert <li> wrapper with the same text won as the exact
match and its click set nothing. Prefer [role=option] elements, click
the inner button when a wrapper is all we have, and verify the hidden
input's value actually changed — retrying with the widget opened when
the closed-state click doesn't take. Verified live on Huzzler's
category picker. Bumps to 1.5.1
- logo/screenshots resolve to asset URLs; the panel fetches the bytes
(8MB cap, up to 5 screenshots) and the page fill hands the input
real File objects via DataTransfer, firing input/change
- Hidden inputs backing custom dropdowns are filled by opening the
widget and clicking the option matching one of the package's
categories (verified against Huzzler's Alpine combobox)
- Scraper now includes file inputs (even behind styled dropzones) and
named hidden inputs, skipping csrf/honeypot names. Bumps to 1.5.0
- FormFieldMapperService gains logo/screenshots as mappable targets
(file inputs) and allows category on hidden inputs backing custom
dropdowns; accept/multiple pass through to the model
- PackageSerializer serves assets as storage proxy URLs so the
extension can fetch the bytes from our own host to fill uploads
- Huzzler curated map now covers category, logo and media uploads
A 404 from POST /api/directories/map now says the configured API runs
an older server instead of a bare 'Not Found'. Stored-mapping hits
report source 'cache' so the sidebar can say a mapping was learned
earlier vs mapped just now. Dev hints corrected to port 3109 (bin/dev).
Bumps to 1.4.1
📝 Describe AI form mapping on the sitean older server instead of a bare 'Not Found'. Stored-mapping hits
report source 'cache' so the sidebar can say a mapping was learned
earlier vs mapped just now. Dev hints corrected to port 3109 (bin/dev).
Bumps to 1.4.1
/extension feature card and the privacy policy now cover on-demand AI
mapping: only form structure is sent, never page values, and learned
mappings are stored and shared.
✨ Autofill any directory via AI mappingmapping: only form structure is sent, never page values, and learned
mappings are stored and shared.
- Autofill is enabled on every http(s) page once a package is loaded:
curated mappings fill directly, otherwise the sidebar scrapes the
form's field structure (labels, names, attributes — never values),
posts it to /api/directories/map and fills from the answer
- scrapeFormFields computes verified-unique selectors (#id, [name],
class, nth-of-type path) in the page, so the AI only picks by index
- Zero fills from a stored mapping trigger one forced AI remap —
self-healing when a site redesigns its form
- Multi-step forms: each step scrapes differently, so clicking
Autofill per step maps and remembers each step separately
- Result notes the mapping source. Bumps to 1.4.0
POST /api/directories/map answers with a curated Directory mapping,
a stored FormMapping learned earlier, or a fresh AI mapping (Gemini
flash) that is stored and shared by every user and package.
redesign gets a new mapping and each step of a multi-step form gets
its own row
selectors come verbatim from the page, so it can't hallucinate them
redesign); AI calls are throttled per host and per IP and gated on
a valid package slug
including the maxlength and the Quill editor, category correctly
skipped
✨ Fill rich-text editors and report unmapped directories honestlya stored FormMapping learned earlier, or a fresh AI mapping (Gemini
flash) that is stored and shared by every user and package.
- FormMapping keys on host + a signature of the form's fields, so a
redesign gets a new mapping and each step of a multi-step form gets
its own row
- The model only assigns canonical fields to descriptor indexes; the
selectors come verbatim from the page, so it can't hallucinate them
- force: true skips the stores and remaps (self-healing after a site
redesign); AI calls are throttled per host and per IP and gated on
a valid package slug
- Verified against Gemini with the real Huzzler form: 4/4 fields,
including the maxlength and the Quill editor, category correctly
skipped
- fillFormInPage handles contenteditable editors (Quill etc.): builds
paragraphs and fires an input event so the editor syncs
- A directory that matches but has zero field mappings now reads
'no autofill mapping yet, copy fields below' with autofill disabled
and the copy panel opened, instead of the misleading 'your package
fields for this site are empty'. Bumps to 1.3.0
Selectors verified against huzzler.so/products/create: #name, #url,
#short_description (max 100) and the Quill .ql-editor full description.
Category (custom combobox) and logo/media uploads stay manual.
📝 Update site copy for the sidebar extension#short_description (max 100) and the Quill .ql-editor full description.
Category (custom combobox) and logo/media uploads stay manual.
Promo card, /extension page and privacy policy now describe the
sidebar behavior, the per-site access grant, and active-tab hostname
use; ships the 1.2.0 zip.
✨ Move the extension from popup to Chrome side panelsidebar behavior, the per-site access grant, and active-tab hostname
use; ships the 1.2.0 zip.
- Toolbar icon now opens a sidebar (sidePanel API, Chrome 114+) that
stays open while browsing; src/popup.* renamed to src/panel.*
- The panel follows the active tab via tabs.onActivated/onUpdated and
clears the previous page's fill report on site change
- Page access is a per-site optional host permission requested on the
first autofill there (no activeTab grant exists in a sidebar)
- Fluid panel width instead of the fixed 340px popup. Bumps to 1.2.0
- /extension page with features, install steps (load unpacked for
now), and a download of the packaged zip
- script/package-extension --publish copies the zip to public/
- My Packages page links to the extension so makers discover it
Every package field gets a copy button (plus Copy all), so the
extension is useful on directories without an autofill mapping —
the panel auto-expands on unmapped sites and stays collapsed where
autofill works. CANONICAL_FIELDS mirrors DEFAULT_LABELS and the sync
test now enforces the field list and labels too. Bumps to 1.1.0.
🔧 Prepare extension for the Chrome Web Storeextension is useful on directories without an autofill mapping —
the panel auto-expands on unmapped sites and stays collapsed where
autofill works. CANONICAL_FIELDS mirrors DEFAULT_LABELS and the sync
test now enforces the field list and labels too. Bumps to 1.1.0.
- script/package-extension builds a store-ready zip (manifest, icons,
src only) with optional MAJOR.MINOR.PATCH version bump
- /privacy gains a Chrome-extension section for the store listing's
privacy policy URL
Directory.find_by_host resolves a host through a cached host -> id map
keyed on maximum(:updated_at) + count, replacing the per-request scan
that loaded and URI-parsed every active directory in the API lookup.
✅ Enforce resolver.js / PackageFieldResolver synckeyed on maximum(:updated_at) + count, replacing the per-request scan
that loaded and URI-parsed every active directory in the API lookup.
Runs a fully-populated project through PackageSerializer + the
extension's resolver.js (via node) and through PackageFieldResolver,
asserting every canonical field resolves identically and that no key
falls through to null in the extension. Skips when node is absent.
✨ Improve extension autofill feedbackextension's resolver.js (via node) and through PackageFieldResolver,
asserting every canonical field resolves identically and that no key
falls through to null in the extension. Skips when node is absent.
- Warn when values exceed a directory's character limits (max/over_by,
mirroring PackageFieldResolver#rows_for)
- List required-but-empty package fields and prompt for a missing
contact email after filling
- Distinguish an unreachable API from an unmapped directory
- Try each comma-separated candidate when matching <select> options
- Cache package/lookup responses in storage.session (5 min TTL);
explicit Load bypasses the cache, 404 lookups are never cached
- Escape API-sourced strings before rendering them in the popup