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
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Community activity happens on a separate Discourse instance. Its display name is
- The og:image file is public/og.png and its full URL is https://offon.dev/og.png.
- PR preview deployments are served from the gh-pages branch under /pr-preview/pr-{number}/.
- The open source challenges content lives in a separate organisation at https://github.com/dynatrace-oss/open-ecosystem-challenges. This is an intentional external link and must never be changed or flagged as a violation.
- The community Discourse instance is at https://community.open-ecosystem.com. Use the `COMMUNITY_URL` constant from `src/data/constants.ts`, never hardcode this URL.
- The community Discourse instance is at https://community.offon.dev. Use the `COMMUNITY_URL` constant from `src/data/constants.ts`, never hardcode this URL.
- `COMMUNITY_DISPLAY_NAME` is defined in `src/data/constants.ts` as the user-facing display name for the community URL. Use it for visible text, use `COMMUNITY_URL` for href attributes.

---
Expand Down Expand Up @@ -221,7 +221,7 @@ When diagnosing a bug, especially in the production build, follow these rules wi
- **Build-time fetching:** Discussion data lives in per-level JSON files under `src/data/adventures/<adventure-id>/<level-id>-posts.json`. Each file contains only `discussionUrl`, `discussionPosts`, and `totalReplies`. These are refreshed hourly by the GitHub Action in `.github/workflows/refresh-community-data.yml` (runs `scripts/refresh-discussions.mjs`). Components import the JSON dynamically via `import.meta.glob`.
- When adding a new adventure level, create its per-level discussion JSON file (`<level-id>-posts.json`) with a `discussionUrl` field. The refresh script uses this URL to fetch posts.
- `scripts/refresh-discussions.mjs`, `scripts/refresh-leaderboard.mjs`, and `scripts/refresh-community-leaders.mjs` each contain a `COMMUNITY_BASE` constant that is a necessary duplicate of `COMMUNITY_URL` in `src/data/constants.ts`. The scripts run in Node and cannot import from `src/`, so the value must be maintained manually in all four places. Always update them together.
- The domain `community.open-ecosystem.com` in the three refresh scripts and `src/data/constants.ts` is the actual Discourse server URL used for API calls at build time. It is not a brand inconsistency. Do not change it to `community.offon.dev` or any other display name. `COMMUNITY_DISPLAY_NAME` in `src/data/constants.ts` is the separate user-facing label shown in the UI.
- The domain `community.offon.dev` in the three refresh scripts and `src/data/constants.ts` is the actual Discourse server URL used for API calls at build time. `COMMUNITY_DISPLAY_NAME` in `src/data/constants.ts` is the separate user-facing label shown in the UI. Always update all four places together.

---

Expand Down
7 changes: 3 additions & 4 deletions scripts/refresh-community-leaders.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
* Writes:
* src/data/community-leaders.json
*
* NOTE: community.open-ecosystem.com here is the actual Discourse server URL.
* It is not the same as the display name community.offon.dev. Do not change it.
* NOTE: community.offon.dev is the actual Discourse server URL.
*/

import { readFileSync, writeFileSync, existsSync } from "node:fs";
Expand All @@ -35,7 +34,7 @@ import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = resolve(__dirname, "..");
const OUT_PATH = resolve(ROOT, "src/data/community-leaders.json");
const COMMUNITY_BASE = "https://community.open-ecosystem.com";
const COMMUNITY_BASE = "https://community.offon.dev";

// Query IDs in Discourse Data Explorer.
const COMMUNITY_QUERY_ID = 7;
Expand Down Expand Up @@ -82,7 +81,7 @@ function buildAvatarUrl(username, uploadedAvatarId, size = AVATAR_SIZE) {
const letter = username.charAt(0).toLowerCase();
return `https://avatars.discourse-cdn.com/v4/letter/${letter}/b5a626/${size}.png`;
}
return `${COMMUNITY_BASE}/user_avatar/community.open-ecosystem.com/${encodeURIComponent(username)}/${size}/${uploadedAvatarId}_2.png`;
return `${COMMUNITY_BASE}/user_avatar/community.offon.dev/${encodeURIComponent(username)}/${size}/${uploadedAvatarId}_2.png`;
}

async function runQuery(queryId, params, apiKey, apiUsername) {
Expand Down
2 changes: 1 addition & 1 deletion scripts/refresh-discussions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { join, resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = dirname(fileURLToPath(import.meta.url));
const COMMUNITY_BASE = "https://community.open-ecosystem.com";
const COMMUNITY_BASE = "https://community.offon.dev";
const ADVENTURES_DIR = resolve(__dirname, "../src/data/adventures");

/**
Expand Down
8 changes: 3 additions & 5 deletions scripts/refresh-leaderboard.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@
* Writes per-adventure leaderboard data to:
* src/data/adventures/<adventure-id>/leaderboard.json
*
* NOTE: community.open-ecosystem.com here is the actual Discourse server URL used
* for API calls. It is not the same as the display name community.offon.dev.
* Do not change it. See CLAUDE.md for context.
* NOTE: community.offon.dev is the actual Discourse server URL used for API calls.
*/

import { readFileSync, writeFileSync, existsSync } from "node:fs";
Expand All @@ -28,12 +26,12 @@ import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = resolve(__dirname, "..");
const ADVENTURES_DIR = resolve(ROOT, "src/data/adventures");
const COMMUNITY_BASE = "https://community.open-ecosystem.com";
const COMMUNITY_BASE = "https://community.offon.dev";
const QUERY_ID = 5;

// Maps adventure ID -> Discourse category ID and which difficulty levels are active.
// Update this when a new adventure is added.
// Category IDs are from: GET https://community.open-ecosystem.com/categories.json
// Category IDs are from: GET https://community.offon.dev/categories.json
const ADVENTURE_CATEGORIES = {
"echoes-lost-in-orbit": { categoryId: 35, has_beginner: true, has_intermediate: true, has_expert: true, has_single: false },
"building-cloudhaven": { categoryId: 36, has_beginner: true, has_intermediate: true, has_expert: true, has_single: false },
Expand Down
10 changes: 7 additions & 3 deletions src/components/ConsentBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@ export function ConsentBanner(): JSX.Element | null {
const { consent, grant, deny, reset } = useConsent();
const [mounted, setMounted] = useState(false);
const declineRef = useRef<HTMLButtonElement>(null);
const prevConsentRef = useRef<string | null | undefined>(undefined);

useEffect(() => {
setMounted(true); // eslint-disable-line react-hooks/set-state-in-effect -- mount guard; SSG requires a safe default (null) on first render so the banner is absent from prerendered HTML and cannot become the LCP element
}, []);

// Move focus to Decline when the banner appears (initial visit or after reset)
// so keyboard users are not left with focus stranded on an unmounted button.
// Move focus to Decline only when the banner reappears after a reset (consent
// transitions from non-null to null). Skips the initial page-load case so the
// banner never steals focus from the skip nav link.
useEffect(() => {
if (mounted && consent === null) {
const prevConsent = prevConsentRef.current;
prevConsentRef.current = consent;
if (mounted && consent === null && prevConsent != null) {
declineRef.current?.focus();
}
}, [mounted, consent]);
Expand Down
8 changes: 4 additions & 4 deletions src/data/adventures/blind-by-design/beginner-posts.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
{
"discussionUrl": "https://community.open-ecosystem.com/t/wire-openfeature-flagd-into-a-spring-boot-service-with-zero-setup-adventure-04-beginner/1419",
"discussionUrl": "https://community.offon.dev/t/wire-openfeature-flagd-into-a-spring-boot-service-with-zero-setup-adventure-04-beginner/1419",
"discussionPosts": [
{
"username": "theharithsa",
"avatarUrl": "https://community.open-ecosystem.com/user_avatar/community.open-ecosystem.com/theharithsa/40/297_2.png",
"avatarUrl": "https://community.offon.dev/user_avatar/community.offon.dev/theharithsa/40/297_2.png",
"cooked": "It was a cool challenge. Thanks, Simon. Tip: For me, when Codespaces started, the ports were private. After changing the port visibility to Public, the website started to load; otherwise, we got a 502 error.",
"created_at": "2026-05-05T07:27:53.930Z",
"challengeSolved": true,
"topicUrl": "https://community.open-ecosystem.com/t/wire-openfeature-flagd-into-a-spring-boot-service-with-zero-setup-adventure-04-beginner/1419"
"topicUrl": "https://community.offon.dev/t/wire-openfeature-flagd-into-a-spring-boot-service-with-zero-setup-adventure-04-beginner/1419"
}
],
"totalReplies": 1,
"solvers": [
{
"username": "theharithsa",
"avatarUrl": "https://community.open-ecosystem.com/user_avatar/community.open-ecosystem.com/theharithsa/40/297_2.png",
"avatarUrl": "https://community.offon.dev/user_avatar/community.offon.dev/theharithsa/40/297_2.png",
"solvedAt": "2026-05-05T07:27:53.930Z"
}
]
Expand Down
8 changes: 4 additions & 4 deletions src/data/adventures/blind-by-design/expert-posts.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
{
"discussionUrl": "https://community.open-ecosystem.com/t/read-the-chart-adventure-04-expert/1530",
"discussionUrl": "https://community.offon.dev/t/read-the-chart-adventure-04-expert/1530",
"discussionPosts": [
{
"username": "theharithsa",
"avatarUrl": "https://community.open-ecosystem.com/user_avatar/community.open-ecosystem.com/theharithsa/40/297_2.png",
"avatarUrl": "https://community.offon.dev/user_avatar/community.offon.dev/theharithsa/40/297_2.png",
"cooked": "Done with the challenge!!! — CERTIFICATE START — Adventure: 04-blind-by-design Level: expert User: Harithsa, Vishruth Repo: theharithsa/open-ecosystem-challenges Date: Wed May 20 12:19:44 PM UTC 2026 Url: — CERTIFICATE END — It is so satisfying to see traces flowing and turn on the lights without redeploying the lab. It was a fun challenge. Thanks again @simon.schrottner and thanks for posting @KatharinaSick",
"created_at": "2026-05-20T12:25:39.159Z",
"challengeSolved": true,
"topicUrl": "https://community.open-ecosystem.com/t/read-the-chart-adventure-04-expert/1530"
"topicUrl": "https://community.offon.dev/t/read-the-chart-adventure-04-expert/1530"
}
],
"totalReplies": 1,
"solvers": [
{
"username": "theharithsa",
"avatarUrl": "https://community.open-ecosystem.com/user_avatar/community.open-ecosystem.com/theharithsa/40/297_2.png",
"avatarUrl": "https://community.offon.dev/user_avatar/community.offon.dev/theharithsa/40/297_2.png",
"solvedAt": "2026-05-20T12:25:39.159Z"
}
]
Expand Down
8 changes: 4 additions & 4 deletions src/data/adventures/blind-by-design/intermediate-posts.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
{
"discussionUrl": "https://community.open-ecosystem.com/t/outcome-by-cohort-adventure-04-intermediate/1485",
"discussionUrl": "https://community.offon.dev/t/outcome-by-cohort-adventure-04-intermediate/1485",
"discussionPosts": [
{
"username": "theharithsa",
"avatarUrl": "https://community.open-ecosystem.com/user_avatar/community.open-ecosystem.com/theharithsa/40/297_2.png",
"avatarUrl": "https://community.offon.dev/user_avatar/community.offon.dev/theharithsa/40/297_2.png",
"cooked": "Intermediate Challenge Accepted and Completed!! These challenges are getting more fun with each new one. I’m learning many in-depth things about OSS and Observability. --- CERTIFICATE START --- Adventure: 04-blind-by-design Level: intermediate User: Harithsa, Vishruth Repo: theharithsa/open-ecosystem-challenges Date: Tue May 19 04:30:36 AM UTC 2026 Url: --- CERTIFICATE END --- I encountered one issue when pushing the final changes to GitHub. The application generates an app.log file, which needs to be added to the .gitignore file before committing the changes to Git. Otherwise, the ./verify.sh script incorrectly reports uncommitted changes in the repository.",
"created_at": "2026-05-19T04:37:22.351Z",
"challengeSolved": true,
"topicUrl": "https://community.open-ecosystem.com/t/outcome-by-cohort-adventure-04-intermediate/1485"
"topicUrl": "https://community.offon.dev/t/outcome-by-cohort-adventure-04-intermediate/1485"
}
],
"totalReplies": 1,
"solvers": [
{
"username": "theharithsa",
"avatarUrl": "https://community.open-ecosystem.com/user_avatar/community.open-ecosystem.com/theharithsa/40/297_2.png",
"avatarUrl": "https://community.offon.dev/user_avatar/community.offon.dev/theharithsa/40/297_2.png",
"solvedAt": "2026-05-19T04:37:22.351Z"
}
]
Expand Down
2 changes: 1 addition & 1 deletion src/data/adventures/blind-by-design/leaderboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{
"rank": 1,
"username": "theharithsa",
"avatarUrl": "https://community.open-ecosystem.com/user_avatar/community.open-ecosystem.com/theharithsa/90/297.png",
"avatarUrl": "https://community.offon.dev/user_avatar/community.offon.dev/theharithsa/90/297.png",
"points": 300,
"challengesSolved": 3,
"beginnerPoints": 100,
Expand Down
34 changes: 17 additions & 17 deletions src/data/adventures/building-cloudhaven/beginner-posts.json
Original file line number Diff line number Diff line change
@@ -1,65 +1,65 @@
{
"discussionUrl": "https://community.open-ecosystem.com/t/practice-infrastructure-as-code-with-zero-setup-adventure-02-beginner/656",
"discussionUrl": "https://community.offon.dev/t/practice-infrastructure-as-code-with-zero-setup-adventure-02-beginner/656",
"discussionPosts": [
{
"username": "KatharinaSick",
"avatarUrl": "https://community.open-ecosystem.com/user_avatar/community.open-ecosystem.com/katharinasick/40/9_2.png",
"avatarUrl": "https://community.offon.dev/user_avatar/community.offon.dev/katharinasick/40/9_2.png",
"cooked": "The intermediate level is now live: Adventure 02: Building CloudHaven | Intermediate: The Modular Metropolis",
"created_at": "2026-01-21T13:02:00.099Z",
"topicUrl": "https://community.open-ecosystem.com/t/practice-infrastructure-as-code-with-zero-setup-adventure-02-beginner/656"
"topicUrl": "https://community.offon.dev/t/practice-infrastructure-as-code-with-zero-setup-adventure-02-beginner/656"
},
{
"username": "KatharinaSick",
"avatarUrl": "https://community.open-ecosystem.com/user_avatar/community.open-ecosystem.com/katharinasick/40/9_2.png",
"avatarUrl": "https://community.offon.dev/user_avatar/community.offon.dev/katharinasick/40/9_2.png",
"cooked": "Thanks for letting us know! I’ll take a look. I hope it didn’t cost you too much time",
"created_at": "2026-01-18T05:22:50.240Z",
"topicUrl": "https://community.open-ecosystem.com/t/practice-infrastructure-as-code-with-zero-setup-adventure-02-beginner/656"
"topicUrl": "https://community.offon.dev/t/practice-infrastructure-as-code-with-zero-setup-adventure-02-beginner/656"
},
{
"username": "justinrand",
"avatarUrl": "https://community.open-ecosystem.com/user_avatar/community.open-ecosystem.com/justinrand/40/140_2.png",
"avatarUrl": "https://community.offon.dev/user_avatar/community.offon.dev/justinrand/40/140_2.png",
"cooked": "Had to rerun the post-start.sh script to get the gcp “credentials” to work.",
"created_at": "2026-01-18T04:11:43.746Z",
"challengeSolved": true,
"topicUrl": "https://community.open-ecosystem.com/t/practice-infrastructure-as-code-with-zero-setup-adventure-02-beginner/656"
"topicUrl": "https://community.offon.dev/t/practice-infrastructure-as-code-with-zero-setup-adventure-02-beginner/656"
},
{
"username": "enri-kapaj",
"avatarUrl": "https://community.open-ecosystem.com/user_avatar/community.open-ecosystem.com/enri-kapaj/40/165_2.png",
"avatarUrl": "https://community.offon.dev/user_avatar/community.offon.dev/enri-kapaj/40/165_2.png",
"cooked": "Done",
"created_at": "2026-01-16T18:02:15.040Z",
"challengeSolved": true,
"topicUrl": "https://community.open-ecosystem.com/t/practice-infrastructure-as-code-with-zero-setup-adventure-02-beginner/656"
"topicUrl": "https://community.offon.dev/t/practice-infrastructure-as-code-with-zero-setup-adventure-02-beginner/656"
},
{
"username": "alvin",
"avatarUrl": "https://avatars.discourse-cdn.com/v4/letter/a/b5a626/40.png",
"cooked": "Thanks for the challenge!",
"created_at": "2026-01-14T23:51:19.893Z",
"challengeSolved": true,
"topicUrl": "https://community.open-ecosystem.com/t/practice-infrastructure-as-code-with-zero-setup-adventure-02-beginner/656"
"topicUrl": "https://community.offon.dev/t/practice-infrastructure-as-code-with-zero-setup-adventure-02-beginner/656"
},
{
"username": "DavidHirsch",
"avatarUrl": "https://community.open-ecosystem.com/user_avatar/community.open-ecosystem.com/davidhirsch/40/8_2.png",
"avatarUrl": "https://community.offon.dev/user_avatar/community.offon.dev/davidhirsch/40/8_2.png",
"cooked": "Wow you are definitely the fastest challenger!",
"created_at": "2026-01-12T21:28:18.279Z",
"topicUrl": "https://community.open-ecosystem.com/t/practice-infrastructure-as-code-with-zero-setup-adventure-02-beginner/656"
"topicUrl": "https://community.offon.dev/t/practice-infrastructure-as-code-with-zero-setup-adventure-02-beginner/656"
},
{
"username": "gsigmund",
"avatarUrl": "https://community.open-ecosystem.com/user_avatar/community.open-ecosystem.com/gsigmund/40/99_2.png",
"avatarUrl": "https://community.offon.dev/user_avatar/community.offon.dev/gsigmund/40/99_2.png",
"cooked": "Completed the challenge.",
"created_at": "2026-01-12T21:22:52.209Z",
"challengeSolved": true,
"topicUrl": "https://community.open-ecosystem.com/t/practice-infrastructure-as-code-with-zero-setup-adventure-02-beginner/656"
"topicUrl": "https://community.offon.dev/t/practice-infrastructure-as-code-with-zero-setup-adventure-02-beginner/656"
}
],
"totalReplies": 7,
"solvers": [
{
"username": "gsigmund",
"avatarUrl": "https://community.open-ecosystem.com/user_avatar/community.open-ecosystem.com/gsigmund/40/99_2.png",
"avatarUrl": "https://community.offon.dev/user_avatar/community.offon.dev/gsigmund/40/99_2.png",
"solvedAt": "2026-01-12T21:22:52.209Z"
},
{
Expand All @@ -69,12 +69,12 @@
},
{
"username": "enri-kapaj",
"avatarUrl": "https://community.open-ecosystem.com/user_avatar/community.open-ecosystem.com/enri-kapaj/40/165_2.png",
"avatarUrl": "https://community.offon.dev/user_avatar/community.offon.dev/enri-kapaj/40/165_2.png",
"solvedAt": "2026-01-16T18:02:15.040Z"
},
{
"username": "justinrand",
"avatarUrl": "https://community.open-ecosystem.com/user_avatar/community.open-ecosystem.com/justinrand/40/140_2.png",
"avatarUrl": "https://community.offon.dev/user_avatar/community.offon.dev/justinrand/40/140_2.png",
"solvedAt": "2026-01-18T04:11:43.746Z"
}
]
Expand Down
Loading
Loading