Skip to content

Spell out ø, æ, ß and ł in new signup links and refs - #671

Merged
richshaw merged 2 commits into
mainfrom
claude/issue-641-kebab-letters
Oct 26, 2026
Merged

richshaw merged 2 commits into
mainfrom
claude/issue-641-kebab-letters

Conversation

@richshaw

Copy link
Copy Markdown
Owner

Fixes #641.

The problem

toKebab (src/lib/kebab.ts) makes the kebab-case behind new signup slugs, workspace slugs and field and slot refs. It strips accents with NFKD, but ø, æ, œ, ß, ł, đ, þ and ð don't decompose. So they were dropped or split the word: "Smørrebrød" became sm-rrebr-d, "Æblekage" blekage, "Straße" stra-e and "Łódź" odz.

The fix

  • toKebab now spells those letters out, upper and lower case: ø→o, æ→ae, œ→oe, ß→ss (and capital ẞ), ł→l, đ→d, þ→th, ð→d. The regex is built from the map's keys, so the two lists can't drift apart.
  • One choice the issue didn't make: the mapping runs after the accents are stripped, not before NFKD. Letters like ǿ (ø with an acute) decompose into ø plus a mark, so mapping first would miss them. None of the mapped letters change under NFKD, so the order makes no other difference.
  • Other scripts (Cyrillic, Greek, Chinese) still come out empty and fall back to signup, field or slot, as the issue asks. The doc comment now says so.
  • Existing slugs and refs don't change. toSlug and toFieldRef are only called to make new values: pickAvailableSlug, the slot ref in services/slots.ts, the workspace slug in auth/adapter.ts and the builder's new-field ref. Nothing re-derives a stored one.
  • kebab.ts still has no Node imports, so the builder can keep using it.
  • CHANGELOG entry under Fixed.

Tests

  • New: src/lib/kebab.test.ts:

    • the issue's four examples;
    • each mapped letter in both cases;
    • ǿ, a mapped letter that also carries an accent;
    • å, ä, ö, é and è come out as before;
    • Cyrillic and Chinese stay empty.

    Before the fix, 18 of its 20 tests failed. The existing slug.test.ts and slot-fields.test.ts outputs are unchanged.

  • Passed on e2f0948: pnpm lint && pnpm typecheck && pnpm knip && pnpm test (1793). No services, DB, MCP or client bundle changes beyond this pure helper, so no pnpm test:db was needed.


Generated by Claude Code

toKebab stripped accents with NFKD, but ø, æ, œ, ß, ł, đ, þ and ð don't
decompose, so they were dropped or split the word: "Smørrebrød" became
sm-rrebr-d and "Æblekage" blekage. They are now spelled out (o, ae, oe,
ss, l, d, th, d) after the accents come off, so a letter that carries
both, like ǿ, is caught too. Only new signup slugs, workspace slugs and
field and slot refs use this, so existing links don't change.

Fixes #641.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qBeQRGx7pCq7m7Xv8ZCpD
@richshaw richshaw added pipeline:ux Changes what a person using OpenSignup sees or does pipeline:working A session is working on it right now labels Oct 26, 2026 — with Claude
@richshaw
richshaw requested a balanced review from Copilot October 26, 2026 03:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@richshaw richshaw added pipeline:tophat pipeline:working A session is working on it right now and removed pipeline:working A session is working on it right now labels Oct 26, 2026 — with Claude

Copy link
Copy Markdown
Owner Author

Tophat: passed

Ran e2f0948 locally with pnpm dev, Postgres 16, and Chromium at 1280×900 and 390×844 (phone with touch on). I didn't run the worker, since nothing here touches jobs. The data was made up (pat@example.com, titles like "Smørrebrød lunch" and "Straße cleanup"). For the "before" rows I ran the same steps on the PR's base, main at 53fba65, then switched to the PR head in the same database. I read slugs and refs from the URLs, the API responses and the signups / slot_fields tables.

Check Result What I saw
Before (main): new signup titles, through New signup and POST /api/signups ✅ bug reproduced "Smørrebrød lunch" → sm-rrebr-d-lunch-…, "Æblekage bake sale" → blekage-…, "Straße cleanup" → stra-e-cleanup-…, "Łódź trip" → odz-trip-…, "Œuvre night" → uvre-night-…, "Þórsmörk hike" → orsmork-hike-…
Before (main): a field added in Fields › Add field named "Størrelse" ✅ bug reproduced Its ref was st-rrelse
After: the same titles, plus "Đakovo choir" ✅ smorrebrod-lunch-…, aeblekage-bake-sale-…, strasse-cleanup-…, lodz-trip-…, oeuvre-night-…, thorsmork-hike-…, dakovo-choir-…
After: fields "Størrelse" and "Straße" added in the builder ✅ Refs storrelse and strasse. The dialog shows the labels as typed
After: a slot posted with What "Rødgrød" ✅ Slot ref rodgrod
Other scripts and a repeated title ✅ "Субботник" → signup-…, as on main. A second "Smørrebrød lunch" got its own suffix (-538wh and -kmcs2)
Existing values don't change ✅ The signup and field made on main kept sm-rrebr-d-lunch-rqmnq and st-rrelse after the switch. Once published, /s/sm-rrebr-d-lunch-rqmnq still opened (200, and on the phone)
Neighbour: publish and the public page, desktop and phone ✅ Publish and share showed the new link in the message (/s/lodz-strasse-day-… on the phone). Open public page opened it and showed the slot
Browser console and server log ✅ No console errors. The server log's only errors were the health check's pgboss.job probe, which fails because the worker wasn't running

For the owner (UX): new signup links and the refs of new fields and slots now spell out ø, æ, œ, ß, ł, đ, þ and ð instead of dropping them or splitting the word. A Danish organizer's "Smørrebrød lunch" now gets /s/smorrebrod-lunch-… rather than /s/sm-rrebr-d-lunch-…. Field refs show up in the CSV export and to an AI assistant. Nothing already saved changes, so links people have already shared keep working. Screens look the same: the difference is the link in the share panel and the address bar. Screenshots: 671-before-desktop-field and 671-after-desktop-field (the Fields dialog, which looks the same; the refs differ in the database), 671-after-desktop-public (/s/smorrebrod-lunch-…), 671-after-phone-share (the message with /s/lodz-strasse-day-…), 671-after-phone-public, and 671-after-phone-old-slug (an old sm-rrebr-d-… link still opening).

Screenshots are in https://claude.ai/code/session_01JgD5nWkvxExYEd7feWgCLE.


Generated by Claude Code

@richshaw richshaw added pipeline:review pipeline:working A session is working on it right now and removed pipeline:tophat pipeline:working A session is working on it right now labels Oct 26, 2026 — with Claude

@richshaw richshaw left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: passed (round 1)

This PR adds a SPELLED map to src/lib/kebab.ts (ø→o, æ→ae, œ→oe, ß/ẞ→ss, ł→l, đ→d, þ→th, ð→d, upper and lower case) and applies it in toKebab after the accents are stripped and before lowercasing. The regex is built from the map's keys. The PR runs the mapping after NFKD, where the issue's outline said before, and that order is the right one: ǿ decomposes to ø plus a combining acute, so mapping first would miss it, and a test covers this. Uppercase letters map to uppercase spellings and are lowercased afterwards, so ẞ doesn't depend on how toLowerCase treats it. The file still has no Node imports, so the builder can keep using it. I checked the description's claim that existing values don't change: every caller (toSlug in pickAvailableSlug, the slot ref in services/slots.ts, the workspace slug in auth/adapter.ts, and toFieldRef in the builder's add-field) only makes new values. Nothing re-derives a stored slug or ref, or compares against one. The opt-out route's slug check still holds, since output is still [a-z0-9-]. Both "Done when" items are done: there's a test for each mapped letter in both cases, and å, ä and ö come out as before.

Blocking (0): none
Optional (0): none
Earlier threads: none. Copilot couldn't review (quota used up).
Checks: CI is green on e2f0948 (lint-type-test, e2e, CodeQL). The tophat passed on this same SHA at 1280 and 390, so runtime code hasn't changed since. It saw the new slugs and refs, a slug made on main still opened, and Cyrillic still fell back to signup. The branch is behind main but merges cleanly. I went through the diff against the repo checklist. It's a pure helper, with no services, schema, public-route, legal, help or budget impact, and the CHANGELOG has an entry under Fixed. I ran nothing locally, since there were no findings to prove.

Why the owner: UX. A new signup's web address, and the refs of new fields and slots (which show up in the CSV export and to an AI assistant), now spell out these letters. "Smørrebrød lunch" gets /s/smorrebrod-lunch-… instead of /s/sm-rrebr-d-lunch-…. Links already shared don't change. See the tophat's 671-after-desktop-public, 671-after-phone-share and 671-after-phone-old-slug.

https://claude.ai/code/session_01PNvKfYWgbETo6DkRk5LsRm


Generated by Claude Code

@richshaw richshaw added pipeline:owner Passed; waits for the owner (UX or high-risk) and removed pipeline:review pipeline:working A session is working on it right now labels Oct 26, 2026 — with Claude
@richshaw richshaw added pipeline:merge and removed pipeline:owner Passed; waits for the owner (UX or high-risk) labels Oct 26, 2026
@richshaw richshaw added the pipeline:working A session is working on it right now label Oct 26, 2026 — with Claude

Copy link
Copy Markdown
Owner Author

Approved by the owner at e2f0948.

https://claude.ai/code/session_01VpmvHcvGXzLtAcbiLtSQ12


Generated by Claude Code

@richshaw richshaw added pipeline:working A session is working on it right now and removed pipeline:working A session is working on it right now labels Oct 26, 2026 — with Claude
@richshaw
richshaw merged commit d093d0d into main Oct 26, 2026
5 checks passed
@richshaw
richshaw deleted the claude/issue-641-kebab-letters branch October 26, 2026 04:54
@richshaw richshaw removed pipeline:merge pipeline:ux Changes what a person using OpenSignup sees or does pipeline:working A session is working on it right now labels Oct 26, 2026 — with Claude
richshaw pushed a commit that referenced this pull request Oct 26, 2026
Through the server, telemetry and screen branches. No conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UW9ZpEu4MWkeqTv2Qe14ks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Signup links and field refs drop ø, æ, ß and ł

3 participants