Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions site/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,14 @@ can return Cloudflare Error 1027 until reset; existing static pages continue
serving. There is no automatic upgrade or overage bill on Free. A paid plan is
an optional decision if homepage/missing-route traffic exceeds the free quota.

Organization JSON-LD uses the existing feedback form. The owner's postal address
is intentionally omitted; the audit's complete Organization check will therefore
remain unmet. Set the build variable `PUBLIC_CONTACT_EMAIL` only after a dedicated
address can receive mail. No placeholder email is published. To create
`contact@tritimes.org`, provision a mailbox or configure Cloudflare Email Routing
with a verified destination inbox and the required DNS records, then verify receipt.
Email provisioning requires mail/DNS access and (for forwarding) a destination inbox.
Organization JSON-LD publishes `support@tritimes.org` alongside the existing
feedback form. Cloudflare Email Routing is enabled for `tritimes.org`, with its
MX, SPF, and DKIM records installed and an enabled exact-address forwarding rule
to the owner's verified private inbox. The destination is managed in Cloudflare;
do not publish it in this repository. Other addresses are not caught by this rule.
This is an inbound forwarding alias, not a mailbox or an outbound email service.
The owner's postal address is intentionally omitted; the audit's complete
Organization check will therefore remain unmet.

## Agent readiness

Expand All @@ -90,12 +91,11 @@ The owner elected to omit a phone number and postal address, so the audit's
PostalAddress recommendation is deliberately unresolved. Do not invent an
address or publish an email before delivery is verified.

For a new `contact@tritimes.org` forwarding address, an administrator needs
Cloudflare Email Routing access and a verified destination inbox. Check existing
mail DNS before enabling routing; preserve any active mail provider. After the
route is active, verify delivery, then set `PUBLIC_CONTACT_EMAIL` at build time to publish it on the Contact
page and in Organization `contactPoint.email`.
No email address has been provisioned by this change.
`support@tritimes.org` is the default in `src/lib/site.ts`, used by the Contact
page and Organization `contactPoint.email`. `PUBLIC_CONTACT_EMAIL` can override
it at build time, but only use an address with a working mailbox or verified
forwarding destination. Confirm Cloudflare routing remains enabled when changing
mail DNS, and test delivery from a different sender than the destination inbox.

After `npm run build`, start `npm run preview`, then run `npm run verify:readiness`.
It fetches every built public file, compares served bytes to the build, parses
Expand Down
3 changes: 2 additions & 1 deletion site/src/lib/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const FEEDBACK_URL = "https://docs.google.com/forms/d/e/1FAIpQLScavYEE5U8
export const SITE_DESCRIPTION = "TriTimes is an independent triathlon results viewer for IRONMAN and IRONMAN 70.3 races, with athlete histories, full-field time distributions, and swim, bike, run, and overall percentiles.";

// The operator chose not to publish a phone number or postal address.
// Add a direct email only once its mailbox or forwarding route is verified.
// Cloudflare Email Routing forwards this address to a verified private inbox.
export const ORGANIZATION = {
"@context": "https://schema.org",
"@type": "Organization",
Expand All @@ -15,6 +15,7 @@ export const ORGANIZATION = {
contactPoint: {
"@type": "ContactPoint",
contactType: "customer support",
email: "support@tritimes.org",
url: FEEDBACK_URL,
},
};
5 changes: 3 additions & 2 deletions site/src/pages/contact.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import TrustPage from "@/components/TrustPage.astro";
import { FEEDBACK_URL } from "@/lib/site";
import { organizationJsonLd } from "@/lib/organization";
const email = organizationJsonLd(import.meta.env.PUBLIC_CONTACT_EMAIL).contactPoint.email;
const description = `Contact TriTimes${email ? " by email or through its feedback form" : " through its feedback form"} to report missing races, incorrect results, site issues, or privacy concerns.`;
---
<TrustPage title="Contact TriTimes" description="Contact TriTimes through its feedback form to report missing races, incorrect results, site issues, or privacy concerns." canonical="/contact">
<TrustPage title="Contact TriTimes" description={description} canonical="/contact">
{email && <p>Email <a class="text-link" href={`mailto:${email}`}>{email}</a> to contact TriTimes.</p>}
<p>Use the <a class="text-link" href={FEEDBACK_URL}>TriTimes feedback form</a> to contact the project about missing races, incorrect results, athlete search problems, accessibility issues, or suggestions. This is the same feedback channel linked from the footer. The form is hosted by Google and opens outside TriTimes; Google's privacy terms apply to information you submit there.</p>
<p>For a results issue, include the TriTimes page URL, the race name and year, the athlete name as published, and a short description of what appears wrong. A link to the corresponding official result helps distinguish a source-data issue from a display problem. For a technical issue, describe the steps you took and the browser or device you used. Do not send passwords, payment details, or other sensitive information.</p>
<p>For a privacy question or a request concerning an athlete listing, use the same feedback form and identify the relevant page and your concern. Review the <a class="text-link" href="/privacy">Privacy page</a> for an explanation of published race information and local browser preferences. TriTimes is an independent project and cannot change official race records; corrections to official timing or registration information should also be raised with the event organizer. There is no guaranteed response time.</p>
<p>For a privacy question or a request concerning an athlete listing, {email ? "email TriTimes or use the feedback form" : "use the feedback form"} and identify the relevant page and your concern. Review the <a class="text-link" href="/privacy">Privacy page</a> for an explanation of published race information and local browser preferences. TriTimes is an independent project and cannot change official race records; corrections to official timing or registration information should also be raised with the event organizer. There is no guaranteed response time.</p>
</TrustPage>
3 changes: 3 additions & 0 deletions site/src/pages/privacy.astro
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
---
import TrustPage from "@/components/TrustPage.astro";
import { organizationJsonLd } from "@/lib/organization";
const hasContactEmail = Boolean(organizationJsonLd(import.meta.env.PUBLIC_CONTACT_EMAIL).contactPoint.email);
---
<TrustPage title="TriTimes Privacy" description="How TriTimes uses published race results, browser preferences, hosting, optional analytics, and its external feedback form." canonical="/privacy">
<p>TriTimes displays publicly published triathlon results, including athlete names, countries, age groups, race placements, and split and finish times where available. Athlete histories bring together results associated with an athlete. This information comes from the results service described in the <a class="text-link" href="/faq">FAQ</a>. A public result can contain errors, and a combined history should not be treated as verified identity information.</p>
<p>The site does not require an account to search athletes or view results. Search matching runs in your browser after downloading result indexes. Requests for search partitions, race data, and athlete data still reach the hosting provider. TriTimes uses your browser's local storage to remember an explicitly chosen light or dark theme and races you hide from an athlete view. You can remove those preferences by clearing this site's browser storage.</p>
<p>Cloudflare serves the site and receives the network information needed to handle requests, such as IP addresses and requested URLs. When a Cloudflare Web Analytics token is configured for a build, the site also loads Cloudflare's analytics beacon. These services have their own data practices; see <a class="text-link" href="https://www.cloudflare.com/privacypolicy/">Cloudflare's privacy policy</a>. This page does not promise a retention period for provider logs.</p>
<p>The feedback link leads to a Google-hosted form. Information you choose to submit is handled through that service, subject to <a class="text-link" href="https://policies.google.com/privacy">Google's privacy policy</a>. Avoid submitting sensitive personal information. For questions about a listing or a privacy concern, follow the instructions on the <a class="text-link" href="/contact">Contact page</a> and include the relevant TriTimes URL.</p>
{hasContactEmail && <p>Email sent to the support address on the Contact page is routed by Cloudflare to the project's private support inbox. Your email address, message, and any attachments are processed by Cloudflare and the receiving email provider so the project can review and respond to your request. Avoid including sensitive information that is not needed to explain your concern.</p>}
</TrustPage>
6 changes: 3 additions & 3 deletions site/tests/agents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@ test("Vary is case insensitive, idempotent, and preserves wildcard", () => {
}
});

test("Organization uses the existing feedback contact without publishing an address or invented email", () => {
test("Organization publishes the verified support email and feedback form without a postal address", () => {
const schema = organizationJsonLd();
assert.equal(schema["@type"], "Organization");
assert.equal(schema.contactPoint["@type"], "ContactPoint");
assert.ok(schema.contactPoint.contactType);
assert.ok(schema.contactPoint.url.startsWith("https://docs.google.com/forms/"));
assert.ok(!("address" in schema));
assert.ok(!("email" in schema.contactPoint));
assert.equal(organizationJsonLd("support@tritimes.org").contactPoint.email, "support@tritimes.org");
assert.equal(schema.contactPoint.email, "support@tritimes.org");
assert.equal(organizationJsonLd("other@tritimes.org").contactPoint.email, "other@tritimes.org");
assert.throws(() => organizationJsonLd("not an email"));
});

Expand Down
Loading