Skip to content

Kudos and sharing on blog posts and Curiosity Feed entries - #263

Merged
darkmastermindz merged 4 commits into
mainfrom
blog-kudos
Oct 26, 2026
Merged

darkmastermindz merged 4 commits into
mainfrom
blog-kudos

Conversation

@darkmastermindz

@darkmastermindz darkmastermindz commented Oct 25, 2026 •

Copy link
Copy Markdown
Owner

Summary

Blog posts and Curiosity Feed entries now get two buttons:

  • 👏 Kudos: the same live kudos as the About card's XP entries. They use the same /api/kudos function and Netlify Blobs counts, and the same once-a-day limit.
  • ↗ Share: opens the device's share sheet with the post's title and link. Where there's no share sheet, it copies the link instead.

Related Issue

Follows #262 (XP kudos) and #267. Hansel's requests: "Add same to blogs and articles" and "Allow a share function too".

Changes Made

  • netlify/functions/kudos.mjs:
    • Accepts post ids, post-<slug>.
    • The slug must follow the dev.to mirror's own rule (SLUG in scripts/lib/devto.mjs), so there are no dots, slashes or capitals.
    • Posts aren't a fixed list (dev.to posts arrive at build time). So before a post's first kudos, the function checks that the deployed site has it: its prerendered /blog/<slug>/_payload.json must answer 200, with redirects not followed.
    • It checks against this deploy's own address (DEPLOY_URL, else URL). The answer is kept in memory for 10 minutes.
    • A made-up post gets 404, and nothing is stored for it.
    • Stored counts keep only XP ids and post ids with a plain slug.
    • Everything Kudos: tighten counting #267 added still applies to posts: only from the site's own pages, once per connection per day, and daily records cleared.
  • components/KudosButton.vue:
    • One kudos button for every place kudos appear, with the label, the given state and the presses that stop at the button.
    • The About card now uses it, beside its glossary terms; it looks and behaves as before.
  • composables/useKudos.js: one shared set of counts for the whole app (useState). Each page keeps them live while its buttons are on screen.
  • components/ShareButton.vue + utils/share.js: ↗ Share.
    • With a share sheet: navigator.share with the title and the post's full link. Closing the sheet isn't an error.
    • Without one, or if the sheet refuses: the link is copied, and the button shows "✓ Link copied" for a moment.
    • A hidden status line says the same to screen readers ("Link copied", or "Couldn't copy the link").
    • Its label is "Share: <title>".
  • Post page (pages/blog/[slug].vue): "Enjoyed this post? 👏 n ↗ Share" at the end of each post. Share shows even where kudos can't (a local preview).
  • Curiosity Feed (pages/blog/feed/index.vue): 👏 and ↗ Share under each entry. The entry's link is its own /blog/<slug> page.
  • Blog index: the cards there are links, so they get no buttons, since there's no button inside a link.
  • Tests:
    • tests/security/kudos.test.mjs:
      • a real post counts and a made-up one is refused;
      • hostile post ids (KUDOS_POST_IDS in payloads.mjs) are refused before the site is asked about them;
      • the existence check is cached for 10 minutes;
      • stored post keys are cleaned.
        These sit alongside Kudos: tighten counting #267's tests.
    • tests/app/share.test.mjs: share sheet, closed sheet, copy fallback, and failure without throwing.
    • tests/e2e/a11y.spec.mjs:
      • post page and feed kudos run axe before and after a tap, and check that the tap counted for the right id;
      • sharing a post with no share sheet copies the exact link, shows and announces "Link copied", and stays axe-clean.
  • README: the kudos paragraph now covers posts, the feed and sharing.
  • Merged with main (through Gear editor (signed in, collected into a PR) + glossary terms with hover definitions #255 and Kudos: tighten counting #267): the kudos function, its tests and payloads.mjs keep both sides.

How to Test

  1. npm test: 345 pass.
  2. npm run generate.
  3. PLAYWRIGHT_CHROMIUM_PATH=/opt/pw-browsers/chromium npx playwright test tests/e2e/a11y.spec.mjs -g "Kudos|About card|/blog" --project="Pixel 7" --project="Galaxy Tab S9 landscape": 16 pass.
  4. Done locally with the real handler and its real existence check against the built site, with the browser's own headers:
    • post-hello-world returned 200 and post-no-such-post returned 404.
    • Tapping 👏 on /blog/hello-world and on the feed's first entry counted.
    • No script errors.
  5. On the deploy preview:
    • Open a post, tap 👏 at the end, and reload: the count stays.
    • Tap ↗ Share. A phone opens its share sheet; a desktop without one copies the link.
    • Do the same on /blog/feed.

Additional Notes

  • Shared counts: deploy previews share the live counts, as with XP kudos.
  • No new setup.
  • Allowed by the site's headers: the share sheet and clipboard aren't blocked by Permissions-Policy.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Nginuw5wmUjUSkmQB1J3pS

@darkmastermindz darkmastermindz changed the title Kudos on blog posts and Curiosity Feed entries Kudos and sharing on blog posts and Curiosity Feed entries Oct 25, 2026
@github-actions
github-actions Bot force-pushed the blog-kudos branch 2 times, most recently from f96eca6 to 307b721 Compare October 26, 2026 02:19
- /api/kudos takes `post-<slug>` ids: a plain slug (the dev.to mirror's
  rule) for a post the deployed site has, checked by its prerendered
  /blog/<slug>/_payload.json (200, no redirects; kept for 10 minutes)
  before its first kudos
- components/KudosButton.vue: one button for the About card, the end of
  each post and each feed entry; useKudos keeps one shared set of counts
- Security tests for post ids (KUDOS_POST_IDS), the existence check and
  its cache; accessibility tests for a post and the feed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nginuw5wmUjUSkmQB1J3pS
↗ Share next to the kudos: the device's share sheet with the post's
title and link, or the link copied where there's no share sheet, with a
"Link copied" status for screen readers. utils/share.js is unit-tested;
the copy path is checked in the browser with axe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nginuw5wmUjUSkmQB1J3pS
# Conflicts:
#	tests/security/payloads.mjs
Kudos keep both: post ids with the existence check, and the same-origin
check, per-connection counting and daily sweep. The XP line keeps the
glossary terms and the kudos button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nginuw5wmUjUSkmQB1J3pS
@darkmastermindz
darkmastermindz merged commit a1b6066 into main Oct 26, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants