From d2afda5e70233ce037494c9edfd10caf5da71d03 Mon Sep 17 00:00:00 2001 From: sajeetharan Date: Thu, 20 Aug 2026 23:19:32 +0530 Subject: [PATCH 1/4] feat: improve profile sharing activity --- app/api/activities/platform/route.js | 3 +- app/page.jsx | 18 +++++---- app/share/[login]/page.jsx | 8 +--- components/DetailPanel.jsx | 24 ++++++----- lib/platform-activity.js | 16 +++++--- lib/site.js | 2 +- next.config.js | 6 +++ styles/main.css | 60 ++++++++++++++++++++++------ tests/activity.test.js | 14 +++++++ 9 files changed, 107 insertions(+), 44 deletions(-) diff --git a/app/api/activities/platform/route.js b/app/api/activities/platform/route.js index f18db78..41014c2 100644 --- a/app/api/activities/platform/route.js +++ b/app/api/activities/platform/route.js @@ -4,11 +4,12 @@ import { saveActivities } from '../../../../lib/activity-store.js'; import { createPlatformActivity } from '../../../../lib/platform-activity.js'; const LOGIN_PATTERN = /^[a-z\d](?:[a-z\d-]{0,37}[a-z\d])?$/i; +const ACTIVITY_TYPES = new Set(['generated_card', 'generated_readme']); export async function POST(request) { try { const body = await request.json(); - if (body.type !== 'generated_card' || !LOGIN_PATTERN.test(body.targetLogin || '')) { + if (!ACTIVITY_TYPES.has(body.type) || !LOGIN_PATTERN.test(body.targetLogin || '')) { return NextResponse.json({ error: 'Invalid platform activity' }, { status: 400 }); } diff --git a/app/page.jsx b/app/page.jsx index 9a32001..35a83ce 100644 --- a/app/page.jsx +++ b/app/page.jsx @@ -320,17 +320,17 @@ export default function Home() { setFiltered(rankedResults); }, [developers]); - const recordCardActivity = useCallback((targetLogin) => { + const recordPlatformActivity = useCallback((type, targetLogin) => { fetch('/api/activities/platform', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ type: 'generated_card', targetLogin }), + body: JSON.stringify({ type, targetLogin }), }).catch(() => {}); }, []); const handleGenerateCard = useCallback((developer) => { const rankedDeveloper = developers.find(item => item.login === developer.login) || developer; - recordCardActivity(rankedDeveloper.login); + recordPlatformActivity('generated_card', rankedDeveloper.login); setSelectedDev(rankedDeveloper); setCardContext('generate'); setCardRequest(request => request + 1); @@ -338,7 +338,7 @@ export default function Home() { if (rankedDeveloper.lat != null && rankedDeveloper.lng != null) { setFlyTarget({ lat: rankedDeveloper.lat, lng: rankedDeveloper.lng }); } - }, [developers, recordCardActivity]); + }, [developers, recordPlatformActivity]); const handleOpenCardFeature = useCallback(() => { const developer = resolveIdentityCardDeveloper(selectedDev, user, developers); @@ -350,7 +350,7 @@ export default function Home() { requestAnimationFrame(() => document.querySelector('#search-bar input')?.focus()); }, [developers, handleGenerateCard, selectedDev, user]); - const handleOpenReadmeFeature = useCallback((requestedDeveloper) => { + const handleOpenReadmeFeature = useCallback((requestedDeveloper, { recordActivity = true } = {}) => { const developer = requestedDeveloper || selectedDev || resolveIdentityCardDeveloper(null, user, developers); if (!developer) { setTourStep('search'); @@ -358,12 +358,13 @@ export default function Home() { return; } track('profile_readme_opened', { login: developer.login, source: 'home' }); + if (recordActivity) recordPlatformActivity('generated_readme', developer.login); setCardRequest(0); setCardContext(null); setSelectedDev(developer); setSidebarOpen(false); setReadmeRequest(request => request + 1); - }, [developers, selectedDev, user]); + }, [developers, recordPlatformActivity, selectedDev, user]); // Resume a "Generate README" request started from the home menu before sign-in. useEffect(() => { @@ -373,7 +374,7 @@ export default function Home() { if (!pendingLogin) return; try { localStorage.removeItem(PENDING_HOME_README_KEY); } catch { /* best-effort cleanup */ } const developer = developers.find(candidate => candidate.login.toLowerCase() === pendingLogin.toLowerCase()); - if (developer) handleOpenReadmeFeature(developer); + if (developer) handleOpenReadmeFeature(developer, { recordActivity: false }); }, [user, developers, handleOpenReadmeFeature]); const handleOpenCompareFeature = useCallback(() => { @@ -617,7 +618,8 @@ export default function Home() { key={`${selectedDev.login}-${cardRequest}`} dev={selectedDev} onClose={handleCloseDetail} - onCardGenerated={recordCardActivity} + onCardGenerated={targetLogin => recordPlatformActivity('generated_card', targetLogin)} + onReadmeGenerated={targetLogin => recordPlatformActivity('generated_readme', targetLogin)} claimedLogins={claimedLogins} user={user} onClaim={handleClaim} diff --git a/app/share/[login]/page.jsx b/app/share/[login]/page.jsx index 1f7bc85..b7309f2 100644 --- a/app/share/[login]/page.jsx +++ b/app/share/[login]/page.jsx @@ -1,7 +1,8 @@ import Link from 'next/link'; -import { headers } from 'next/headers'; import { getSiteUrl, SOCIAL_PREVIEW_VERSION } from '../../../lib/site.js'; +export const revalidate = 86400; + export async function generateMetadata({ params }) { const { login } = await params; const siteUrl = getSiteUrl(); @@ -34,11 +35,6 @@ export async function generateMetadata({ params }) { export default async function DeveloperSharePage({ params }) { const { login } = await params; - const requestHeaders = await headers(); - console.info('social-preview-page', { - login, - userAgent: requestHeaders.get('user-agent') || 'unknown', - }); const encodedLogin = encodeURIComponent(login); const siteUrl = getSiteUrl(); const pageUrl = `${siteUrl}/share/${encodedLogin}`; diff --git a/components/DetailPanel.jsx b/components/DetailPanel.jsx index da7de75..6a6fb48 100644 --- a/components/DetailPanel.jsx +++ b/components/DetailPanel.jsx @@ -6,7 +6,7 @@ import { track } from '../lib/analytics.js'; import * as d3 from 'd3'; import { formatNum, formatRelativeTime, formatUsd, isStaleData } from '../lib/format.js'; import { DIMENSIONS, SCORE_METHODOLOGY } from '../lib/scoring.js'; -import { IDENTITY_CARD_VERSION, SOCIAL_PREVIEW_VERSION } from '../lib/site.js'; +import { getSiteUrl, IDENTITY_CARD_VERSION, SOCIAL_PREVIEW_VERSION } from '../lib/site.js'; import { classifyAgent } from '../lib/agent-class.js'; import { AI_TOOLS } from '../lib/ai-profile.js'; import { publicApiUrl } from '../lib/public-api.js'; @@ -14,7 +14,7 @@ import { resolveReadmeAccess } from '../lib/profile-readme.js'; import SpecialTags from './SpecialTags.jsx'; import ReadmeGeneratorModal from './ReadmeGeneratorModal.jsx'; -export default function DetailPanel({ dev, onClose, onCardGenerated, claimedLogins, user, onClaim, readmeRequest = 0, openCardOnMount = false, claimSuccess = false }) { +export default function DetailPanel({ dev, onClose, onCardGenerated, onReadmeGenerated, claimedLogins, user, onClaim, readmeRequest = 0, openCardOnMount = false, claimSuccess = false }) { const [fullData, setFullData] = useState(null); const [showCard, setShowCard] = useState(false); const [showReadmeGenerator, setShowReadmeGenerator] = useState(false); @@ -108,6 +108,7 @@ export default function DetailPanel({ dev, onClose, onCardGenerated, claimedLogi const handleReadmeAccess = () => { setShowReadmeGenerator(true); track('profile_readme_opened', { login: dev.login, access: readmeAccess }); + onReadmeGenerated?.(dev.login); }; // Radar chart @@ -732,7 +733,7 @@ function CardModal({ dev, claimSuccess, onClose }) { const name = dev.name || login; const cardUrl = `/api/card?login=${encodeURIComponent(login)}&v=${IDENTITY_CARD_VERSION}`; const sharePath = `/share/${encodeURIComponent(login)}?v=${SOCIAL_PREVIEW_VERSION}`; - const shareUrl = typeof window !== 'undefined' ? `${window.location.origin}${sharePath}` : sharePath; + const shareUrl = `${getSiteUrl()}${sharePath}`; const rankText = dev.globalRank ? `Global #${dev.globalRank} of ${dev.globalTotal}` : 'Ranked on DevGlobe'; const agent = classifyAgent(dev); @@ -849,7 +850,17 @@ function CardModal({ dev, claimSuccess, onClose }) {
- Share on: +
+ Share your developer card + Show your open-source identity to your network +
+ + More handleSocialShare('twitter')}> @@ -860,11 +871,6 @@ function CardModal({ dev, claimSuccess, onClose }) { - handleSocialShare('reddit')}> diff --git a/lib/platform-activity.js b/lib/platform-activity.js index 85c91e5..f92d577 100644 --- a/lib/platform-activity.js +++ b/lib/platform-activity.js @@ -3,24 +3,28 @@ import { randomUUID } from 'crypto'; const FALLBACK_ACTIVITIES = [ { login: 'codeatlas', type: 'generated_card', description: 'had their developer card generated' }, { login: 'pixelbranch', type: 'logged_in', description: 'signed in to explore the developer globe' }, + { login: 'readmeforge', type: 'generated_readme', description: 'generated their GitHub profile README' }, { login: 'cloudsyntax', type: 'generated_card', description: 'had their developer card generated' }, { login: 'commitcraft', type: 'logged_in', description: 'signed in to explore the developer globe' }, ]; export function createPlatformActivity({ type, login, avatarUrl, targetLogin, now = new Date() }) { const isCard = type === 'generated_card'; + const isReadme = type === 'generated_readme'; + const target = targetLogin || login; + const description = isCard + ? target !== login ? `generated @${target}'s developer card` : 'had their developer card generated' + : isReadme + ? target !== login ? `generated @${target}'s GitHub profile README` : 'generated their GitHub profile README' + : 'signed in to DevGlobe'; return { id: `platform:${randomUUID()}`, login, avatarUrl: avatarUrl || null, type, - description: isCard && targetLogin && targetLogin !== login - ? `generated @${targetLogin}'s developer card` - : isCard - ? 'had their developer card generated' - : 'signed in to DevGlobe', + description, repo: null, - url: isCard ? `/developer/${encodeURIComponent(targetLogin || login)}` : `/developer/${encodeURIComponent(login)}`, + url: isCard || isReadme ? `/developer/${encodeURIComponent(target)}` : `/developer/${encodeURIComponent(login)}`, createdAt: now.toISOString(), documentType: 'platform-activity', }; diff --git a/lib/site.js b/lib/site.js index bb80983..036cdff 100644 --- a/lib/site.js +++ b/lib/site.js @@ -1,5 +1,5 @@ const DEFAULT_SITE_URL = 'https://www.devglobe.dev'; -export const SOCIAL_PREVIEW_VERSION = '11'; +export const SOCIAL_PREVIEW_VERSION = '12'; export const IDENTITY_CARD_VERSION = '5'; export function getSiteUrl() { diff --git a/next.config.js b/next.config.js index 097c91b..4f44ad3 100644 --- a/next.config.js +++ b/next.config.js @@ -17,6 +17,12 @@ const nextConfig = { { key: 'Vary', value: 'Accept' }, ], }, + { + source: '/share/:login', + headers: [ + { key: 'Cache-Control', value: 'public, s-maxage=86400, stale-while-revalidate=604800' }, + ], + }, ]; }, async rewrites() { diff --git a/styles/main.css b/styles/main.css index 46e1f3c..0002a53 100644 --- a/styles/main.css +++ b/styles/main.css @@ -5027,15 +5027,57 @@ body { .card-modal__share { display: flex; align-items: center; - gap: 12px; - padding-top: 16px; - border-top: 1px solid var(--border); + flex-wrap: wrap; + gap: 10px; + padding: 16px; + border: 1px solid rgba(10, 102, 194, 0.35); + border-radius: 8px; + background: rgba(10, 102, 194, 0.07); } -.card-modal__share-label { +.card-modal__share-heading { + display: grid; + flex: 1 0 100%; + gap: 3px; +} + +.card-modal__share-heading strong { + color: var(--text-primary); font-size: 13px; +} + +.card-modal__share-heading span { color: var(--text-secondary); - font-weight: 500; + font-size: 11px; +} + +.card-modal__share-label { + margin-left: 4px; + font-size: 11px; + color: var(--text-secondary); + font-weight: 600; + text-transform: uppercase; +} + +.card-modal__linkedin { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 7px; + min-height: 38px; + padding: 8px 13px; + border: 1px solid #0a66c2; + border-radius: 6px; + background: #0a66c2; + color: #fff; + font: inherit; + font-size: 12px; + font-weight: 650; + cursor: pointer; +} + +.card-modal__linkedin:hover { + background: #084f96; } .card-modal__social { @@ -5071,14 +5113,6 @@ body { border-color: rgba(24, 119, 242, 0.4); } -.card-modal__social--linkedin { - color: #0a66c2; -} -.card-modal__social--linkedin:hover { - background: rgba(10, 102, 194, 0.12); - border-color: rgba(10, 102, 194, 0.4); -} - .card-modal__social--reddit { color: #ff4500; } diff --git a/tests/activity.test.js b/tests/activity.test.js index eb02d28..6785a81 100644 --- a/tests/activity.test.js +++ b/tests/activity.test.js @@ -49,6 +49,20 @@ test('creates platform card activity for the actor and target', () => { assert.equal(activity.documentType, 'platform-activity'); }); +test('creates platform README activity for the actor and target', () => { + const activity = createPlatformActivity({ + type: 'generated_readme', + login: 'octocat', + targetLogin: 'hubot', + now: new Date('2026-08-13T12:00:00Z'), + }); + + assert.equal(activity.login, 'octocat'); + assert.equal(activity.description, "generated @hubot's GitHub profile README"); + assert.equal(activity.url, '/developer/hubot'); + assert.equal(activity.documentType, 'platform-activity'); +}); + test('creates stable fallback activities within an hourly window', () => { const first = createFallbackActivities(Date.parse('2026-08-13T12:10:00Z')); const second = createFallbackActivities(Date.parse('2026-08-13T12:50:00Z')); From ca02309098d5530240cbef34515c6f8440b09aff Mon Sep 17 00:00:00 2001 From: sajeetharan Date: Thu, 20 Aug 2026 23:41:56 +0530 Subject: [PATCH 2/4] feat: tailor identity card social captions --- components/DetailPanel.jsx | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/components/DetailPanel.jsx b/components/DetailPanel.jsx index 6a6fb48..ef76d99 100644 --- a/components/DetailPanel.jsx +++ b/components/DetailPanel.jsx @@ -728,7 +728,7 @@ function renderLanguages(container, languages) { function CardModal({ dev, claimSuccess, onClose }) { const [loading, setLoading] = useState(true); const [error, setError] = useState(false); - const [linkedinCopied, setLinkedinCopied] = useState(false); + const [captionCopiedFor, setCaptionCopiedFor] = useState(''); const { login } = dev; const name = dev.name || login; const cardUrl = `/api/card?login=${encodeURIComponent(login)}&v=${IDENTITY_CARD_VERSION}`; @@ -737,16 +737,17 @@ function CardModal({ dev, claimSuccess, onClose }) { const rankText = dev.globalRank ? `Global #${dev.globalRank} of ${dev.globalTotal}` : 'Ranked on DevGlobe'; const agent = classifyAgent(dev); - const shareHashtags = ['buildinpublic', 'DevGlobe', 'OpenSource', 'DeveloperCommunity', 'GitHub']; + const shareHashtags = ['DevGlobe', 'OpenSource', 'DeveloperDiscovery']; const hashtagText = shareHashtags.map(hashtag => `#${hashtag}`).join(' '); - const shareText = `I mapped my open-source identity on DevGlobe: ${rankText}. Generate yours and see where you rank.\n\n${hashtagText}`; - const linkedinCaption = `I mapped my open-source contributions on DevGlobe and discovered my developer identity: ${agent.name}. ${rankText}. Build your card and see where your work places you in the global developer community.\n\n${hashtagText}`; + const twitterCaption = `Open-source work should make developers discoverable.\n\nMy DevGlobe identity uses contribution signals to help people and AI agents find my work.\n\n${agent.name} · ${rankText}\n\n${hashtagText}`; + const linkedinCaption = `Open-source work should be discoverable by what you build.\n\nDevGlobe mapped my public contribution signals into an open-source developer identity designed to help engineering teams, communities, and AI agents find relevant expertise.\n\n${agent.name} · ${rankText}\n\nExplore my profile and create yours.\n\n${hashtagText}`; + const facebookCaption = `Open-source work tells a richer story than a job title.\n\nDevGlobe mapped my public contributions into a developer identity that helps teams, communities, and AI agents discover what I build and where I can contribute.\n\n${agent.name} · ${rankText}\n\nExplore my profile and create yours.\n\n${hashtagText}`; const shareLinks = { - twitter: `https://twitter.com/intent/tweet?text=${encodeURIComponent(shareText)}&url=${encodeURIComponent(shareUrl)}`, + twitter: `https://twitter.com/intent/tweet?text=${encodeURIComponent(twitterCaption)}&url=${encodeURIComponent(shareUrl)}`, facebook: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`, linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(shareUrl)}`, - reddit: `https://reddit.com/submit?url=${encodeURIComponent(shareUrl)}&title=${encodeURIComponent(`My DevGlobe Developer Card - ${name} ${hashtagText}`)}`, + reddit: `https://reddit.com/submit?url=${encodeURIComponent(shareUrl)}&title=${encodeURIComponent(`${name}'s open-source developer identity on DevGlobe - ${agent.name}, ${rankText}`)}`, }; const handleDownload = async () => { @@ -776,12 +777,15 @@ function CardModal({ dev, claimSuccess, onClose }) { }; const handleLinkedInShare = () => { - navigator.clipboard?.writeText(linkedinCaption).then(() => setLinkedinCopied(true)).catch(() => {}); + navigator.clipboard?.writeText(linkedinCaption).then(() => setCaptionCopiedFor('LinkedIn')).catch(() => {}); track('identity_card_shared', { login, channel: 'linkedin' }); window.open(shareLinks.linkedin, '_blank', 'noopener,noreferrer'); }; const handleSocialShare = channel => { + if (channel === 'facebook') { + navigator.clipboard?.writeText(facebookCaption).then(() => setCaptionCopiedFor('Facebook')).catch(() => {}); + } track('identity_card_shared', { login, channel }); }; @@ -866,7 +870,7 @@ function CardModal({ dev, claimSuccess, onClose }) { - handleSocialShare('facebook')}> + handleSocialShare('facebook')}> @@ -876,7 +880,7 @@ function CardModal({ dev, claimSuccess, onClose }) { - {linkedinCopied && Caption and tags copied. Paste them into your LinkedIn post.} + {captionCopiedFor && Caption and tags copied. Paste them into your {captionCopiedFor} post.}
From 6047fde8d65227297b253dd1db83481804fc3a80 Mon Sep 17 00:00:00 2001 From: sajeetharan Date: Thu, 20 Aug 2026 23:52:44 +0530 Subject: [PATCH 3/4] fix: align share icons and simplify header --- components/DetailPanel.jsx | 4 +-- components/Header.jsx | 1 - styles/main.css | 66 ++++++++------------------------------ 3 files changed, 14 insertions(+), 57 deletions(-) diff --git a/components/DetailPanel.jsx b/components/DetailPanel.jsx index ef76d99..4a56fec 100644 --- a/components/DetailPanel.jsx +++ b/components/DetailPanel.jsx @@ -858,13 +858,11 @@ function CardModal({ dev, claimSuccess, onClose }) { Share your developer card Show your open-source identity to your network - - More handleSocialShare('twitter')}> diff --git a/components/Header.jsx b/components/Header.jsx index 0c9b70c..8e0fb9c 100644 --- a/components/Header.jsx +++ b/components/Header.jsx @@ -12,7 +12,6 @@ export default function Header({ onHome, theme, onToggleTheme, user, onLogout, o
DevGlobe

DevGlobe

- The discovery layer for the agentic web: connecting developers and AI agents to build together.