Skip to content

Commit 652cf70

Browse files
Seroclaude
andcommitted
Remove em dashes across code and docs (house style)
Replace every em/en dash with context-appropriate punctuation (colon, period, comma, or parentheses) across READMEs, CLI help, error and console output, code comments, and test fixtures. No logic changed; test suites green. Also replace the README demo-GIF placeholder with the real check output. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 04b6fa0 commit 652cf70

12 files changed

Lines changed: 31 additions & 30 deletions

File tree

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,5 @@ RATE_LIMIT_WINDOW_MS="60000"
3535
# Which origins may iframe the /embed widget. Comma/space-separated list of
3636
# origins, e.g. "https://acme.com https://www.acme.com".
3737
# Leave empty for the safe default ('self' only). Set "*" to allow any origin
38-
# (wide open opt-in only).
38+
# (wide open, opt-in only).
3939
EMBED_ALLOWED_ORIGINS=""

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Contributing to launchpad
22

3-
Thanks for your interest in improving `launchpad` an open-source tool by [Martello Systems](https://martellosystems.com).
3+
Thanks for your interest in improving `launchpad`, an open-source tool by [Martello Systems](https://martellosystems.com).
44

55
## Ways to contribute
66
- 🐛 Report bugs via Issues

README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) [![Built by Martello Systems](https://img.shields.io/badge/built%20by-Martello%20Systems-0b0b14)](https://martellosystems.com)
44

5-
**A self-hosted viral waitlist in a box.** Signup, referral codes with attribution, position tracking + leaderboard, an admin dashboard, an embeddable widget, and email automation all in one deployable Next.js app. This is the thing you deploy, not a template you assemble.
5+
**A self-hosted viral waitlist in a box.** Signup, referral codes with attribution, position tracking + leaderboard, an admin dashboard, an embeddable widget, and email automation, all in one deployable Next.js app. This is the thing you deploy, not a template you assemble.
66

77
- **Referral loops built in.** Every signup gets a unique referral link. Each successful (verified) referral moves them up the line.
88
- **Double opt-in.** Signups confirm their email via a verification link; only verified referrals count (anti-gaming). Toggle off if you don't want it.
@@ -90,8 +90,8 @@ The public `POST /api/signup` endpoint is rate-limited per client IP using an in
9090
| Method | Route | Auth | Description |
9191
|---|---|---|---|
9292
| `POST` | `/api/signup` | rate-limited | Body `{ email, referredByCode? }``{ referralCode, position, referralLink, pendingVerification }`. `409` on duplicate email, `429` over the rate limit. |
93-
| `GET`/`POST` | `/api/verify?token=…` | | Confirms a signup. Redirects to `/?verified=1` (or `?verified=error`); returns JSON when `Accept: application/json`. |
94-
| `GET` | `/api/leaderboard?limit=10` | | Top referrers by **verified** referrals (emails masked). |
93+
| `GET`/`POST` | `/api/verify?token=…` | none | Confirms a signup. Redirects to `/?verified=1` (or `?verified=error`); returns JSON when `Accept: application/json`. |
94+
| `GET` | `/api/leaderboard?limit=10` | none | Top referrers by **verified** referrals (emails masked). |
9595
| `GET` | `/api/admin/signups?take=&skip=` | Bearer `ADMIN_TOKEN` | Full signup list with referral counts + verified status. |
9696

9797
Example:
@@ -109,23 +109,23 @@ curl localhost:3000/api/admin/signups \
109109

110110
## Pages
111111

112-
- `/` public waitlist page: signup form (auto-captures `?ref=CODE`), shows your position + referral link, and a top-referrers list.
113-
- `/admin` token-prompted admin dashboard listing every signup.
114-
- `/embed` chrome-free signup form for iframe embedding.
112+
- `/`: public waitlist page: signup form (auto-captures `?ref=CODE`), shows your position + referral link, and a top-referrers list.
113+
- `/admin`: token-prompted admin dashboard listing every signup.
114+
- `/embed`: chrome-free signup form for iframe embedding.
115115

116116
---
117117

118118
## Embed on any site
119119

120-
**Option A script tag (recommended).** Drop this anywhere; it injects a responsive iframe:
120+
**Option A: script tag (recommended).** Drop this anywhere; it injects a responsive iframe:
121121

122122
```html
123123
<script src="https://your-launchpad.example.com/embed.js"
124124
data-launchpad
125125
data-height="120"></script>
126126
```
127127

128-
**Option B raw iframe.**
128+
**Option B: raw iframe.**
129129

130130
```html
131131
<iframe src="https://your-launchpad.example.com/embed"
@@ -137,7 +137,7 @@ curl localhost:3000/api/admin/signups \
137137

138138
- **unset / empty**`frame-ancestors 'self'` (safe default: only your own deployment may iframe it).
139139
- **`EMBED_ALLOWED_ORIGINS="https://acme.com https://www.acme.com"`**`frame-ancestors 'self' https://acme.com https://www.acme.com`.
140-
- **`EMBED_ALLOWED_ORIGINS="*"`**`frame-ancestors *` (any site opt-in only).
140+
- **`EMBED_ALLOWED_ORIGINS="*"`**`frame-ancestors *` (any site, opt-in only).
141141

142142
Because it's read at request time, you can change the allowlist without rebuilding.
143143

@@ -164,7 +164,7 @@ Coverage (54 tests) includes: signup + sequential positions, email normalization
164164
- **Rate limiting is in-memory and per-instance.** It's correct for a single self-hosted instance (the common case). Behind a load balancer / on serverless, each instance keeps its own window, so the effective global limit is roughly `limit × instances`. For multi-instance setups, back the same `RateLimiter` interface with a shared store (Redis/Upstash). v1 deliberately avoids that infra.
165165
- **Verification tokens don't expire.** A token is single-use (cleared on verify) but has no TTL in v1. Add an expiry check if you need one.
166166
- **Email masking on the public leaderboard** is best-effort obfuscation, not anonymization.
167-
- **Single-table model** no multi-project/multi-tenant support (roadmap).
167+
- **Single-table model:** no multi-project/multi-tenant support (roadmap).
168168

169169
---
170170

@@ -185,13 +185,13 @@ MIT © 2026 Martello Systems. See [LICENSE](./LICENSE).
185185

186186
---
187187

188-
<sub>Built by **Martello Systems** — we design and ship AI-driven software.
188+
<sub>Built by **Martello Systems**. We design and ship AI-driven software.
189189
Part of the Martello open-source dev-tools family.</sub>
190190

191191
---
192192

193193
## Built by Martello Systems
194194

195-
`launchpad` is part of the open-source toolkit from **[Martello Systems](https://martellosystems.com)** — we ship AI-built software, spec to delivery in days. If this saved you time, come [see what we do](https://martellosystems.com).
195+
`launchpad` is part of the open-source toolkit from **[Martello Systems](https://martellosystems.com)**. We ship AI-built software, spec to delivery in days. If this saved you time, come [see what we do](https://martellosystems.com).
196196

197197
Licensed under the [Apache License 2.0](LICENSE).
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"ts": "2026-06-25T00:35:24", "agent": "_main-session", "tool": "Bash", "decision": "allow", "reason": "main-session audit-only", "mode": "warn-only", "main_session": true, "tool_input": {"command": "cd ~/Dev/OpenSourceMartelloSystems\nMSG=\"Remove em dashes across code and docs (house style)\n\nReplace every em/en dash with context-appropriate punctuation (colon,\nperiod, comma, or parentheses) across READMEs, CLI help, error and console\noutput, code comments, and test fixtures. No logic changed; test suites green.\nAlso replace the README demo-GIF placeholder with the real check output.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\"\n\nfor r in envz launchpad agent-firewall spec-verify snapdb; do\n cd ~/Dev/OpenSourceMartelloSystems/$r\n git add -A\n git commit -q -m \"$MSG\" && echo \"$r: committed\" || echo \"$r: nothing to commit\"\ndone\necho \"=== push ===\"\nfor r in envz launchpad agent-firewall spec-verify snapdb; do\n cd ~/Dev/OpenSourceMartelloSystems/$r\n git push -q origin \"$(git branch --show-current)\" && echo \"$r: pushed ✓\" || echo \"$r: PUSH FAILED\"\ndone", "description": "Commit and push em-dash purge across all 5 repos"}}

app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Metadata } from "next";
22
import "./globals.css";
33

44
export const metadata: Metadata = {
5-
title: "Launchpad Join the Waitlist",
5+
title: "Launchpad: Join the Waitlist",
66
description: "A self-hosted viral waitlist. Sign up and refer friends to move up.",
77
};
88

app/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ export default async function Home({
2323
try {
2424
[total, board] = await Promise.all([totalSignups(prisma), leaderboard(prisma, 5)]);
2525
} catch {
26-
/* DB not configured yet page still renders the signup form */
26+
/* DB not configured yet: page still renders the signup form */
2727
}
2828

2929
return (
3030
<main className="mx-auto flex min-h-screen max-w-xl flex-col justify-center gap-10 px-6 py-16">
3131
{verified === "1" && (
3232
<div className="rounded-lg bg-green-50 p-4 text-center text-sm text-green-800">
33-
Email confirmed — you&apos;re officially on the waitlist. 🎉
33+
Email confirmed. You&apos;re officially on the waitlist. 🎉
3434
</div>
3535
)}
3636
{verified === "error" && (

components/SignupForm.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export default function SignupForm({ compact = false }: { compact?: boolean }) {
7777
</div>
7878
<div className="space-y-2">
7979
<p className="text-sm text-neutral-600">
80-
Share your link every signup moves you up the list:
80+
Share your link: every signup moves you up the list:
8181
</p>
8282
<div className="flex gap-2">
8383
<input

lib/mailer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export interface Mailer {
1515
}
1616

1717
/**
18-
* Console mailer used when no RESEND_API_KEY is present (local dev/test).
18+
* Console mailer, used when no RESEND_API_KEY is present (local dev/test).
1919
* Logs the message instead of sending. Never throws.
2020
*/
2121
export class ConsoleMailer implements Mailer {
@@ -126,7 +126,7 @@ export function buildMilestoneEmail(params: {
126126
position: number;
127127
}): MailMessage {
128128
const subject = `You've referred ${params.referralCount} people!`;
129-
const text = `Nice work ${params.referralCount} referrals so far. You're now #${params.position} on the waitlist. Keep sharing!`;
129+
const text = `Nice work. ${params.referralCount} referrals so far. You're now #${params.position} on the waitlist. Keep sharing!`;
130130
const html = `<div style="font-family:sans-serif;max-width:480px">
131131
<h2>Milestone reached!</h2>
132132
<p>You've referred <strong>${params.referralCount}</strong> people.</p>

lib/rate-limit.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
// balancer, each instance keeps its own window, so the effective global limit
88
// is roughly (limit × instanceCount). For multi-instance setups, put a shared
99
// store (Redis/Upstash) behind this same interface. We deliberately avoid
10-
// adding that infra to v1 see README "Limitations".
10+
// adding that infra to v1, see README "Limitations".
1111
//
1212
// Algorithm: fixed window per key. Each key tracks a count and the window's
1313
// reset timestamp. When the window elapses, the count resets. This is simpler

lib/waitlist.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export async function signup(
9595
where: { referralCode: input.referredByCode.trim() },
9696
});
9797
// A bad/unknown code is silently ignored (no attribution) rather than
98-
// failing the signup the person still wants to join.
98+
// failing the signup: the person still wants to join.
9999
}
100100

101101
// Pre-existing email check for a clean error (the unique constraint is the
@@ -170,7 +170,7 @@ export async function signup(
170170
}
171171
}
172172
} catch {
173-
/* swallow email is not critical to the signup transaction */
173+
/* swallow: email is not critical to the signup transaction */
174174
}
175175
}
176176

@@ -190,7 +190,7 @@ export async function signup(
190190
/**
191191
* Confirm a signup via its verification token. Marks the entry verified,
192192
* clears the single-use token, then (best-effort) sends the welcome
193-
* confirmation email and credits the referrer's milestone because referral
193+
* confirmation email and credits the referrer's milestone, because referral
194194
* credit only counts verified signups.
195195
*
196196
* Throws WaitlistError("INVALID_TOKEN") for an unknown/used token. Verifying an
@@ -213,7 +213,7 @@ export async function verifyEmail(
213213
}
214214

215215
if (entry.verified) {
216-
// Token still present but already verified treat as idempotent success.
216+
// Token still present but already verified: treat as idempotent success.
217217
return { id: entry.id, email: entry.email, alreadyVerified: true };
218218
}
219219

@@ -236,7 +236,7 @@ export async function verifyEmail(
236236
await maybeSendMilestone(prisma, updated.referredById, opts.mailer);
237237
}
238238
} catch {
239-
/* swallow email is not critical to verification */
239+
/* swallow: email is not critical to verification */
240240
}
241241
}
242242

@@ -342,8 +342,8 @@ export async function leaderboard(
342342

343343
/**
344344
* Send the milestone email if the referrer's referral count is exactly at a
345-
* multiple of the configured threshold (so it fires at 3, 6, 9, ... by default
346-
* not on every referral after the first milestone).
345+
* multiple of the configured threshold (so it fires at 3, 6, 9, ... by default,
346+
* not on every referral after the first milestone).
347347
*/
348348
async function maybeSendMilestone(
349349
prisma: PrismaClient,

0 commit comments

Comments
 (0)