Repository navigation
Gear editor (signed in, collected into a PR) + glossary terms with hover definitions - #255
Merged
Merged
Conversation
github-actions
Bot
force-pushed
the
gear-editor
branch
from
October 25, 2026 20:15
d98f65a to
577d96f
Compare
github-actions
Bot
force-pushed
the
gear-editor
branch
from
October 25, 2026 21:22
577d96f to
eea0c72
Compare
This was referenced Oct 25, 2026
… source Acronyms, lingo and jargon in the site's own text (MLH, SPI, TIAA, CI/CD, SUP, Guide Mode, ...) get a dotted underline and a definition bubble with a link to where the term comes from. - data/glossary.js: each term's short definition, link and other spellings - <Term> and <TermText> mark Vue text (first use per piece of text); glossaryHtml marks the bio's rendered markdown, and blog posts that opt in with `glossary: true`, after sanitizing (parsed, not regex-edited) - <TermTip> in the layout: opens on hover, focus (Enter moves to its link) and tap; Escape, a tap elsewhere or scrolling closes it (WCAG 1.4.13). Screen readers get the definition as the term's description. Taps on a term inside the About card don't switch the card. - The About card's plain-English lines use it - CLAUDE.md: consider this for all new display text; a test fails on an acronym in the display text that has no definition Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nginuw5wmUjUSkmQB1J3pS
… a PR Signed in, each gear page has "Edit list": change an item's name, brand, quantity, section, category, status, tags or notes, remove it, or add one. Changes preview on the page and wait in the browser until sent. - The lists move to data/gear/*.json in one canonical form (same output on the site); utils/gearEdits.mjs checks and applies edits the same way everywhere, so the same changes always give the same file - Send: /api/gear/token signs exactly those changes for a signed-in owner (HMAC with GEAR_EDIT_KEY, 15 minutes), then they go to the "gear-edit" Netlify Form - The Gear updates workflow (weekly, on every merge to main, on demand) reads the submissions, drops any whose signature doesn't check out, applies the rest in order on the gear-updates branch (one commit each), runs the tests, opens or updates the one "Gear updates" pull request and deletes the submissions it handled - Gear tags and notes show glossary terms (SUP, PFD, Guide Mode, ...) - README "Editing gear lists" has the one-time setup; until it's done the workflow logs "not set up" and passes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nginuw5wmUjUSkmQB1J3pS
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nginuw5wmUjUSkmQB1J3pS
The key that signs gear edits now comes from GITHUB_CLIENT_SECRET (HKDF with its own label, like the session key), so GEAR_EDIT_KEY is gone. The Gear updates workflow no longer holds any key: it asks the site to check each submission (POST /api/gear/verify, yes or no only), and a submission the site can't be asked about waits for the next run instead of being dropped. Setup is down to one GitHub secret, NETLIFY_API_TOKEN, to read and delete the form's submissions; the site ID is in the workflow (it isn't secret). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nginuw5wmUjUSkmQB1J3pS
github-actions
Bot
force-pushed
the
gear-editor
branch
from
October 25, 2026 21:52
eea0c72 to
5027a8b
Compare
The XP list line keeps both the glossary terms (TermText) and the new kudos button; payloads keep both GEAR_TEXT and KUDOS_BODIES. The XP text moved to data/xp.mjs, so the glossary test reads it from there too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nginuw5wmUjUSkmQB1J3pS
The Netlify connection only lets Netlify read the repo, so the workflow needed NETLIFY_API_TOKEN to read and delete Netlify Form submissions. Now nothing is stored anywhere: - /api/gear/submit (was /api/gear/token): for a signed-in owner only, checks and signs the changes and keeps them in the "gear-edits" Netlify Blobs store under pending/<time>-<random>. The Netlify Form (public/forms/gear-edit.html) is gone. - /api/gear/pending: GET the pending edits, DELETE the handled ones, only for a GitHub Actions OIDC token signed by GitHub (RS256 against its published keys) for this repository's gear-updates.yml on main (netlify/lib/githubOidc.mjs). - The workflow asks GitHub for that token (id-token: write), runs on push to main instead of pull_request closed (a pull request's run is on its merge ref), and still has each edit verified before applying. - Tests: forged, expired, wrong-audience/repo/workflow/branch tokens, alg none and HS256 confusion, key caching; pending reads and deletes only pending/ keys; submit keeps exactly what it signed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nginuw5wmUjUSkmQB1J3pS
docs/gear-editing.md: a sequence diagram from Edit list to the merged Gear updates pull request (submit, Blobs, the workflow's GitHub OIDC sign-in, pending, verify, apply, delete), and who can call what. Linked from the README. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nginuw5wmUjUSkmQB1J3pS
The workflow's GitHub token must also carry this repository's id and its owner's id, which stay the same through a rename. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nginuw5wmUjUSkmQB1J3pS
# Conflicts: # tests/security/payloads.mjs
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
Two features.
1. Edit gear lists when signed in. Each gear page gets ✏️ Edit list: change an item's name, brand, quantity, section, category, status, tags or notes, remove it, or add one. Changes show on the page as they'll look. Send for review keeps them on Netlify. The Gear updates GitHub Action then folds every sent edit into one rolling "Gear updates" pull request. It runs weekly, on every push to main and on demand. The same changes on the same list always give the same file, byte for byte.
NETLIFY_API_TOKEN, and no Netlify Form.2. Glossary terms. Acronyms, lingo and jargon in the site's own text get a dotted underline. Hover, focus or tap one for a short definition and a link to its source. Examples: MLH, SPI, AMGA, TIAA, CCDC, CI/CD, RAG, OpenShift, SUP, PFD, Guide Mode.
Related Issue
Hansel's requests:
Changes Made
Gear editor
data/inventory.mjstodata/gear/outdoor.jsonandcamera.jsonin one canonical form, and each item gets a stableid.data/inventory.mjsreads them; its API and page output are unchanged (checked against a snapshot).utils/gearEdits.mjs: edits are a list ofadd/update/removeoperations./api/gear/submit(netlify/functions/gear-submit.mjs), for a signed-in owner only, same-origin, POST:netlify/lib/gearToken.mjs), keyed fromGITHUB_CLIENT_SECRETwith HKDF and its own label;gear-editsunderpending/<time>-<random>./api/gear/pending(netlify/functions/gear-pending.mjs):GETlists the pending edits, oldest first, andDELETE { keys }removes the handled ones (onlypending/…keys).netlify/lib/githubOidc.mjs:token.actions.githubusercontent.com;hanselwei.dev/gear-updates;darkmastermindz/hanselwei.dev, matched by name and by id, along with its owner's id;gear-updates.ymlonrefs/heads/main;/api/gear/verify(netlify/functions/gear-verify.mjs) answers only yes or no..github/workflows/gear-updates.yml+scripts/gear-updates.mjs):id-token: writeand asks GitHub for its sign-in token.gear-updates, one commit per edit with a plain-text change list (@mentions and backticks defused).npm test, opens or updates the PR, then deletes the handled edits.pull_request: closed, because a pull request's run is on its merge ref, which the site rightly refuses. Outside Actions it says so and stops.composables/useGearEditor.js,components/GearItemForm.vueandpages/gear/[list].vue./api/gear/submit.Glossary
data/glossary.js: 34 terms, each with a short definition, a link (official site or Wikipedia) and aliases.utils/glossary.jsfindTerms: whole-word, case-exact, longest match first, and only the first use per piece of text.<Term>,<TermText>, and one<TermTip>in the default layout.aria-describedby.server/utils/glossaryHtml.js: marks the bio's markdown after sanitizing.<abbr>+ hidden<span>.glossary: true.CLAUDE.md+tests/app/glossary.test.mjs: new display text considers glossary terms, and the test fails on any undefined acronym in the display text. It reads the XP lines fromdata/xp.mjs.Docs:
docs/gear-editing.md: a Mermaid sequence diagram from Edit list to the merged pull request, and a who-can-call-what diagram.data/gear/.Merged with main (now through #266):
<TermText>) and the 👏 kudos button.payloads.mjskeepsGEAR_TEXT,KUDOS_BODIES,BAD_BEARER_TOKENSandBAD_COMMITS.How to Test
npm test: 333 pass.tests/security/github-oidc.test.mjs: a genuine token for this workflow on main is accepted. Each of these is refused:alg: none), or HS256 "signed" with GitHub's public key;BAD_BEARER_TOKENSinpayloads.mjs).It also covers GitHub's keys being cached and refetched at most once a minute, and that
/api/gear/pendingonly reads and deletespending/keys, for that token only.tests/security/gear-edits.test.mjs:/api/gear/verifyagrees;tests/app/gear-edits.test.mjs: determinism, ordering, and submissions through the real verify handler.npm run generatebuilds. The About card, kudos and gear accessibility tests pass on a phone and a tablet (10 passed)./bio, hover "UNC Charlotte" or "ARC".scripts/gear-updates.mjsran against stand-in GitHub (OIDC token and API) and site servers using the real submit, pending and verify handlers.POST /api/gear/submitwith the changes, shows "Sent", and passes axe.Additional Notes
One-time setup after merging: GitHub → Settings → Actions → General → "Allow GitHub Actions to create and approve pull requests". There are no secrets to add and nothing to turn on at Netlify.
Limits:
GITHUB_CLIENT_SECRETsigns everyone out, and voids edits sent but not yet applied (send them again). It also restarts the daily kudos limit.gear-editsstore with the live site (it's site-wide). Only owners can add to it.Other: the glossary modules are
.jsbecause Nitro left.mjsimports fromserver/utilsunbundled with a path that broke the prerender.🤖 Generated with Claude Code
https://claude.ai/code/session_01Nginuw5wmUjUSkmQB1J3pS