feat: add multi-contributor credit system for adventures - #235
Merged
Conversation
- Distinguish Adventure Designer (ideator) from Challenge Builder (level builder) in contributor badges and sidebar labels - Auto-derive Challenge Builders and Adventure Designers leaderboard sections from adventure YAML data instead of Discourse badges - Add discourse_username field to contributor schema for Discourse avatar cross-referencing - Restore challenge-builders/challenge-grand-builders sections to community-leaders.json refresh script so Discourse avatar cache remains populated - Sort Challenge Builders section by number of adventures contributed (descending) - Add Adventure Designers to challenges page leaderboard (end of list) - Add lightbulb icon for Adventure Designers section - Remove ADVENTURE_CONTRIBUTORS static list from team.ts; source all contributor data from the content collection Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Fix expert.ts: replace placeholder contributor with KATHARINA_SICK constant - Fix CommunitySidebar: pass proposer prop so adventure contributors get "Adventure Builder" label instead of "Challenge Builder" - Fix ChallengeBuildersSection: destructure and render roleLabel per contribution - Fix CommunityLeaders: credit adventure contributor for levels with no explicit level.contributor in mixed-attribution adventures; remove unused hasLevelContributors variable - Fix AdventureCard: compute contributor pill even when adventure.contributor is absent but level contributors exist; tighten pill gate to avoid empty div - Fix adventures/index.astro: rename ArrowRightIcon → ARROW_RIGHT_ICON, rawHowItWorks → inline array per SCREAMING_SNAKE_CASE convention - Fix [id].astro: type levelBuilderMap levels as Difficulty[] instead of string[], import Difficulty, remove unsafe cast on DifficultyBadge - Fix CommunitySidebar: rename avatarPalette → AVATAR_PALETTE - Fix solution.astro: make sr-only Solution Steps heading unconditional - Update community-leaders test: avatarUrl is now optional in schema - Add CommunityLeaders comment: username field in adventure sections is a display name, not a Discourse handle - Update ADVENTURES.md: document discourse_username on contributor, add level contributor field to level fields table and YAML example - Update styleguide.md: ContributorBadge new props, ChallengeBuildersSection data source, CommunitySidebar levelContributor prop, AdventureCard pill conditions, CommunityLeaders data source split Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- WCAG 2.1.2: tab traversal now detects repeating focus patterns (cycle length 1-5) that exclude the page's first focusable element, signalling focus is stuck inside a subset of elements - WCAG 3.2.1: each tab step snapshots location.href before and after; fails if a URL change is triggered by focus alone - both checks share a single traversal per page to avoid doubling test time relative to the existing focus ring traversal - ACCESSIBILITY.md: expand automated testing section into a full table listing all ten automated checks with their WCAG criterion; add 2.4.13 to the AAA targets table; add 3.2.1 to the WCAG reference table Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
Contributor URL links in ContributorBadge were rendering at 16px tall, below the 24px WCAG 2.5.8 minimum. Added min-h-[24px] to both link variants (primary proposer and compound builder). Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
Contributor
|
…oard JSON - `echoes-lost-in-orbit/intermediate.ts` credited Simon Schrottner, but nothing in the repo attributes that walkthrough to him and the original commit used `KATHARINA_SICK`. Reverted to the shared constant, matching the other two echoes solution files. - `community-leaders.json` had been hand-edited into a shape the refresh script cannot emit: an empty `challenge-grand-builders` array, which `.filter(s => s.users.length > 0)` rules out. Restored the real Discourse data and reordered the sections to match the order the script now writes. - Documented why `challenge-builders` and `challenge-grand-builders` stay in the payload at all: `CommunityLeaders.astro` discards their user lists and derives both sections from the adventures collection, but every section is scanned to map a `discourse_username` to a real avatar. Removing them would silently downgrade builder avatars to letter avatars rather than just drop a section. Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
`ChallengeBuildersSection` and `CommunityLeaders` each derived "who built what" inline, with different rules. The former disabled the adventure-contributor fallback for every level as soon as any level named its own builder; the latter applied the fallback per level. With partial coverage the same person was credited with two different level counts on the same page. - New `src/lib/adventure-credit.ts` owns the rule for all five consumers: `AdventureCard`, the adventure page aside, `CommunitySidebar`, `ChallengeBuildersSection` and `CommunityLeaders`. Pure functions over plain data, no `astro:content` import, so the rules are unit-testable without a build. - The rule is per level, not all-or-nothing: a designer who builds two of three levels keeps credit for those two while a guest builder takes the third. This also fixes the adventure page aside, which previously listed only explicit level contributors and so dropped the designer's own build credit entirely. - `creditIntegrityError` rejects the one shape the design forbids — levels naming a builder on an adventure that names no designer — as a schema `superRefine`, so it fails `astro sync` and the build with the adventure named and the fix spelled out. Adventures with neither stay valid: `sync-adventure.mjs` deliberately omits `contributor` and a reviewer adds it during review. - `levelBuilderCredit` compares designer and level contributor by name, so naming the designer explicitly on a level no longer flips their pill label from "Adventure Builder" to "Challenge Builder". - Leaderboard rendering separates a Discourse handle from a display name. `LeaderRow.displayName` is the visible text, `discourseUsername` resolves the avatar; `AvatarLink`'s prop is renamed to match. Previously both meanings shared one `username` field, so the card mixed real names with handles. - `adventure-designers` now appears on all six leaderboard surfaces. The `sections` prop is typed `SectionId[]` and is a set, not an order: the card always renders in `SECTION_ICON_NAMES` order, so it reads the same everywhere. - `avatarUrl` is `z.url().optional()` — optional because `buildAvatarUrl` can return undefined, but format-checked because it goes into an `<img src>`. - Fixes two smaller defects in the process: the role sort was computed and never used while the rendered label used the unsorted array, and "built everything" was tested against unique difficulties rather than level count, so two levels sharing a difficulty broke the collapse. 41 unit tests cover the rules, weighted to partial coverage, including one that asserts the section body and the leaderboard counts agree on the same fixture. Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
The pill had been restructured from an anchor into a span wrapping an inline anchor, which left the interactive element 113x16px — below the 24x24 minimum in WCAG 2.5.8 — and dropped the hover affordance in dark mode. Note this was never an axe failure: axe honours the Inline exception for 2.5.8, so it passed throughout. The gate is the hand-written sweep in `e2e/a11y.spec.ts`, which is deliberately stricter. - One credit: the pill is the anchor again, so the pill itself is the 26px target and carries the hover styles. No `min-h` needed, and no added height. - Two credits: the pill cannot be an anchor, so each inner link carries its own target and hover state. `min-h-6` gives a 24px border box and `-my-1` pulls it back inside the pill's `py-1`, keeping the pill at 26px instead of 34px. - Extracted `ContributorPill` so the shape rules live in one place. `ContributorBadge` now only derives labels and delegates, and the solution page passes its own "Solution Contributor" label instead of carrying a 22-line copy of the pill markup that had drifted into behaving differently. Measured after the change: single-credit pill `<a>` 269x26 with hover in both themes; compound pill `<span>` 503x26 with inner links 108x24 and 125x24, hover per link. The solution page renders pixel-identically to before the extraction. Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
Nothing in the suite referenced the feature, so every test would have passed unchanged if it were reverted. 16 tests over the built pages: pill labels and shape, the Contributors aside, the role-label strings, the Adventure Designers section showing real names rather than handles, and canonical section order on all six surfaces. Verified discriminating: 13 of the 16 fail against a build of `main`. The other three are labelled in-file as shape guards rather than feature coverage, since they hold for the pre-feature markup too. Both describes pin an explicit viewport and every navigation waits for load: the Community Leaders card is `hidden lg:block`, so tests that read it must not inherit the runner's default viewport or race layout. Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- `src/lib/community-leaders.ts` and `src/lib/community-data.ts` import `zod` directly, but it was only resolvable because npm hoists Astro's transitive copy. Declared at `^4.4.3` to match; `npm ls zod` confirms a single deduped copy. `content.config.ts` already relies on zod 4 semantics via `z.url()`. - `tsconfig.json` excludes `public` (vendored, minified reveal.js), plus the gitignored `coverage` and `test-results`. Nothing imports any of it, and it was the entire source of `astro check`'s hint noise: 46 hints down to 4, and no more multi-kilobyte minified lines in the output. Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
`styleguide.md` requires overline and eyebrow labels to be written lowercase in source because CSS applies `text-transform: uppercase`. Several were title case. Rendering is unchanged — confirmed by the visual baselines, where `brand` comes out byte-identical across 15 changed lines. Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- `styleguide.md`: new `ContributorPill` entry with the two pill shapes and why the single-credit one must not gain a `min-h`; a new "Adventure credit" section mapping each consumer to its function; updated `ContributorBadge`, `ChallengeBuildersSection` and `CommunityLeaders` entries. - `CLAUDE.md` and `AGENTS.md`: `astro check` run without a preceding `astro sync` types the collection as `never` and emits 150+ phantom errors from a stale `.astro/types.d.ts`. Added beside the existing cache note. CI already sequences them, so this is local-only. AGENTS.md had drifted from CLAUDE.md here; this is the sync CLAUDE.md asks for. - `CONTRIBUTING.md` and `e2e/a11y.spec.ts`: the full suite runs 8 workers each with a browser and is memory-hungry. When the machine runs out, the OS kills the static server and Playwright reports whatever each test was doing, which looks like an unrelated regression. Listed the symptoms that identify it — a different failing set each run, `ERR_CONNECTION_REFUSED`, empty received values, `Killed: 9` — and the actionable advice: check free memory before checking your diff. The 200% zoom block is the usual casualty, so its comment names both this and the missing `document.fonts.ready` wait. Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
Two causes, measured rather than inferred: - The new `Adventure Designers` leaderboard section renders a 139px block. That accounts for 14 baselines across 7 pages: home, consent-banner (same page), adventures, challenges, challenges-otel, handbook (+139px each) and about (+102px, where the sticky sidebar is shorter than the main column). - Contributor credit markup accounts for 4: adventure-echoes (+4px, the aside heading changing from "Adventure by" to "Contributors") and level-beginner (no reflow, 721 dark / 810 light differing pixels from the pill label). `brand`, `404` and `solution-beginner` are byte-identical and left untouched. Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- refresh-discussions.mjs could not fail: every per-topic error was a warn-and-continue, so Discourse being down exited 0, the untouched files still passed the workflow's structural check, and the site served stale data indefinitely. - Fail when more than one topic errors, or when every attempted topic errors. One failure is tolerated so a single deleted thread cannot block every other topic's update from being committed, but it warns and names the topic so a failure repeating hourly stays visible. - Errors list each failing URL with its reason, which is what separates one bad discussion URL from Discourse being unreachable. - A run that updates zero files is deliberately not a failure: these threads are static for hours and the job runs hourly, so failing on it would fire most hours and train everyone to ignore the alarm. - refresh-leaderboard.mjs and refresh-community-leaders.mjs exit 1 on a missing DISCOURSE_API_KEY when CI is set. A rotated or removed secret previously produced a green run with data frozen forever. Locally an absent key still skips with exit 0. Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- The workflow gate asserted only Array.isArray on a few top-level fields, which is weaker than the schemas the build runs. A section id Discourse newly started producing passed the gate, was committed to main, and only then failed z.enum in the deploy build. - Validate with parseCommunityLeadersData and the discussion and leaderboard schemas instead, so whatever the build would reject is rejected before anything is committed. - Export discussionSchema and leaderboardSchema. getDiscussion and getLeaderboard cannot serve this: they bind ADVENTURES_DIR at module load, so against any other directory they report "file absent" rather than validating, and a test written on them passes vacuously. That is documented on the exports so the next person does not repeat it. - Retitle the failure issue, which fires on any step now that the fetch scripts can fail, so a Discourse outage is no longer labelled a JSON validation problem. Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- ADVENTURES.md described registering an adventure id in an ADVENTURE_CATEGORIES constant as a required manual step. The constant was deleted; buildAdventureCategories in refresh-leaderboard.mjs derives the registry by reading community_category_id out of every adventure.yaml at runtime. - Setting community_category_id in the YAML is the whole registration step, and it was already documented separately in the PR checklist. - The workflow table also claimed validate-adventures.yml verifies that registration. It does not: it runs astro sync, checks every live level has its posts JSON, and verifies the SKILL.md digest. - Document how the refresh scripts now fail, so the thresholds are discoverable from the adventure docs rather than only the source. - The same stale claim in CLAUDE.md, AGENTS.md and README.md is fixed in the following commit, which rewrites those files anyway. Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- The two files drifted twice while maintained as near-duplicates, and in both directions: AGENTS.md was missing the consent state machine table and the analytics "where it lives" section, while CLAUDE.md was missing the entire Icons section and the no-React rule. Neither was a superset, so no reviewer could tell which was right. - Merge both files' unique content into AGENTS.md, then reduce CLAUDE.md to an @AGENTS.md import plus the Claude-specific slash-command table. Verified empirically, not from docs: with all lookup tools disabled a session could quote content that exists only in AGENTS.md, and could not once the import line was removed. - Repoint every cross-reference that treated CLAUDE.md as canonical, including PERFORMANCE.md's "where this doc and CLAUDE.md disagree, CLAUDE.md wins". - check-docs.sh now accepts AGENTS.md rather than CLAUDE.md. Accepting CLAUDE.md would let a two-line import stub satisfy a documentation gate without documenting anything. - Also fixes the ADVENTURE_CATEGORIES claims in these four files. Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- The pill label used to flip between "Adventure Builder" and "Adventure Designer" depending on whether a guest built a level elsewhere in the adventure, so the same person read two different ways for identical work. It now carries exactly one person, the designer, and the label tracks only their own scope: "Adventure Builder" when they built every challenge, "Adventure Designer" otherwise. - Level pages always read "Challenge Builder". The page is about one challenge, so splitting the label by whether that builder also designed the adventure made one fact read two ways. - Challenge cards carry no credit. The card already holds a difficulty badge, a title, body copy and a link target, and attribution competed with one of them at every size and position tried. - The adventure aside is headed "challenge builder(s)" and lists only people who built a level, each once, with their difficulty badges and bio. A designer who built nothing is credited in the title pill instead of appearing there with a partial set of badges. - The contributors section is "Challenge Contributors" and lists adventures only; roles and per-level detail live on the adventure pages. - CommunityLeaders no longer re-derives builder standing from YAML and discards Discourse's rows. A local threshold could silently move someone between tiers or empty a section. Discourse owns the badges; only adventure-designers is derived, and YAML builders Discourse does not know are added so a builder with no forum account still appears. Handles are mapped back to real names where the YAML knows them. - Delete ContributorBadge, whose only job was deriving labels. Labels now live in adventure-credit.ts where they are unit-tested. Also removes pillCreditOf, levelBuilderCredit, formatRoles, guestBuildersOf and GRAND_BUILDER_THRESHOLD. - Includes the meta_description fallback for adventure card excerpts. Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
The sortDifficulties test used Difficulty as a type annotation but never imported it, causing an astro check type error. Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
Pre-merge audit follow-ups on the credit system, plus the docs and data
hygiene items the audit turned up.
- Assemble Challenge Builders unconditionally in `CommunityLeaders`
rather than appending to whichever section the fetched JSON happens to
carry. `refresh-community-leaders.mjs` drops empty sections from the
file, so with no badged builder upstream there was nothing to append
to and every YAML-credited builder vanished from the card, silently.
- Revert the Discourse handle-to-name rewrite. It only ever covered
contributors, so a section rendered one spaced-out real name among
four camelCase handles. Derived sections still show real names,
because that is what the YAML holds. `displayNameByHandle` and its
unit tests go with it.
- Key the Discourse avatar lookup on the lowercased handle, so YAML
casing need not match the forum's or a real photo silently downgrades
to a letter avatar.
- Drop two `as CreditAdventure` assertions. `astro check` stays at zero
errors without them, so they only suppressed future drift between the
content schema and the credit types.
- Remove the unreachable `glow` prop and `.contributor-pill-glow` CSS:
no call site ever passed it.
- Sort names and titles through `Intl.Collator("en")`. A bare
`localeCompare()` collates in the build machine's locale, and that
order is rendered output feeding the VRT baselines and the e2e order
assertions.
- Stop writing `like_count`. Nothing renders it and
`discussionPostSchema` does not declare it, so Zod discarded it at
build time; the committed JSON never carried it either.
- Soften the accessibility statement to what is actually verified: WCAG
2.2 AA in both modes, body text well above it, some secondary text,
overlines and code comments between 5:1 and 7:1. It claimed AAA 7:1
site-wide, which axe never checked and 158 nodes did not meet. Also
name forced-colors in the axe description, since we do run it.
- Fix three stale references: the consent store pointed at CLAUDE.md for
a table that now lives in AGENTS.md; the AGENTS.md e2e listing named a
`teardown.ts` that does not exist and omitted three files that do; the
comment in `refresh-community-leaders.mjs` described the inverse of
what the component now does.
- Regenerate the 18 desktop VRT baselines. They were generated before
the credit rework landed, so 18 of 24 failed against the build.
Two things left deliberately unfixed:
- The Challenge Builders fix has no test. The component statically
imports the real `community-leaders.json`, so the empty-section case
cannot be varied from a test. Better flagged than faked.
- Correcting an earlier audit claim: the e2e suite IS network-dependent.
GA is intercepted, but Discourse avatars are not, so every page
carrying the Community Leaders card fetches them live from
community.offon.dev. Two VRT flakes traced to this while doing the
work, both passing 3/3 in isolation. It is the likeliest explanation
next time either suite fails for no apparent reason. Routing
`**/user_avatar/**` and `**discourse-cdn.com/**` to a fixture would
remove it.
Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
`Layout.astro` moved focus to `#main-content` on any load with a same-origin `document.referrer`. It was added deliberately when `<ClientRouter />` was removed, to preserve the focus-to-main behaviour the SPA router provided. In a real MPA it is a regression, verified in Chromium against two builds of the site served side by side: - On a fresh load it does nothing. Focus is on `<body>` and the first Tab stop is the skip link, with or without the block. - On an in-site link click it drops the skip link and the entire `<header>` nav out of the forward tab order. First four Tab stops became content links inside `<main>`; the skip link was unreachable in six presses. The nav remained reachable by Shift+Tab, so it was not a trap, but backwards-only access to primary navigation on every page after the first is a real cost. - For a screen reader user it moves the virtual cursor past the banner and nav landmarks and can interrupt the page-title announcement, which the browser already performs correctly on a full load. - It fired arbitrarily. `document.referrer` survives a reload but is empty on back/forward, so a plain refresh skipped the nav only if the user had originally arrived by clicking a link. Tests. `e2e/smoke.spec.ts` asserted the old behaviour; it now asserts the inverse, plus the consequence that assertion exists to protect, plus the reload case. Each guards that `document.referrer` is actually set, so the test cannot decay into a case the block never fired on. The skip-link test in `e2e/a11y.spec.ts` used `page.goto()`, which sends no referrer, so it only ever exercised a cold load. That gap is why the regression went unnoticed: the suite was fully green while the skip link was unreachable by forward Tab on every in-site navigation. Four cases now arrive by real link clicks across each layout type. Non-vacuity checked by restoring the block and rebuilding: all three smoke tests and all four in-site navigation cases failed, while the four direct-load cases still passed, reproducing the original blind spot. Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
`ChallengesFilter.astro` carried 17 non-null assertions and was internally inconsistent about them: the difficulty reads used `?? ""` followed by `isDifficulty()` narrowing, while the tag reads and every `querySelector` result used a bare `!`. Both now use the pattern already present in the file. Tag dataset reads use `?? ""`. Runtime behaviour is unchanged: every one of these already branched on `tag ? ... : ...`, treating the falsy case as the "All Tools" pill, and `undefined` and `""` are both falsy. The `!` was purely a type lie. Element lookups are split by whether the element is actually required: - The five that `apply()` writes on every render share one early-return guard, matching the existing `if (!root) return;`. - The dropdown panel and trigger are resolved once into a record and any wrapper missing either is filtered out, so `panelOf`/`triggerOf` and their four assertions are gone. Every later use is non-null by construction. - The three `closest(...)!` sites use a lookup against that list or a local null check. In the arrow-key handler `preventDefault()` now runs after the panel check, so a missing panel leaves the key to the browser rather than suppressing a default with no replacement. Why this matters beyond types. With the assertions, a missing required element left the widget half-wired: the click and keydown listeners were attached first, then the initial `apply()` threw on a null write. Every later click threw in the same place. Nothing on screen indicated a fault; the filter silently did nothing, permanently. The guards make it degrade to no filtering instead, keeping the server-rendered list intact, which is the progressive-enhancement baseline this component is already built on. `e2e/challenges-filter.spec.ts` and `challenges-filter-deep.spec.ts`: 36 passed. Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
`refresh-discussions.mjs` warned and continued on a failed pagination chunk,
so a topic whose later pages all failed still counted as a success. That was
the one remaining path producing quietly wrong data rather than stale data:
the written file was structurally valid, passed the workflow's own
validation, and was missing replies. Because the stored posts are the last
eight replies and solvers are derived from the same array, a dropped page
removed visible activity and could cost a contributor their solver credit,
with no failure reported anywhere.
A failed chunk now returns `{ ok: false, reason }`, so it lands in the
existing failure accounting and flows through `evaluateRefreshOutcome` like
any other fetch error. Both sub-cases are covered, a non-ok status and an
unparseable body, and the reason names the missing range. The caller never
writes from a non-ok result, so the previous file survives: stale, but
correct.
`fetchTopicPosts` is exported for testing, with a doc comment stating that
callers must not write a file from a non-ok result.
Tests. Six cases stub `globalThis.fetch` to serve a 45-post topic (20 inline,
two chunks). Verified non-vacuous by reverting the chunk logic to
warn-and-continue and re-running: four of the six failed, including the exact
silent-truncation case where the first chunk succeeds and the second does
not. The other two are counterweights that pass either way, guarding against
over-correcting into failing healthy topics.
Note on the test setup: `vi.unstubAllGlobals()` tears down the `localStorage`
stub that `src/test/setup.ts` installs suite-wide via `vi.stubGlobal`,
breaking setup's own `beforeEach` with a confusing error in unrelated tests.
`fetch` is restored by name instead.
Branch verification notes, recorded here because they apply to all three
commits on this branch rather than to any one of them:
- The earlier full-suite run that reported 727 passed was measured against a
working tree that still contained another session's uncommitted work on
this same branch. That result did not transfer, which is why the suite was
re-run on the combined committed state.
- One full e2e run produced a large failure batch across `mobile-menu`,
`starter-nudge`, `hero-cta` and several `smoke` routes that could not be
pinned down. It was not memory exhaustion: free memory was around 2 GB with
zero swap in use. The same 24 tests passed immediately afterwards in
isolation, and a re-run of the full suite was green with `dist/index.html`
checksummed identical before and after, so the build was stable across the
run. It did not reproduce, but someone should know it happened.
Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…r-credit Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
KatharinaSick
previously approved these changes
Sep 7, 2026
…signer - "Adventure Builder" → "Designer & Builder" (designed and built every level) - "Adventure Designer" → "Designer" (at least one guest builder) Drops the word "Adventure" from both labels; the context is clear from the pill placement on adventure cards and the adventure page header. Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
KatharinaSick
previously approved these changes
Sep 7, 2026
…t spec Missed one /Adventure (Builder|Designer)/ pattern when the labels were renamed to "Designer & Builder" / "Designer". Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
src/lib/adventure-credit.tscentralises all contributor credit derivation (adventure page, cards, challenges, leaderboard) from one source of truthContributorBadgereplaced byContributorPill— linked avatar + handle pill, WCAG 2.5.8 complianta11y
focus()call on#main-contentinLayout.astro—<ClientRouter />leftover that dropped the skip link and<header>from the tab order on every page loadCI / data pipeline
refresh-discussions.mjsnow exits non-zero on error instead of swallowing failuresscripts/validate-refreshed-data.mjsvalidates refreshed community JSON against the build's Zod schemas before committingTests
e2e/contributor-credit.spec.ts,src/test/lib/adventure-credit.test.ts,refresh-discussions.test.ts,validate-refreshed-data.test.tsDocs
AGENTS.mdis now the canonical AI guide;CLAUDE.mdimports it with@AGENTS.mdinstead of duplicatingstyleguide.mdupdated withContributorPillprops, classes, and credit rulesType of change
featnew featurefixbug fixrefactorno behavior changedocs/chore/config/perf/style/securityManual checks
npm run build && npm run preview) (UI changes only)discussionUrl(adventure/level changes only)Screenshots: