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
1 change: 1 addition & 0 deletions apps/web/next.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
allowedDevOrigins: ['127.0.0.1'],
typedRoutes: true,
serverExternalPackages: ['pg'],
}
Expand Down
70 changes: 36 additions & 34 deletions apps/web/src/app/leaderboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,41 +30,43 @@ export default async function LeaderboardPage() {
</CardHeader>
<CardContent>
{group.rows.length > 0 ? (
<Table>
<thead>
<tr>
<Th>#</Th>
<Th>Handle</Th>
<Th>Host</Th>
<Th className="text-right">Rage/1k</Th>
<Th className="text-right">Swears</Th>
<Th className="text-right">Words</Th>
<Th>Top</Th>
<Th className="text-right">Share</Th>
</tr>
</thead>
<tbody>
{group.rows.map((row) => (
<tr key={row.id}>
<Td>{row.rank}</Td>
<Td className="font-medium">{row.handle}</Td>
<Td>{row.hostApp}</Td>
<Td className="text-right">{row.ratePerThousandWords}</Td>
<Td className="text-right">{row.scoredProfanityCount}</Td>
<Td className="text-right">{row.userWordCount}</Td>
<Td>{row.topIntensity ?? '-'}</Td>
<Td className="text-right">
<Link
className="text-zinc-600 underline"
href={`/share/${row.id}` as Route}
>
Open
</Link>
</Td>
<div className="overflow-x-auto">
<Table className="min-w-[680px]">
<thead>
<tr>
<Th>#</Th>
<Th>Handle</Th>
<Th>Host</Th>
<Th className="text-right">Rage/1k</Th>
<Th className="text-right">Swears</Th>
<Th className="text-right">Words</Th>
<Th>Top</Th>
<Th className="text-right">Share</Th>
</tr>
))}
</tbody>
</Table>
</thead>
<tbody>
{group.rows.map((row) => (
<tr key={row.id}>
<Td>{row.rank}</Td>
<Td className="font-medium">{row.handle}</Td>
<Td>{row.hostApp}</Td>
<Td className="text-right">{row.ratePerThousandWords}</Td>
<Td className="text-right">{row.scoredProfanityCount}</Td>
<Td className="text-right">{row.userWordCount}</Td>
<Td>{row.topIntensity ?? '-'}</Td>
<Td className="text-right">
<Link
className="text-zinc-600 underline"
href={`/share/${row.id}` as Route}
>
Open
</Link>
</Td>
</tr>
))}
</tbody>
</Table>
</div>
) : (
<p className="text-sm text-zinc-500">No rankable rows yet.</p>
)}
Expand Down
68 changes: 37 additions & 31 deletions apps/web/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import { getLeaderboardRows } from '@/lib/leaderboard'

export const dynamic = 'force-dynamic'

function formatHostApp(hostApp: string): string {
return hostApp === 'claude' ? 'Claude' : 'Codex'
}

export default async function HomePage() {
const rows = await getLeaderboardRows('daily').catch(() => [])
const topHost = rows[0]?.hostApp
Expand Down Expand Up @@ -41,37 +45,39 @@ export default async function HomePage() {
</CardHeader>
<CardContent>
{rows.length > 0 ? (
<Table>
<thead>
<tr>
<Th>#</Th>
<Th>Handle</Th>
<Th>Host</Th>
<Th className="text-right">Rage/1k</Th>
<Th className="text-right">Words</Th>
<Th className="text-right">Share</Th>
</tr>
</thead>
<tbody>
{rows.map((row) => (
<tr key={row.id}>
<Td>{row.rank}</Td>
<Td className="font-medium">{row.handle}</Td>
<Td>{row.hostApp}</Td>
<Td className="text-right">{row.ratePerThousandWords}</Td>
<Td className="text-right">{row.userWordCount}</Td>
<Td className="text-right">
<Link
className="text-zinc-600 underline"
href={`/share/${row.id}` as Route}
>
Open
</Link>
</Td>
<div className="overflow-x-auto">
<Table className="min-w-[520px]">
<thead>
<tr>
<Th>#</Th>
<Th>Handle</Th>
<Th>Host</Th>
<Th className="text-right">Rage/1k</Th>
<Th className="text-right">Words</Th>
<Th className="text-right">Share</Th>
</tr>
))}
</tbody>
</Table>
</thead>
<tbody>
{rows.map((row) => (
<tr key={row.id}>
<Td>{row.rank}</Td>
<Td className="font-medium">{row.handle}</Td>
<Td>{row.hostApp}</Td>
<Td className="text-right">{row.ratePerThousandWords}</Td>
<Td className="text-right">{row.userWordCount}</Td>
<Td className="text-right">
<Link
className="text-zinc-600 underline"
href={`/share/${row.id}` as Route}
>
Open
</Link>
</Td>
</tr>
))}
</tbody>
</Table>
</div>
) : (
<div className="rounded-md border border-dashed p-8 text-center text-zinc-500">
No published rage yet. Suspiciously calm.
Expand All @@ -88,7 +94,7 @@ export default async function HomePage() {
<CardContent className="grid gap-2 text-sm text-zinc-600">
<p>
{topHost
? `${topHost} is testing everyone&apos;s patience today.`
? `${formatHostApp(topHost)} is testing everyone's patience today.`
: 'The tools are behaving, or everyone is bottling it up.'}
</p>
<p>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/share/[rowId]/opengraph-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export default async function Image({ params }: ShareImageProps) {
Rage AI
</div>
<div style={{ color: '#52525b', fontSize: 28 }}>
{formatWindow(data.window)} aggregate share
{`${formatWindow(data.window)} aggregate share`}
</div>
</div>

Expand Down
7 changes: 6 additions & 1 deletion apps/web/src/components/ui/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import type * as React from 'react'
import { cn } from '@/lib/utils'

export function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return <div className={cn('rounded-lg border border-zinc-200 bg-white', className)} {...props} />
return (
<div
className={cn('min-w-0 rounded-lg border border-zinc-200 bg-white', className)}
{...props}
/>
)
}

export function CardHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
Expand Down
14 changes: 14 additions & 0 deletions packages/rage-core/src/__tests__/share.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,20 @@ describe('share drafts', () => {
expect(draft.text).toContain('aggregate-only')
})

it('does not duplicate aggregate-only wording for non-rankable scores', () => {
const draft = createShareDraft({
handle: 'wise',
platform: 'linkedin',
tone: 'professional',
window: 'daily',
url: shareUrl,
scores: [{ ...(scores[0] as ShareScore), rankEligible: false }],
})

expect(draft.text).toContain('This is an aggregate-only share.')
expect(draft.text).not.toContain('aggregate-only, aggregate-only')
})

it('does not include private or sensitive field names', () => {
const draft = createShareDraft({
handle: 'wise',
Expand Down
24 changes: 19 additions & 5 deletions packages/rage-core/src/share.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,20 @@ function formatScore(score: ShareScore): string {
return `${formatHostApp(score.hostApp)} ${formatRate(score.ratePerThousandWords)}/1k`
}

function scoreBoundary(score: ShareScore): string {
return score.rankEligible ? 'rankable, aggregate-only' : 'aggregate-only'
}

function scoreBoundaryStart(score: ShareScore): string {
return score.rankEligible ? 'Rankable, aggregate-only' : 'Aggregate-only'
}

function shareBoundarySentence(score: ShareScore): string {
return score.rankEligible
? 'This is a rankable, aggregate-only share.'
: 'This is an aggregate-only share.'
}

function comparisonLine(scores: ShareScore[]): string {
return scores.map(formatScore).join(' vs ')
}
Expand Down Expand Up @@ -106,20 +120,20 @@ function createSingleHostDraft(input: ShareDraftInput, score: ShareScore): strin
const window = formatWindow(input.window)
const host = formatHostApp(score.hostApp)
const rate = formatRate(score.ratePerThousandWords)
const rankable = score.rankEligible ? 'rankable' : 'aggregate-only'
const boundary = scoreBoundary(score)

if (input.platform === 'x') {
if (input.tone === 'snark') {
return `${host} extracted ${rate} rage hits/1k words from me ${window}. ${rankable} Rage AI score, transcripts stay local. ${input.url}`
return `${host} extracted ${rate} rage hits/1k words from my ${window} window. ${scoreBoundaryStart(score)} Rage AI score, transcripts stay local. ${input.url}`
}
return `${window} Rage AI score for ${host}: ${rate} rage hits/1k words. ${rankable}, aggregate-only, transcripts stay local. ${input.url}`
return `${window} Rage AI score for ${host}: ${rate} rage hits/1k words. ${boundary}, transcripts stay local. ${input.url}`
}

if (input.tone === 'snark') {
return `${host} delivered my ${window} Rage AI score: ${rate} rage hits per 1,000 words.\n\nThe share is ${rankable} and aggregate-only. No raw transcripts, no matched words, no model names.\n\n${input.url}`
return `${host} delivered my ${window} Rage AI score: ${rate} rage hits per 1,000 words.\n\nThe share is ${boundary}. No raw transcripts, no matched words, no model names.\n\n${input.url}`
}

return `My ${window} Rage AI score for ${host} is ${rate} rage hits per 1,000 words.\n\nThis is a ${rankable}, aggregate-only share. No raw transcripts, no matched words, no model names.\n\n${input.url}`
return `My ${window} Rage AI score for ${host} is ${rate} rage hits per 1,000 words.\n\n${shareBoundarySentence(score)} No raw transcripts, no matched words, no model names.\n\n${input.url}`
}

function fitXDraft(text: string, input: ShareDraftInput, scores: ShareScore[]): string {
Expand Down
19 changes: 14 additions & 5 deletions plugins/rage-claude/bin/rage.js
Original file line number Diff line number Diff line change
Expand Up @@ -14829,6 +14829,15 @@ function bestScore(scores) {
function formatScore(score) {
return `${formatHostApp(score.hostApp)} ${formatRate(score.ratePerThousandWords)}/1k`;
}
function scoreBoundary(score) {
return score.rankEligible ? "rankable, aggregate-only" : "aggregate-only";
}
function scoreBoundaryStart(score) {
return score.rankEligible ? "Rankable, aggregate-only" : "Aggregate-only";
}
function shareBoundarySentence(score) {
return score.rankEligible ? "This is a rankable, aggregate-only share." : "This is an aggregate-only share.";
}
function comparisonLine(scores) {
return scores.map(formatScore).join(" vs ");
}
Expand Down Expand Up @@ -14859,23 +14868,23 @@ function createSingleHostDraft(input, score) {
const window = formatWindow(input.window);
const host = formatHostApp(score.hostApp);
const rate = formatRate(score.ratePerThousandWords);
const rankable = score.rankEligible ? "rankable" : "aggregate-only";
const boundary = scoreBoundary(score);
if (input.platform === "x") {
if (input.tone === "snark") {
return `${host} extracted ${rate} rage hits/1k words from me ${window}. ${rankable} Rage AI score, transcripts stay local. ${input.url}`;
return `${host} extracted ${rate} rage hits/1k words from my ${window} window. ${scoreBoundaryStart(score)} Rage AI score, transcripts stay local. ${input.url}`;
}
return `${window} Rage AI score for ${host}: ${rate} rage hits/1k words. ${rankable}, aggregate-only, transcripts stay local. ${input.url}`;
return `${window} Rage AI score for ${host}: ${rate} rage hits/1k words. ${boundary}, transcripts stay local. ${input.url}`;
}
if (input.tone === "snark") {
return `${host} delivered my ${window} Rage AI score: ${rate} rage hits per 1,000 words.

The share is ${rankable} and aggregate-only. No raw transcripts, no matched words, no model names.
The share is ${boundary}. No raw transcripts, no matched words, no model names.

${input.url}`;
}
return `My ${window} Rage AI score for ${host} is ${rate} rage hits per 1,000 words.

This is a ${rankable}, aggregate-only share. No raw transcripts, no matched words, no model names.
${shareBoundarySentence(score)} No raw transcripts, no matched words, no model names.

${input.url}`;
}
Expand Down
19 changes: 14 additions & 5 deletions plugins/rage-codex/bin/rage.js
Original file line number Diff line number Diff line change
Expand Up @@ -14829,6 +14829,15 @@ function bestScore(scores) {
function formatScore(score) {
return `${formatHostApp(score.hostApp)} ${formatRate(score.ratePerThousandWords)}/1k`;
}
function scoreBoundary(score) {
return score.rankEligible ? "rankable, aggregate-only" : "aggregate-only";
}
function scoreBoundaryStart(score) {
return score.rankEligible ? "Rankable, aggregate-only" : "Aggregate-only";
}
function shareBoundarySentence(score) {
return score.rankEligible ? "This is a rankable, aggregate-only share." : "This is an aggregate-only share.";
}
function comparisonLine(scores) {
return scores.map(formatScore).join(" vs ");
}
Expand Down Expand Up @@ -14859,23 +14868,23 @@ function createSingleHostDraft(input, score) {
const window = formatWindow(input.window);
const host = formatHostApp(score.hostApp);
const rate = formatRate(score.ratePerThousandWords);
const rankable = score.rankEligible ? "rankable" : "aggregate-only";
const boundary = scoreBoundary(score);
if (input.platform === "x") {
if (input.tone === "snark") {
return `${host} extracted ${rate} rage hits/1k words from me ${window}. ${rankable} Rage AI score, transcripts stay local. ${input.url}`;
return `${host} extracted ${rate} rage hits/1k words from my ${window} window. ${scoreBoundaryStart(score)} Rage AI score, transcripts stay local. ${input.url}`;
}
return `${window} Rage AI score for ${host}: ${rate} rage hits/1k words. ${rankable}, aggregate-only, transcripts stay local. ${input.url}`;
return `${window} Rage AI score for ${host}: ${rate} rage hits/1k words. ${boundary}, transcripts stay local. ${input.url}`;
}
if (input.tone === "snark") {
return `${host} delivered my ${window} Rage AI score: ${rate} rage hits per 1,000 words.

The share is ${rankable} and aggregate-only. No raw transcripts, no matched words, no model names.
The share is ${boundary}. No raw transcripts, no matched words, no model names.

${input.url}`;
}
return `My ${window} Rage AI score for ${host} is ${rate} rage hits per 1,000 words.

This is a ${rankable}, aggregate-only share. No raw transcripts, no matched words, no model names.
${shareBoundarySentence(score)} No raw transcripts, no matched words, no model names.

${input.url}`;
}
Expand Down