Skip to content

fix: prettifyModel variable reference bug#30

Merged
ohong merged 3 commits into
mainfrom
nightshift/td-review
Mar 12, 2026
Merged

fix: prettifyModel variable reference bug#30
ohong merged 3 commits into
mainfrom
nightshift/td-review

Conversation

@ohong

@ohong ohong commented Mar 11, 2026

Copy link
Copy Markdown
Owner

Summary

Open issues for follow-up work:

Test plan

  • bun test apps/web/__tests__/unit/prettify-model.test.ts — 12 tests pass
  • Full test suite — 307 pass (12 more than main), 169 pre-existing failures unchanged
  • Type check — 40 pre-existing errors unchanged, no new errors

🤖 Generated with Claude Code


Automated by nightshift

Summary by CodeRabbit

  • Bug Fixes

    • Fixed model name formatting to consistently normalize whitespace, ensuring accurate display across Claude and GPT variants regardless of leading or trailing spaces.
  • Tests

    • Added comprehensive unit test coverage for model name formatting across all supported providers including Claude, OpenAI, and legacy models.

@vercel

vercel Bot commented Mar 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
straude Ready Ready Preview, Comment Mar 12, 2026 8:11pm

Request Review

@coderabbitai

coderabbitai Bot commented Mar 11, 2026

Copy link
Copy Markdown

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 79e0fbe8-db92-4f5f-994b-f20ee83b2806

📥 Commits

Reviewing files that changed from the base of the PR and between 5f3b698 and afc0388.

📒 Files selected for processing (1)
  • docs/CHANGELOG.md

📝 Walkthrough

Walkthrough

Refactors and exports prettifyModel to always use a trimmed/normalized input for all model-name checks and fallbacks; adds a new unit test suite validating behavior across Claude, OpenAI GPT variants, o3/o4 anchors, unknown models, and whitespace handling.

Changes

Cohort / File(s) Summary
Test Suite
apps/web/__tests__/unit/prettify-model.test.ts
Adds a new unit test file (+75 lines) covering Claude variants (Opus, Sonnet, Haiku, legacy includes), OpenAI GPT names and o3/o4 anchors, unknown-model passthrough, and whitespace handling (leading/trailing, tabs, newlines).
prettifyModel Refactoring
apps/web/components/app/feed/ActivityCard.tsx
Exports prettifyModel(model: string) and updates logic to use a trimmed/normalized string for all regex, includes checks, GPT/o3/o4 transformations, and fallback return value.
Documentation
docs/CHANGELOG.md
Documents the fix (prettifyModel now uses trimmed normalized value) and notes addition of unit tests.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 I nibbled whitespace from model names today,
Opus and Sonnet hopped back into play.
GPTs and anchors in neat little rows,
Tests keep them tidy where the meadow grows. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: prettifyModel variable reference bug' directly and clearly describes the main change: fixing a variable reference bug in the prettifyModel function where normalized should have been used instead of model.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch nightshift/td-review
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/__tests__/unit/prettify-model.test.ts`:
- Around line 3-21: prettifyModel logic is duplicated and post-share.ts is
missing the legacy Claude matching (includes("opus"/"sonnet"/"haiku")), causing
inconsistent prettification; extract the function into a single pure utility
(e.g., export prettifyModel from lib/utils/format.ts), implement the full logic
including the legacy includes checks, then replace the local definitions in
ActivityCard.tsx, post-share.ts, and the unit test with imports from the new
utility so all three consumers use the same function and avoid functional drift.

In `@apps/web/lib/email/unsubscribe.ts`:
- Around line 7-12: createUnsubscribeToken now returns string | null when
UNSUBSCRIBE_SECRET is missing; update all callers (e.g.,
send-reactivation-email.ts, send-comment-email.ts, send-weekly-digest.ts,
send-referral-joined-email.ts, send-welcome-email.ts,
send-direct-message-email.ts, send-winback-email.ts, send-empty-profile-email.ts
and app/api/cron/nudge-inactive/route.ts) to guard against a null token: call
createUnsubscribeToken(userId) and if it returns null, either omit the
unsubscribe link from the email payload or early-return/throw (depending on the
handler semantics) to avoid generating URLs like ?token=null or sending emails
without a valid unsubscribe mechanism. Ensure the check happens before building
the unsubscribe URL or inserting the token into templates so no invalid token is
emitted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 34f76128-3ef5-4f70-87f1-63193a3b1bc0

📥 Commits

Reviewing files that changed from the base of the PR and between 6e5c883 and 5bb051e.

📒 Files selected for processing (5)
  • apps/web/__tests__/unit/prettify-model.test.ts
  • apps/web/__tests__/unit/unsubscribe.test.ts
  • apps/web/components/app/feed/ActivityCard.tsx
  • apps/web/lib/email/unsubscribe.ts
  • docs/CHANGELOG.md

Comment on lines +3 to +21
// prettifyModel is a local function in ActivityCard.tsx — extract its logic
// here for unit testing. Kept in sync with the source.
function prettifyModel(model: string): string {
const normalized = model.trim();
if (/claude-opus-4/i.test(normalized)) return "Claude Opus";
if (/claude-sonnet-4/i.test(normalized)) return "Claude Sonnet";
if (/claude-haiku-4/i.test(normalized)) return "Claude Haiku";
if (/^gpt-/i.test(normalized)) {
return normalized
.replace(/^gpt/i, "GPT")
.replace(/-codex$/i, "-Codex");
}
if (/^o4/i.test(normalized)) return "o4";
if (/^o3/i.test(normalized)) return "o3";
if (normalized.includes("opus")) return "Claude Opus";
if (normalized.includes("sonnet")) return "Claude Sonnet";
if (normalized.includes("haiku")) return "Claude Haiku";
return normalized;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for all occurrences of prettifyModel across the codebase
rg -n "function prettifyModel" --type ts

Repository: ohong/straude

Length of output: 334


🏁 Script executed:

#!/bin/bash
# Extract full prettifyModel implementations from all three locations
echo "=== ActivityCard.tsx ==="
sed -n '38,55p' apps/web/components/app/feed/ActivityCard.tsx

echo -e "\n=== post-share.ts ==="
sed -n '12,29p' apps/web/lib/utils/post-share.ts

echo -e "\n=== prettify-model.test.ts ==="
sed -n '5,21p' apps/web/__tests__/unit/prettify-model.test.ts

Repository: ohong/straude

Length of output: 2197


Extract prettifyModel to a shared utility to prevent functional drift.

The function is duplicated in three locations, and post-share.ts is already missing critical logic: it lacks the legacy Claude matching (lines checking for includes("opus"), includes("sonnet"), includes("haiku")), which means model names like "claude-opus" won't be prettified correctly in share operations. Extract prettifyModel to a pure utility (e.g., lib/utils/format.ts) and import from a single source across ActivityCard.tsx, post-share.ts, and tests to eliminate this functional divergence.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/__tests__/unit/prettify-model.test.ts` around lines 3 - 21,
prettifyModel logic is duplicated and post-share.ts is missing the legacy Claude
matching (includes("opus"/"sonnet"/"haiku")), causing inconsistent
prettification; extract the function into a single pure utility (e.g., export
prettifyModel from lib/utils/format.ts), implement the full logic including the
legacy includes checks, then replace the local definitions in ActivityCard.tsx,
post-share.ts, and the unit test with imports from the new utility so all three
consumers use the same function and avoid functional drift.

Comment thread apps/web/lib/email/unsubscribe.ts Outdated
Comment on lines +7 to +12
export function createUnsubscribeToken(userId: string): string | null {
const secret = process.env.UNSUBSCRIBE_SECRET;
if (!secret) throw new Error("UNSUBSCRIBE_SECRET not configured");
if (!secret) {
console.error("UNSUBSCRIBE_SECRET not configured");
return null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all callers of createUnsubscribeToken to verify they handle null
rg -n "createUnsubscribeToken" --type ts -A 3

Repository: ohong/straude

Length of output: 12801


All email builders and cron handlers must guard against null unsubscribe tokens.

The signature change from string to string | null is a breaking API change. Without null checks, all 9+ callers will produce invalid unsubscribe URLs (e.g., ?token=null) when UNSUBSCRIBE_SECRET is missing:

  • send-reactivation-email.ts:20
  • send-comment-email.ts:46
  • send-weekly-digest.ts:179
  • send-referral-joined-email.ts:32
  • send-welcome-email.ts:30
  • send-direct-message-email.ts:31
  • send-winback-email.ts:20
  • send-empty-profile-email.ts:23
  • app/api/cron/nudge-inactive/route.ts:100

Each call site should either skip the unsubscribe link or early-return/throw to prevent sending emails without valid unsubscribe mechanisms.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/lib/email/unsubscribe.ts` around lines 7 - 12,
createUnsubscribeToken now returns string | null when UNSUBSCRIBE_SECRET is
missing; update all callers (e.g., send-reactivation-email.ts,
send-comment-email.ts, send-weekly-digest.ts, send-referral-joined-email.ts,
send-welcome-email.ts, send-direct-message-email.ts, send-winback-email.ts,
send-empty-profile-email.ts and app/api/cron/nudge-inactive/route.ts) to guard
against a null token: call createUnsubscribeToken(userId) and if it returns
null, either omit the unsubscribe link from the email payload or
early-return/throw (depending on the handler semantics) to avoid generating URLs
like ?token=null or sending emails without a valid unsubscribe mechanism. Ensure
the check happens before building the unsubscribe URL or inserting the token
into templates so no invalid token is emitted.

The function computed `normalized = model.trim()` but then used raw
`model` for most regex tests and .includes() calls. Whitespace-padded
model names failed anchor-based matches (^o3, ^o4, ^gpt-) and returned
untrimmed strings for unknown models. All references now use `normalized`.

Exported the function so tests import from source instead of copy-pasting.
Added 12 unit tests covering whitespace, all provider patterns, and
legacy fallbacks.

Created 6 GitHub issues for larger items found during TD review:
- #31 Race condition in usage/submit multi-device flow
- #32 Deduplicate prettifyModel across 3 files
- #33 Add rate limiting to DELETE endpoints
- #34 Add missing test coverage for 17 untested API routes
- #35 PR #22 multi-provider support review feedback
- #36 Validate leaderboard cursor input

Nightshift-Task: td-review
Nightshift-Ref: https://github.com/marcus/nightshift

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ohong ohong force-pushed the nightshift/td-review branch from 5bb051e to 5f3b698 Compare March 11, 2026 06:47
@ohong ohong changed the title fix: TD review — prettifyModel refs, unsubscribe consistency fix: prettifyModel variable reference bug + TD review tasks Mar 11, 2026
Closed duplicate issues #31-36 (duplicates of #24-29 from prior iteration).

Nightshift-Task: td-review
Nightshift-Ref: https://github.com/marcus/nightshift

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/components/app/feed/ActivityCard.tsx`:
- Around line 52-54: The legacy Claude fallback checks use case-sensitive
includes on variable normalized; make them case-insensitive by lowercasing
normalized once before those checks (e.g., compute a lowercase string like
normalizedLower = normalized.toLowerCase() or reassign normalized =
normalized.toLowerCase()) and then use normalizedLower.includes("opus"),
normalizedLower.includes("sonnet"), and normalizedLower.includes("haiku") in the
ActivityCard component so the fallback matches values like
"Anthropic/Claude-3-Opus".

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: da90b3c7-4378-4840-b2d4-aa42c36ce75f

📥 Commits

Reviewing files that changed from the base of the PR and between 5bb051e and 5f3b698.

📒 Files selected for processing (3)
  • apps/web/__tests__/unit/prettify-model.test.ts
  • apps/web/components/app/feed/ActivityCard.tsx
  • docs/CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/CHANGELOG.md

Comment on lines +52 to +54
if (normalized.includes("opus")) return "Claude Opus";
if (normalized.includes("sonnet")) return "Claude Sonnet";
if (normalized.includes("haiku")) return "Claude Haiku";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Make the legacy Claude fallback case-insensitive.

Lines 52-54 use case-sensitive includes(), so values like Anthropic/Claude-3-Opus will miss the fallback even though the regex branches above are case-insensitive. Lowercasing once before these checks keeps the behavior consistent.

💡 Proposed fix
 export function prettifyModel(model: string): string {
   const normalized = model.trim();
+  const normalizedLower = normalized.toLowerCase();
   if (/claude-opus-4/i.test(normalized)) return "Claude Opus";
   if (/claude-sonnet-4/i.test(normalized)) return "Claude Sonnet";
   if (/claude-haiku-4/i.test(normalized)) return "Claude Haiku";
   // Preserve full OpenAI model names (e.g. gpt-5.3-codex -> GPT-5.3-Codex)
   if (/^gpt-/i.test(normalized)) {
     return normalized
       .replace(/^gpt/i, "GPT")
       .replace(/-codex$/i, "-Codex");
   }
   if (/^o4/i.test(normalized)) return "o4";
   if (/^o3/i.test(normalized)) return "o3";
   // Legacy: broader Claude matching
-  if (normalized.includes("opus")) return "Claude Opus";
-  if (normalized.includes("sonnet")) return "Claude Sonnet";
-  if (normalized.includes("haiku")) return "Claude Haiku";
+  if (normalizedLower.includes("opus")) return "Claude Opus";
+  if (normalizedLower.includes("sonnet")) return "Claude Sonnet";
+  if (normalizedLower.includes("haiku")) return "Claude Haiku";
   return normalized;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/components/app/feed/ActivityCard.tsx` around lines 52 - 54, The
legacy Claude fallback checks use case-sensitive includes on variable
normalized; make them case-insensitive by lowercasing normalized once before
those checks (e.g., compute a lowercase string like normalizedLower =
normalized.toLowerCase() or reassign normalized = normalized.toLowerCase()) and
then use normalizedLower.includes("opus"), normalizedLower.includes("sonnet"),
and normalizedLower.includes("haiku") in the ActivityCard component so the
fallback matches values like "Anthropic/Claude-3-Opus".

@ohong ohong changed the title fix: prettifyModel variable reference bug + TD review tasks fix: prettifyModel variable reference bug Mar 11, 2026
@ohong ohong merged commit 115b0a3 into main Mar 12, 2026
2 of 4 checks passed
@ohong ohong deleted the nightshift/td-review branch March 12, 2026 20:13
@ohong

ohong commented Mar 12, 2026

Copy link
Copy Markdown
Owner Author

Merged. The bug was still present on main — prettifyModel computed normalized = model.trim() but then used raw model for all regex tests and .includes() calls. The 12 unit tests are a nice addition too.

Note: the duplicate issues (#31-36) mentioned in the PR body were already closed as dupes of #24-29.

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.

1 participant