Public skipper profile¶
| Rails | |
|---|---|
| Status | ✅ |
| Code | app/controllers/{profiles,profile_settings,profile_picks}_controller.rb, app/controllers/concerns/{profile_urls,public_replay_points}.rb, app/constraints/skipper_host_constraint.rb, app/models/{profile_trip,profile_handle_reservation}.rb, app/models/user.rb, app/helpers/profile_helper.rb, app/views/profiles/, app/views/accounts/_public_profile.html.erb, app/views/trips/_profile_pick.html.erb, app/views/public_trips/show.html.erb, app/javascript/controllers/{image_crop,globe}_controller.js, app/assets/stylesheets/components/profile.css, config/locales/profile.en.yml |
Verified against code: 2026-11-01
Purpose¶
A skipper's public page: a sailing CV to send to a charter company, a crew, a sailing school or friends. It shows the logbook statistics over all trips and the trips the skipper picked. Off until the skipper turns it on. A PRO feature (User#pro?; every user during the beta). Decisions: ADR 019.
Address¶
https://skipper.sailever.com/<handle> (the skipper host, SAILEVER_SKIPPER_HOST). Every other host answers at /s/<handle>; with a skipper host configured, /s/<handle> redirects there (301). Without one (development, tests) /s/<handle> is the page. Trips on the profile: …/<handle>/trips/<trip id>, their replay …/trips/<trip id>/replay. The skipper host answers nothing but these; / sends to the web site.
Handle¶
- 3–30 characters: lowercase letters, digits and single hyphens; starts with a letter, ends with a letter or digit. Stored lowercase, unique.
- Reserved words are refused (
admin,api,my,s,t,www,skipper,sailever, the locale codes and others inUser::RESERVED_HANDLES). - The first handle is free to set. After a change, the handle can't change again for 30 days ("You changed your handle recently. You can change it again from
."; the field is read-only), and the old handle stays reserved for its owner for 30 days ( profile_handle_reservations): nobody else gets it ("is taken"), the owner may take it back. The old address is not redirected. Deleting the account frees the handle at once.
Settings¶
Profile tab (/my/profile#public-profile), card Public skipper profile with a PRO badge, its own form and "Save profile":
Three groups, each with a small caps title:
| Group | Field | Rule |
|---|---|---|
| Address | Handle | Text field after the address prefix (skipper.sailever.com/ or <host>/s/); the rules are behind a (?) (Handle). While locked, "You changed your handle recently. You can change it again from |
| Visibility | Show my public profile | Checkbox, off by default. Off: the page and its trips answer 404 at once. Settings and images are kept |
| Visibility | Let search engines find my profile | Checkbox, off by default; see Indexing |
| Images | Avatar, Header image (4:1) | See Images |
Below them a line saying what the page shows, and, while the profile is visible, "View my profile" (opens it in a new tab). Saved: notice "Profile saved."; errors show above the fields.
Images¶
Two images: an avatar (square, always shown round by CSS) and a header image (4:1).
- Upload opens the file picker (JPEG, PNG, WebP, HEIC; at most 15 MB, else "This image is larger than 15 MB."). The crop dialog opens on it ("Choose your avatar" / "Choose your header image"): the image under a fixed frame (1:1 with a round mask, or 4:1). The skipper drags the image and zooms (mouse wheel, pinch, the zoom buttons, + / −; arrow keys move it); a live preview shows the result. An original smaller than 400 × 400 (avatar) or 1600 × 400 (header) shows "This image is too small. It needs at least W × H pixels." and can't be used.
- Use this part draws the chosen part in the browser at 512 × 512 (avatar) or 2400 × 600 (header) and encodes it as WebP. This drops all photo metadata (camera, date, GPS). The form then sends the cropped image, the original, and the part as
{x, y, w, h}(fractions of the original). - Change crop opens the dialog on the stored original (
/my/profile/images/<kind>/original, only for its owner, never cached) with the last part selected. Saving sends only the new cropped image and part. - Remove (confirm "Remove this image?") deletes the image and its original; notice "Image removed."
- The server checks the type from the file's first bytes (shown image: WebP, JPEG or PNG, at most 3 MB; original: JPEG, PNG, WebP or HEIC, at most 15 MB).
- Shown images are served through the Active Storage proxy. With
SAILEVER_IMAGE_HOSTset (production:images.sailever.com, Cloudflare in front), their URLs use that host; the proxy answers with a year-long public cache, and every upload is a new blob key, so a new image is a new URL and caches never need purging. Originals are never linked from any page.
Trips¶
A trip is on the profile only when its user picked it. On the trip page (owner, skipper and crew) the page-head buttons start with:
| State | Button |
|---|---|
| No handle yet | "Set up your public profile" (→ Account → Profile) |
| Not picked | "Show on my public profile" |
| Picked | "On your public profile" (green); a click takes it off |
Each user picks for their own profile: a crew member may show a trip they sailed on. Notices: "The trip is on your public profile." / "The trip is no longer on your public profile." Leaving or being removed from the crew takes the trip off that member's profile; deleting the trip or the account removes the pick.
Head¶
The skipper header, also on the logbook:
- the header image (4:1, rounded corners), or without one a dark band (8:1);
- over its lower left edge the round avatar (104 px, 76 px on phones) with a border in the page colour, or the name's initial on orange;
- the title "
's Logbook" (the name, e.g. "Jakob's Logbook"), then "@handle" and "Skipper since " (the year of the first counted day on a skippered trip) or "Sailing since " when the user never skippered; - below it the sailor bio, when written.
Name¶
The first name when the user shows it publicly (sharing.md), else the handle. On their own logbook before they have a handle: the first name or the start of their email address.
Layout¶
- Head, with the bio.
- The four totals and distance per year, as on the logbook.
- How the nights were spent and Boat length and type (logbook.md).
- Where
sailed : the globe with a dot at the exact start of every trip with counted days. Picked trips are orange, sized by miles, and open a popup linking to the profile trip page. The others are small grey dots: not clickable, and the page has no title, date or link of them (the data holds only their position and miles). Hint: "Orange: tripsshares, click one · grey: other trips · drag to turn, scroll or +/− to zoom". - Trips: a card per picked trip, newest first: title (→ profile trip page), "Mon YYYY · N days · X.X NM · Skipper|Crew", the boat's model and length. None picked: "
hasn't picked any trips for this page yet." - Footer as on the public trip page.
Not shown anywhere: email, last name, crew names beyond the public trip page's rule, dates or titles of unpicked trips, devices, home port of boats.
Profile trip page¶
…/<handle>/trips/<trip id>: the public trip page for a picked trip, with a back link "
Indexing¶
The profile and its trips send X-Robots-Tag: noindex, nofollow and a robots meta tag, unless the skipper ticked "Let search engines find my profile": then the profile page (only) may be listed. robots.txt disallows /s/*/trips/ and /*/trips/ while indexing is on (SAILEVER_INDEXING).
Link preview¶
Open Graph title "
Data¶
users.profile_handle, profile_public, profile_indexable, profile_handle_changed_at, avatar_crop, header_crop; attachments avatar, avatar_original, header_image, header_original; tables profile_trips and profile_handle_reservations (data-model.md). All go with the account.
Known deviations¶
- Every user is PRO until a plan model exists; a lapsed plan should answer 410 "This profile is not active" (not built).
- No sitemap exists yet, so indexable profiles aren't listed in one.
- The link preview uses the header image, not a drawn picture of the globe with the numbers.