Skip to content

tech-debt: dedupe helpers, surface real errors, prune pass-throughs#111

Merged
ohong merged 13 commits into
mainfrom
tech-debt/cleanup
May 2, 2026
Merged

tech-debt: dedupe helpers, surface real errors, prune pass-throughs#111
ohong merged 13 commits into
mainfrom
tech-debt/cleanup

Conversation

@ohong

@ohong ohong commented May 2, 2026

Copy link
Copy Markdown
Owner

Summary

Driven by a slop-scan audit of the codebase. Five parallel agents on isolated worktrees, integrated locally, one PR.

  • Cross-package dedupprettifyModel, getShareModelLabel, formatTokens lifted into a new @straude/shared workspace, consumed by both apps/web and packages/cli (no more silent drift between web and CLI model labels).
  • Web-internal dedup — date helpers, contribution-day filler, focus-trap hook, OG-script helper consolidated. 11 byte-identical date helpers → 1; 3 fillDays copies → 1; 3 inline focus-trap blocks → 1 hook.
  • Surfaced failuresgetOpenStatsForPage snapshot read/write failures now emit observable errors instead of silent fallback; users/me invalid-link rejection preserves the underlying parse error via cause.
  • Pruned pass-throughsproxy, getCellColor, buildSubject, buildProfileShareUrl, hasCodexLogs shims gone. stripMarkdown extracted to its own module with 13 unit tests covering each replacement.
  • Dead code removed — 3 stale root artifacts (straude-codemap.html, prometheus-list.png, posthog-setup-report.md), 3 orphaned source files (lib/performance/interaction.ts, landing/GlobalFeed.tsx, cli/lib/codex.ts), 8 stray export keywords on module-private helpers, 3 truly-unused symbols.

Net: +424 / −1,808 lines across 67 files.

slop-scan delta

Metric Before (main) After Δ
Total findings 105 69 −36
Repo score 339.0 265.5 −73.5
structure.duplicate-function-signatures 28 0 −28
structure.pass-through-wrappers 10 4 −6
defensive.async-noise 2 0 −2
defensive.error-obscuring 7 6 −1

Remaining findings are intentionally untouched: 6 justified empty catches (idempotent launchctl/crontab, localStorage access in private-mode browsers, readdir on missing Codex sessions dir), 4 Supabase SSR getAll wrappers (required by @supabase/ssr), 9 directory-fanout-hotspot informational entries, 42 tests.duplicate-mock-setup (normal Vitest patterns).

Worktree breakdown

WT Branch Scope Files touched
WT4 tech-debt/inline-pass-throughs Mechanical pass-through inlining + stripMarkdown extraction 15
WT2 tech-debt/web-internal-dedup Web-only helper consolidation 23
WT1 tech-debt/shared-package New @straude/shared workspace + cross-package dedup 15
WT3 tech-debt/error-handling Surface previously-swallowed failures 6
WT5 tech-debt/dead-code-stage Stale-artifact deletion + CHANGELOG 4
(lead) tech-debt/cleanup Integration: orphan deletion + un-export trim 10

Test plan

  • bun --cwd apps/web exec tsc --noEmit -p tsconfig.check.json — clean
  • bun --cwd packages/cli exec tsc --noEmit — clean
  • bun run lint — clean (0 errors, 0 warnings)
  • apps/web Vitest — 558/558 pass (incl. 13 new stripMarkdown cases)
  • packages/cli Vitest — 216/216 pass
  • bun dev:local smoke — landing/recap/feed/admin/focus-trap (run before merging)
  • CLI straude push --dry-run — confirms model labels + token counts unchanged
  • Spot-check OG image generation (bun --cwd apps/web run scripts/generate-og.ts)

Risk notes

  • prettifyModel consolidation has one user-visible behavior change: legacy Claude IDs (e.g. anthropic/claude-3-opus) now return "Claude Opus" instead of the raw string. The previous lib/utils/post-share.ts copy was missing the .includes() fallback that ActivityCard, open-stats, and the CLI all had — consolidation picked the more complete behavior. Verified by existing prettify-model.test.ts.
  • open-stats error path now emits structured logs (console.error with prefixed context + TODOs for PostHog server-side capture). The fallback chain to placeholder stats is preserved.
  • stripMarkdown was lifted byte-for-byte; an existing ordering quirk (link rule runs before image rule, so ![alt](url) becomes !alt not empty) is locked in by a unit test with a comment. Pre-existing behavior, not changed in this PR.
  • @straude/shared is a built package (tscdist/) consumed via workspace alias. Turbo's ^build chain handles compilation order. Direct .ts consumption was tried first but broke under CLI's NodeNext resolution.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added focus trapping to improve keyboard navigation within modals and dialogs.
  • Bug Fixes

    • Snapshot read/write failures in stats metrics are now logged with contextual messages.
    • Profile link validation errors now preserve the underlying parse failure for better debugging.
  • Improvements

    • Standardized date formatting across analytics charts.
    • Consolidated token and model label formatting to shared utilities.
    • Added a shared markdown-stripping utility used for card/share rendering.
  • Removals

    • Removed the global feed component from the landing page.
    • Removed the interactive architecture codemap visualization.

ohong and others added 12 commits May 1, 2026 22:50
…ANGELOG

Drops three exploratory artifacts that were committed to the repo root and
never referenced (`straude-codemap.html`, `prometheus-list.png`,
`posthog-setup-report.md`). Documents the parallel tech-debt cleanup
landing across worktrees in `docs/CHANGELOG.md` under Unreleased.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s and api routes

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… with tests

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lpers

Pull together four sets of duplicated web-only helpers so each pattern
has a single source of truth.

- New `apps/web/lib/utils/dates.ts` exports `formatDateMonDay` (admin
  chart axes) and `formatDateKey` (local-time YYYY-MM-DD). Replaces 6
  inline copies in admin charts and 5 inline copies in
  share-assets / contribution / recap utilities.
- New `fillContributionDays` export on `apps/web/lib/utils/recap.ts`
  replaces three byte-identical copies in the recap page, RecapCard,
  and recap-image renderer.
- New `apps/web/components/app/shared/useFocusTrap.ts` hook replaces
  three nearly-identical inline focus traps in SubmitPromptWidget,
  ImageLightbox, and SuggestCompanyWidget. The shared selector
  matches the broader form-aware variant; the lightbox has no
  textareas/inputs so the wider selector is a no-op there.
- New `apps/web/scripts/_lib.ts` exports `ensureCleanOutputDir(dir)`
  used by all three OG generator scripts.

No behavior change. tsc + apps/web vitest pass.
…pers

Removes three files surfaced by the dead-code sweep with zero importers:
- apps/web/lib/performance/interaction.ts (entire perf dir was empty after)
- apps/web/components/landing/GlobalFeed.tsx (CHANGELOG had announced
  its removal months ago, but the file itself was never deleted)
- packages/cli/src/lib/codex.ts (7-line re-export shim no one imported)

Also downgrades 8 stray exports to module-private — each is referenced
only inside its own file. The export keyword was misleading scope:

- enrichComments (apps/web/lib/comments.ts)
- OPEN_STATS_REVALIDATE_SECONDS (apps/web/lib/open-stats.ts)
- extractPublicStoragePath (apps/web/lib/storage.ts)
- isThemePreference (apps/web/lib/theme.ts)
- buildProfileShareText (apps/web/lib/utils/profile-share.ts)
- getMissingSupabaseServerEnv, hasSupabaseBrowserEnv, hasSupabaseServerEnv
  (apps/web/lib/supabase/env.ts)

CHANGELOG entries extended.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… dead

OPEN_STATS_REVALIDATE_SECONDS was declared but never wired into a
revalidate export. hasSupabaseBrowserEnv and hasSupabaseServerEnv were
boolean wrappers around getMissingSupabase*Env that no caller used.
ESLint flagged all three as unused once the export keyword no longer
shielded them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented May 2, 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 May 2, 2026 4:26pm

Request Review

@coderabbitai

coderabbitai Bot commented May 2, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2294367c-d1fc-48fc-abd4-ea1f3dc5f85c

📥 Commits

Reviewing files that changed from the base of the PR and between 83aa345 and a3f958f.

📒 Files selected for processing (3)
  • .github/workflows/ci.yml
  • package.json
  • turbo.json
💤 Files with no reviewable changes (1)
  • .github/workflows/ci.yml
✅ Files skipped from review due to trivial changes (2)
  • turbo.json
  • package.json

📝 Walkthrough

Walkthrough

This PR centralizes shared helpers into a new @straude/shared package, extracts and consolidates web-only utilities under apps/web/lib/utils and apps/web/components/app/shared, replaces many local implementations with shared imports, adds a focus-trap hook, introduces unit tests for stripMarkdown, removes dead/stale modules, and updates imports/exports and package dependencies accordingly.

Changes

Shared package & re-exports

Layer / File(s) Summary
New package & build config
packages/shared/package.json, packages/shared/tsconfig.json
New @straude/shared package with build scripts and declaration output.
Module exports
packages/shared/src/index.ts
Barrel re-exports for ./models and ./format.
Formatting & models
packages/shared/src/format.ts, packages/shared/src/models.ts
Adds formatTokens, prettifyModel, and getShareModelLabel utilities exported from shared package.

Web utilities & date helpers

Layer / File(s) Summary
New date helpers
apps/web/lib/utils/dates.ts
Adds formatDateMonDay and formatDateKey (local-time formatting helpers) and internal toDate.
New strip-markdown util
apps/web/lib/utils/strip-markdown.ts
Adds exported stripMarkdown(text: string): string (regex-based markdown removal).
Recap helper
apps/web/lib/utils/recap.ts
Adds exported fillContributionDays(...) and updates getPeriodRange to use formatDateKey.
Focus-trap hook
apps/web/components/app/shared/useFocusTrap.ts
Adds useFocusTrap(containerRef, enabled) hook to centralize modal focus trapping.
Unit tests
apps/web/__tests__/lib/strip-markdown.test.ts
Vitest test suite covering markdown stripping and newline normalization.

Replace local implementations with shared imports

Layer / File(s) Summary
Charts & admin components
apps/web/app/admin/components/*Chart.tsx (CodexGrowthCharts, GrowthMetrics, ModelShareChart, ModelUsageChart, NorthStarChart, UserSignupsChart)
Removed local date-format helpers; import formatDateMonDay as formatDate from @/lib/utils/dates.
Token/model formatting
apps/web/app/api/embed/.../route.ts, apps/web/lib/utils/format.ts, apps/web/lib/open-stats.ts, apps/web/components/app/feed/ActivityCard.tsx, apps/web/lib/utils/post-share.ts, packages/cli/src/components/ModelPalette.tsx, packages/cli/src/components/PushSummary.tsx
Replaced local formatTokens/prettifyModel/model-label logic with imports/re-exports from @straude/shared.
Markdown usage
apps/web/lib/share-assets/post-card-image.tsx
Replaced in-file stripMarkdown with import from @/lib/utils/strip-markdown.
Date-key consolidation
apps/web/lib/share-assets/*, apps/web/lib/share-assets/github-card-data.ts, apps/web/lib/share-assets/heatmap.ts, apps/web/lib/share-assets/profile-card-data.ts, apps/web/components/app/profile/ContributionGraph.tsx
Replace local date-key formatters with formatDateKey import.

Focus-trap adoption

Layer / File(s) Summary
Hook import sites
apps/web/components/app/prompts/SubmitPromptWidget.tsx, apps/web/components/app/shared/ImageLightbox.tsx, apps/web/components/app/token-rich/SuggestCompanyWidget.tsx
Replace inline Tab key handling useEffect traps with useFocusTrap(dialogRef, open) calls.

Recap / contribution flow updates

Layer / File(s) Summary
Recap pages & cards
apps/web/app/recap/[username]/page.tsx, apps/web/components/app/recap/RecapCard.tsx, apps/web/lib/utils/recap-image.tsx
Remove local fillDays implementations and import/use fillContributionDays from @/lib/utils/recap.

Export visibility & API surface changes

Layer / File(s) Summary
Downgraded exports
apps/web/lib/comments.ts, apps/web/lib/storage.ts, apps/web/lib/theme.ts, apps/web/lib/supabase/env.ts
Several module-private helpers made non-exported (reduced public surface).
Renamed/removed re-exports
packages/cli/src/lib/codex.ts, packages/cli/src/lib/codex-native.ts
containsSessionFile exported (was internal); removed legacy hasCodexLogs wrapper; removed codex re-export barrel.
Simplified proxy
apps/web/proxy.ts
Re-export updateSession as proxy instead of wrapper function.

Dead code removal

Layer / File(s) Summary
Deleted modules
apps/web/components/landing/GlobalFeed.tsx, apps/web/lib/performance/interaction.ts, straude-codemap.html, posthog-setup-report.md
Removed unused server component, performance timing helper, static architecture page, and PostHog report doc.

Scripts, build, and config tweaks

Layer / File(s) Summary
OG scripts helper
apps/web/scripts/_lib.ts, apps/web/scripts/generate-og*.ts
Extracted ensureCleanOutputDir and updated scripts to call ensureCleanOutputDir(OUTPUT_DIR); simplified filesystem imports.
Dependencies
apps/web/package.json, packages/cli/package.json, root package.json
Added @straude/shared workspace dependency to web and CLI; added root typecheck script.
Turborepo
turbo.json, .github/workflows/ci.yml
Added typecheck task (depends on ^build); removed working-directory from CI Typecheck step so bun run typecheck runs from workflow root.

Error handling & logging

Layer / File(s) Summary
Preserve causes & contextual logs
apps/web/app/api/users/me/route.ts, apps/web/lib/open-stats.ts, packages/cli/src/lib/ccusage.ts, apps/web/lib/api/cli-auth.ts
Add cause to rethrown parse errors and add contextual messages in console.error in snapshot read/write failure paths; small comments clarifying URL/JWT parse fallbacks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

"I'm a rabbit in the code hill, neat and spry,
I hopped through helpers, stitched duplicates awry.
Now tokens and models share one cozy den,
Focus traps snugly guard each modal denizen.
Hooray — less duplication, more carrot pie! 🥕"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'tech-debt: dedupe helpers, surface real errors, prune pass-throughs' directly reflects the main objectives: deduplicating helpers across packages, surfacing observable errors, and removing pass-through wrappers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tech-debt/cleanup

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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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: 7

🧹 Nitpick comments (1)
apps/web/lib/utils/recap.ts (1)

117-121: ⚡ Quick win

Use formatDateKey(d) instead of the inline template literal for consistency.

Line 120 re-inlines the same YYYY-MM-DD template literal that the PR explicitly centralised into formatDateKey. The output is identical today, but the inline copy is exactly the kind of divergence this PR was created to prevent.

♻️ Proposed fix
+import { formatDateKey } from "@/lib/utils/dates";
 ...
   for (let i = 0; i < cappedDays; i++) {
     const d = new Date(start);
     d.setDate(start.getDate() + i);
-    const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
+    const key = formatDateKey(d);
     result.push({ date: key, cost_usd: lookup.get(key) ?? 0 });
   }

(formatDateKey is already imported at line 2 — no new import needed.)

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

In `@apps/web/lib/utils/recap.ts` around lines 117 - 121, Replace the inline
YYYY-MM-DD template literal with the centralized helper: after creating d in the
loop (used with start and cappedDays), call formatDateKey(d) to compute key
instead of the template; then push { date: key, cost_usd: lookup.get(key) ?? 0 }
into result (symbols to edit: formatDateKey, d, lookup, result, start,
cappedDays).
🤖 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/app/api/embed/`[username]/svg/route.ts:
- Line 2: Add an explicit rootDir to the shared package TS config so tsc can
compute output layout: open the packages/shared/tsconfig.json and set "rootDir"
to the source root (e.g., "src") alongside existing compilerOptions so
declaration files are emitted and imports like formatTokens (imported in
apps/web/app/api/embed/[username]/svg/route.ts) resolve; after adding rootDir,
rebuild to ensure TS5011 and downstream TS2307 errors are fixed.

In `@apps/web/components/app/shared/useFocusTrap.ts`:
- Around line 26-30: The focusable NodeList from
containerRef.current.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR) can
include disabled elements (e.g., a disabled submit button), so computing
first/last directly can pick a non-tabbable element and allow focus to escape;
filter the NodeList to only include elements that are not disabled and are
actually tabbable (e.g., check !elem.hasAttribute('disabled') and elem.tabIndex
!== -1 or use elem.matches(':not([disabled])')) before deriving first and last
(references: containerRef, FOCUSABLE_SELECTOR, first, last) so Tab wrapping uses
the real tabbable elements.

In `@apps/web/lib/utils/post-share.ts`:
- Around line 2-5: The TypeScript errors come from importing subpaths from
`@straude/shared` (e.g., getShareModelLabel, prettifyModel in
apps/web/lib/utils/post-share.ts and format/formatCurrency in
apps/web/lib/utils/format.ts) before the package has been compiled to its dist/
output declared in package.json exports; fix by running the build for the shared
package (run npm run build or pnpm build in packages/shared/) to produce the
dist/*.js and dist/*.d.ts files, then ensure your CI/monorepo pipeline builds
packages/shared before apps/web (or adjust workspace build order) so those
subpath imports resolve correctly.

In `@apps/web/lib/utils/recap.ts`:
- Around line 98-114: The week-branch DST bug occurs because start is created
with new Date(now) (preserving wall-clock time) while now may be in DST, causing
daysSinceStart to undercount; to fix, normalize both start and now to local
midnight before computing daysSinceStart and cappedDays (e.g., set
hours/minutes/seconds/ms to 0 for start and now) inside the code paths that
compute msPerDay, daysSinceStart and cappedDays so the diff uses whole days;
update the variables used in the calculation (now, start, msPerDay,
daysSinceStart, cappedDays) and ensure the month branch behavior remains
unchanged.

In `@docs/CHANGELOG.md`:
- Around line 59-65: There are two "### Removed" blocks under the "##
Unreleased" version; remove the duplicate block you added and append its bullet
entries (the items about stale root-level artifacts, pass-through wrappers,
orphaned source files, stray `export` keywords, and truly-dead
constants/helpers) into the existing "### Removed" section within the same "##
Unreleased" block so the changelog follows Keep a Changelog (single header per
section); locate the duplicate `### Removed` header you added and merge its
bullet lines into the existing `### Removed` section under `## Unreleased`, then
delete the duplicate header and its empty space.

In `@packages/shared/src/format.ts`:
- Line 4: The check that formats thousands currently returns `${(n /
1_000).toFixed(1)}k`, which produces "1.0k" for exact thousands; change this to
strip trailing ".0" by converting the toFixed string back to a number before
appending "k" (e.g., use parseFloat((n / 1_000).toFixed(1)) or Number(...) to
drop the unnecessary ".0"). Update the expression in the same conditional that
contains `if (n >= 1_000) return \`${(n / 1_000).toFixed(1)}k\`;` so outputs
become "1k", "1.4k", "1.5k", etc., without altering the fractional precision for
non-exact thousands.

In `@packages/shared/src/models.ts`:
- Around line 14-15: The pattern matching on the normalized model ID incorrectly
collapses any string starting with "o3" or "o4" to the family name; update the
checks around the normalized variable so they only match the bare family or
family followed by a hyphen (e.g., use /^o4(?:$|-)/i and /^o3(?:$|-)/i) to
preserve suffixes like "o4-mini" or "o3-mini" rather than rewriting them to just
"o4"/"o3". Locate the two regex lines that reference normalized and replace them
with the stricter family-or-hyphen matches so suffixes are kept intact.

---

Nitpick comments:
In `@apps/web/lib/utils/recap.ts`:
- Around line 117-121: Replace the inline YYYY-MM-DD template literal with the
centralized helper: after creating d in the loop (used with start and
cappedDays), call formatDateKey(d) to compute key instead of the template; then
push { date: key, cost_usd: lookup.get(key) ?? 0 } into result (symbols to edit:
formatDateKey, d, lookup, result, start, cappedDays).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 089b7c6c-dea1-418b-80df-ace1982407a2

📥 Commits

Reviewing files that changed from the base of the PR and between 6d2f153 and 83aa345.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • prometheus-list.png is excluded by !**/*.png
📒 Files selected for processing (65)
  • apps/web/__tests__/lib/strip-markdown.test.ts
  • apps/web/app/admin/components/CodexGrowthCharts.tsx
  • apps/web/app/admin/components/GrowthMetrics.tsx
  • apps/web/app/admin/components/ModelShareChart.tsx
  • apps/web/app/admin/components/ModelUsageChart.tsx
  • apps/web/app/admin/components/NorthStarChart.tsx
  • apps/web/app/admin/components/UserSignupsChart.tsx
  • apps/web/app/api/embed/[username]/svg/route.ts
  • apps/web/app/api/users/me/route.ts
  • apps/web/app/recap/[username]/page.tsx
  • apps/web/components/app/feed/ActivityCard.tsx
  • apps/web/components/app/profile/ContributionGraph.tsx
  • apps/web/components/app/profile/ProfileSharePanel.tsx
  • apps/web/components/app/prompts/SubmitPromptWidget.tsx
  • apps/web/components/app/recap/RecapCard.tsx
  • apps/web/components/app/shared/ImageLightbox.tsx
  • apps/web/components/app/shared/useFocusTrap.ts
  • apps/web/components/app/token-rich/SuggestCompanyWidget.tsx
  • apps/web/components/landing/GlobalFeed.tsx
  • apps/web/lib/admin.ts
  • apps/web/lib/api/cli-auth.ts
  • apps/web/lib/comments.ts
  • apps/web/lib/email/notification-email.tsx
  • apps/web/lib/email/send-comment-email.ts
  • apps/web/lib/open-stats.ts
  • apps/web/lib/performance/interaction.ts
  • apps/web/lib/share-assets/github-card-data.ts
  • apps/web/lib/share-assets/heatmap.ts
  • apps/web/lib/share-assets/post-card-image.tsx
  • apps/web/lib/share-assets/profile-card-data.ts
  • apps/web/lib/storage.ts
  • apps/web/lib/supabase/env.ts
  • apps/web/lib/theme.ts
  • apps/web/lib/utils/dates.ts
  • apps/web/lib/utils/format.ts
  • apps/web/lib/utils/post-share.ts
  • apps/web/lib/utils/profile-share.ts
  • apps/web/lib/utils/recap-image.tsx
  • apps/web/lib/utils/recap.ts
  • apps/web/lib/utils/strip-markdown.ts
  • apps/web/package.json
  • apps/web/proxy.ts
  • apps/web/scripts/_lib.ts
  • apps/web/scripts/generate-og-athletic-surge.ts
  • apps/web/scripts/generate-og-real-users.ts
  • apps/web/scripts/generate-og.ts
  • docs/CHANGELOG.md
  • packages/cli/__tests__/codex.test.ts
  • packages/cli/__tests__/commands/push.test.ts
  • packages/cli/__tests__/flows/cli-sync-flow.test.ts
  • packages/cli/package.json
  • packages/cli/src/commands/login.ts
  • packages/cli/src/commands/push.ts
  • packages/cli/src/components/ModelPalette.tsx
  • packages/cli/src/components/PushSummary.tsx
  • packages/cli/src/lib/ccusage.ts
  • packages/cli/src/lib/codex-native.ts
  • packages/cli/src/lib/codex.ts
  • packages/shared/package.json
  • packages/shared/src/format.ts
  • packages/shared/src/index.ts
  • packages/shared/src/models.ts
  • packages/shared/tsconfig.json
  • posthog-setup-report.md
  • straude-codemap.html
💤 Files with no reviewable changes (5)
  • packages/cli/src/lib/codex.ts
  • posthog-setup-report.md
  • apps/web/lib/performance/interaction.ts
  • straude-codemap.html
  • apps/web/components/landing/GlobalFeed.tsx

@@ -1,4 +1,5 @@
import { NextRequest } from "next/server";
import { formatTokens } from "@straude/shared/format";

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check packages/shared/package.json for exports/types configuration
fd -g 'package.json' packages/shared --exec cat {}

# Verify TypeScript compiler can resolve subpath from apps/web tsconfig
fd -g 'tsconfig*.json' apps/web --exec cat {}

# Check if any declarations are already generated
fd -e d.ts packages/shared

Repository: ohong/straude

Length of output: 1929


🏁 Script executed:

# Check if dist/format.d.ts exists
fd -type f packages/shared/dist

# Check if source files exist
fd -type f packages/shared/src

# Look at the directory structure of packages/shared
tree -L 3 packages/shared 2>/dev/null || find packages/shared -type f -o -type d | head -30

# Check if there's a build output or tsconfig in packages/shared
cat packages/shared/tsconfig.json 2>/dev/null || echo "No tsconfig.json found"

Repository: ohong/straude

Length of output: 846


🏁 Script executed:

# Check if dist is in .gitignore
cat packages/shared/.gitignore 2>/dev/null || echo "No .gitignore in packages/shared"
cat .gitignore 2>/dev/null | grep -E "dist|node_modules" || echo "Checking root .gitignore"

# Check git status to see if dist is untracked or ignored
git -C packages/shared status 2>/dev/null || git status packages/shared

# Verify the exports field one more time from the initial results is accurate
cat packages/shared/package.json | grep -A 15 '"exports"'

Repository: ohong/straude

Length of output: 720


🏁 Script executed:

cd packages/shared && npm run build

Repository: ohong/straude

Length of output: 637


Actual blocker: packages/shared/tsconfig.json is missing rootDir configuration.

The build fails with TS5011:

The 'rootDir' setting must be explicitly set to this or another path to adjust your output's file layout.

The exports field in package.json is already correctly configured. The issue is that packages/shared/tsconfig.json needs an explicit rootDir setting:

  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "isolatedModules": true,
    "declaration": true,
    "outDir": "./dist",
+   "rootDir": "./src"
  },

Without this, the TypeScript compiler cannot build the package, so the declaration files are never generated, causing the TS2307 error in consuming packages.

🧰 Tools
🪛 GitHub Actions: CI

[error] 2-2: TypeScript (TS2307): Cannot find module '@straude/shared/format' or its corresponding type declarations.

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

In `@apps/web/app/api/embed/`[username]/svg/route.ts at line 2, Add an explicit
rootDir to the shared package TS config so tsc can compute output layout: open
the packages/shared/tsconfig.json and set "rootDir" to the source root (e.g.,
"src") alongside existing compilerOptions so declaration files are emitted and
imports like formatTokens (imported in
apps/web/app/api/embed/[username]/svg/route.ts) resolve; after adding rootDir,
rebuild to ensure TS5011 and downstream TS2307 errors are fixed.

Comment on lines +26 to +30
const focusable =
containerRef.current.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR);
if (focusable.length === 0) return;
const first = focusable[0];
const last = focusable[focusable.length - 1];

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 | ⚡ Quick win

Filter disabled elements before computing first/last.

Both modal-form consumers can disable their submit button while the dialog stays open, so this list can pick a disabled control as last. When that happens, Tab on the real last tabbable element falls through and focus escapes the modal.

Suggested fix
-      const focusable =
-        containerRef.current.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR);
+      const focusable = Array.from(
+        containerRef.current.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),
+      ).filter((el) => !el.matches(":disabled"));
       if (focusable.length === 0) return;
       const first = focusable[0];
       const last = focusable[focusable.length - 1];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/components/app/shared/useFocusTrap.ts` around lines 26 - 30, The
focusable NodeList from
containerRef.current.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR) can
include disabled elements (e.g., a disabled submit button), so computing
first/last directly can pick a non-tabbable element and allow focus to escape;
filter the NodeList to only include elements that are not disabled and are
actually tabbable (e.g., check !elem.hasAttribute('disabled') and elem.tabIndex
!== -1 or use elem.matches(':not([disabled])')) before deriving first and last
(references: containerRef, FOCUSABLE_SELECTOR, first, last) so Tab wrapping uses
the real tabbable elements.

Comment on lines +2 to +5
import { getShareModelLabel } from "@straude/shared/models";
import { formatCurrency, formatTokens } from "./format";

export { prettifyModel, getShareModelLabel } from "@straude/shared/models";

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect shared package configuration and tsconfig path aliases
echo "=== packages/shared/package.json ==="
fd "package.json" packages/shared --max-depth 1 --exec cat {}

echo ""
echo "=== apps/web/tsconfig.json ==="
fd "tsconfig.json" apps/web --max-depth 1 --exec cat {}

echo ""
echo "=== Source files in packages/shared/src ==="
fd . packages/shared/src --type f

echo ""
echo "=== Root tsconfig (for workspace paths) ==="
fd "tsconfig.json" . --max-depth 1 --exec cat {}

Repository: ohong/straude

Length of output: 1681


🏁 Script executed:

#!/bin/bash
# Check if dist folder exists and its contents
echo "=== packages/shared/dist (if exists) ==="
if [ -d "packages/shared/dist" ]; then
  find packages/shared/dist -type f | head -20
else
  echo "dist folder does not exist"
fi

echo ""
echo "=== packages/shared directory structure ==="
ls -la packages/shared/

Repository: ohong/straude

Length of output: 454


CI blocker: @straude/shared subpath imports fail because the package has not been built.

The TS2307 errors occur in:

  • apps/web/lib/utils/post-share.ts lines 2, 5 (@straude/shared/models)
  • apps/web/components/app/feed/ActivityCard.tsx lines 10–11 (@straude/shared/models)
  • apps/web/lib/open-stats.ts line 1 (@straude/shared/models)
  • apps/web/lib/utils/format.ts line 1 (@straude/shared/format)

Root cause: packages/shared/dist/ does not exist. The package source files are present (src/models.ts, src/format.ts, src/index.ts), but they have not been compiled to the dist/ directory that package.json's exports map declares:

"exports": {
  "./models": { "types": "./dist/models.d.ts", ... },
  "./format": { "types": "./dist/format.d.ts", ... }
}

TypeScript cannot resolve these imports because the declared .d.ts files don't exist yet.

Solution: Run npm run build (or pnpm build) in packages/shared/ to generate the dist/ directory with compiled output. Ensure the shared package is built before apps/web in the monorepo build pipeline.

🧰 Tools
🪛 GitHub Actions: CI

[error] 2-2: TypeScript (TS2307): Cannot find module '@straude/shared/models' or its corresponding type declarations.


[error] 5-5: TypeScript (TS2307): Cannot find module '@straude/shared/models' or its corresponding type declarations.

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

In `@apps/web/lib/utils/post-share.ts` around lines 2 - 5, The TypeScript errors
come from importing subpaths from `@straude/shared` (e.g., getShareModelLabel,
prettifyModel in apps/web/lib/utils/post-share.ts and format/formatCurrency in
apps/web/lib/utils/format.ts) before the package has been compiled to its dist/
output declared in package.json exports; fix by running the build for the shared
package (run npm run build or pnpm build in packages/shared/) to produce the
dist/*.js and dist/*.d.ts files, then ensure your CI/monorepo pipeline builds
packages/shared before apps/web (or adjust workspace build order) so those
subpath imports resolve correctly.

Comment on lines +98 to +114
const now = new Date();
let start: Date;

if (period === "week") {
const day = now.getDay();
const mondayOffset = day === 0 ? -6 : 1 - day;
start = new Date(now);
start.setDate(now.getDate() + mondayOffset);
} else {
start = new Date(now.getFullYear(), now.getMonth(), 1);
}

// Cap at today — don't render future days
const msPerDay = 86400000;
const daysSinceStart =
Math.floor((now.getTime() - start.getTime()) / msPerDay) + 1;
const cappedDays = Math.min(totalDays, daysSinceStart);

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 | ⚡ Quick win

DST edge case causes week strip to show 6 days instead of 7 on spring-forward Sunday.

For the week branch, start = new Date(now) copies now's wall-clock time. When a spring-forward DST boundary falls between Monday and today (Sunday), start (Monday in standard time) and now (Sunday in daylight time) differ by 23 hours rather than 6 full days. Math.floor(23h × ... / 86_400_000) = 5, so daysSinceStart = 6 and the Sunday cell is silently omitted from the strip.

The month branch is unaffected because new Date(year, month, 1) creates start at local midnight regardless.

🐛 Proposed fix — normalize both dates to midnight before computing the diff
   // Cap at today — don't render future days
   const msPerDay = 86400000;
-  const daysSinceStart =
-    Math.floor((now.getTime() - start.getTime()) / msPerDay) + 1;
+  const todayMidnight = new Date(now);
+  todayMidnight.setHours(0, 0, 0, 0);
+  const startMidnight = new Date(start);
+  startMidnight.setHours(0, 0, 0, 0);
+  const daysSinceStart =
+    Math.floor((todayMidnight.getTime() - startMidnight.getTime()) / msPerDay) + 1;
   const cappedDays = Math.min(totalDays, daysSinceStart);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const now = new Date();
let start: Date;
if (period === "week") {
const day = now.getDay();
const mondayOffset = day === 0 ? -6 : 1 - day;
start = new Date(now);
start.setDate(now.getDate() + mondayOffset);
} else {
start = new Date(now.getFullYear(), now.getMonth(), 1);
}
// Cap at today — don't render future days
const msPerDay = 86400000;
const daysSinceStart =
Math.floor((now.getTime() - start.getTime()) / msPerDay) + 1;
const cappedDays = Math.min(totalDays, daysSinceStart);
const now = new Date();
let start: Date;
if (period === "week") {
const day = now.getDay();
const mondayOffset = day === 0 ? -6 : 1 - day;
start = new Date(now);
start.setDate(now.getDate() + mondayOffset);
} else {
start = new Date(now.getFullYear(), now.getMonth(), 1);
}
// Cap at today — don't render future days
const msPerDay = 86400000;
const todayMidnight = new Date(now);
todayMidnight.setHours(0, 0, 0, 0);
const startMidnight = new Date(start);
startMidnight.setHours(0, 0, 0, 0);
const daysSinceStart =
Math.floor((todayMidnight.getTime() - startMidnight.getTime()) / msPerDay) + 1;
const cappedDays = Math.min(totalDays, daysSinceStart);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/lib/utils/recap.ts` around lines 98 - 114, The week-branch DST bug
occurs because start is created with new Date(now) (preserving wall-clock time)
while now may be in DST, causing daysSinceStart to undercount; to fix, normalize
both start and now to local midnight before computing daysSinceStart and
cappedDays (e.g., set hours/minutes/seconds/ms to 0 for start and now) inside
the code paths that compute msPerDay, daysSinceStart and cappedDays so the diff
uses whole days; update the variables used in the calculation (now, start,
msPerDay, daysSinceStart, cappedDays) and ensure the month branch behavior
remains unchanged.

Comment thread docs/CHANGELOG.md
Comment on lines +59 to +65
### Removed

- **Stale root-level artifacts.** `straude-codemap.html`, `prometheus-list.png`, and `posthog-setup-report.md` were committed to the repo root during exploratory work and never referenced — deleted.
- **Pass-through wrappers left over from prior renames.** `proxy` is now a direct re-export of `updateSession`, `getCellColor` is a re-export of `getHeatmapCellColor`, and the unused `buildSubject`, `buildProfileShareUrl`, and `hasCodexLogs` shims were removed.
- **Orphaned source files.** `apps/web/lib/performance/interaction.ts` (zero importers, no `lib/performance` consumers remained), `apps/web/components/landing/GlobalFeed.tsx` (the CHANGELOG had previously announced its removal but the file was never deleted), and `packages/cli/src/lib/codex.ts` (a 7-line re-export shim with no callers — consumers now import from `./codex-native` directly) were deleted.
- **Stray `export` keywords on module-private helpers.** `enrichComments`, `extractPublicStoragePath`, `isThemePreference`, `buildProfileShareText`, and `getMissingSupabaseServerEnv` were exported but only referenced inside their own files. Downgraded to module-private to signal scope honestly.
- **Truly-dead constants and helpers.** Un-exporting also revealed three symbols with zero callers anywhere — `OPEN_STATS_REVALIDATE_SECONDS` (declared but never wired into a `revalidate` site), `hasSupabaseBrowserEnv`, and `hasSupabaseServerEnv`. Removed.

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 | ⚡ Quick win

Merge into the existing ### Removed section instead of creating a duplicate.

A ### Removed section already exists at line 73 under ## Unreleased. Adding a second one violates Keep a Changelog format, which requires each section type to appear at most once per version block.

🛠️ Proposed fix — move entries to existing section

Remove the new ### Removed block (lines 59-65) and append its entries into the existing ### Removed block at line 73 instead:

-### Removed
-
-- **Stale root-level artifacts.** ...
-- **Pass-through wrappers ...** ...
-- **Orphaned source files.** ...
-- **Stray `export` keywords ...** ...
-- **Truly-dead constants ...** ...
-
 ### Added

 - **User Signups bar chart ...** ...
...
 ### Removed

+- **Stale root-level artifacts.** ...
+- **Pass-through wrappers ...** ...
+- **Orphaned source files.** ...
+- **Stray `export` keywords ...** ...
+- **Truly-dead constants ...** ...
+
 - **Product Hunt launch banner and badge.** ...

As per coding guidelines: "follow Keep a Changelog format with Added/Changed/Fixed/Removed sections under ## Unreleased".

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

In `@docs/CHANGELOG.md` around lines 59 - 65, There are two "### Removed" blocks
under the "## Unreleased" version; remove the duplicate block you added and
append its bullet entries (the items about stale root-level artifacts,
pass-through wrappers, orphaned source files, stray `export` keywords, and
truly-dead constants/helpers) into the existing "### Removed" section within the
same "## Unreleased" block so the changelog follows Keep a Changelog (single
header per section); locate the duplicate `### Removed` header you added and
merge its bullet lines into the existing `### Removed` section under `##
Unreleased`, then delete the duplicate header and its empty space.

export function formatTokens(n: number): string {
if (n >= 1_000_000_000) return `${(n / 1_000_000_000).toFixed(1)}B`;
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`;

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 | ⚡ Quick win

Undocumented behavioral change: round thousands now render as "1.0k" instead of "1k".

The old CLI implementation used Math.round(n / 1_000)k, so:

n old CLI new shared
1 000 "1k" "1.0k"
1 400 "1k" "1.4k" ✓ more precise
1 500 "2k" "1.5k" ✓ more precise
2 000 "2k" "2.0k"

The precision gain for fractional values is an improvement, but the trailing .0 for exact multiples of 1000 is visual noise in CLI output. The PR notes only list prettifyModel as a user-visible change; this one is undocumented. If intentional, it should be noted in the changelog. If not, parseFloat drops the trailing zero without losing precision:

💡 Proposed fix to strip trailing zeros
-  if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`;
+  if (n >= 1_000) return `${parseFloat((n / 1_000).toFixed(1))}k`;

parseFloat("1.0")1, parseFloat("1.4")1.4, so "1k" / "1.4k" / "1.5k" etc.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`;
if (n >= 1_000) return `${parseFloat((n / 1_000).toFixed(1))}k`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/format.ts` at line 4, The check that formats thousands
currently returns `${(n / 1_000).toFixed(1)}k`, which produces "1.0k" for exact
thousands; change this to strip trailing ".0" by converting the toFixed string
back to a number before appending "k" (e.g., use parseFloat((n /
1_000).toFixed(1)) or Number(...) to drop the unnecessary ".0"). Update the
expression in the same conditional that contains `if (n >= 1_000) return \`${(n
/ 1_000).toFixed(1)}k\`;` so outputs become "1k", "1.4k", "1.5k", etc., without
altering the fractional precision for non-exact thousands.

Comment on lines +14 to +15
if (/^o4/i.test(normalized)) return "o4";
if (/^o3/i.test(normalized)) return "o3";

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 | ⚡ Quick win

Don't collapse suffixed o3/o4 identifiers.

These prefix checks rewrite any o3*/o4* model ID to the family name, so labels like o3-mini or o4-mini lose their distinguishing suffix while the GPT branch above deliberately preserves it. That will mislabel distinct models anywhere this shared helper is used.

Suggested fix
-  if (/^o4/i.test(normalized)) return "o4";
-  if (/^o3/i.test(normalized)) return "o3";
+  if (/^o4$/i.test(normalized)) return "o4";
+  if (/^o3$/i.test(normalized)) return "o3";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (/^o4/i.test(normalized)) return "o4";
if (/^o3/i.test(normalized)) return "o3";
if (/^o4$/i.test(normalized)) return "o4";
if (/^o3$/i.test(normalized)) return "o3";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/models.ts` around lines 14 - 15, The pattern matching on
the normalized model ID incorrectly collapses any string starting with "o3" or
"o4" to the family name; update the checks around the normalized variable so
they only match the bare family or family followed by a hyphen (e.g., use
/^o4(?:$|-)/i and /^o3(?:$|-)/i) to preserve suffixes like "o4-mini" or
"o3-mini" rather than rewriting them to just "o4"/"o3". Locate the two regex
lines that reference normalized and replace them with the stricter
family-or-hyphen matches so suffixes are kept intact.

Adds a `typecheck` task to turbo.json with `dependsOn: ["^build"]` and
switches CI from `bun run typecheck` in `apps/web` to `bun run typecheck`
at the repo root (which delegates to `turbo typecheck`). Without this,
`@straude/shared`'s `dist/` is never built before tsc runs in `apps/web`,
producing TS2307 on `@straude/shared/format` and `@straude/shared/models`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ohong ohong merged commit 1da3e87 into main May 2, 2026
5 checks passed
@ohong ohong deleted the tech-debt/cleanup branch May 2, 2026 17:02
@ohong

ohong commented May 2, 2026

Copy link
Copy Markdown
Owner Author

Merged into main as 1da3e87 (squash). Branch deleted.

Net: +428 / −1,809 across 70 files. Highlights:

  • Extracted @straude/shared workspace; deduped date/contribution/focus-trap/og helpers across web + CLI
  • Surfaced previously-swallowed failures in CI/runtime paths
  • Pruned orphaned files (GlobalFeed.tsx, codex.ts, interaction.ts, root artifacts) and un-exported truly-dead module-private symbols
  • Routed typecheck through Turbo so workspace deps build first

All checks were green at merge (CI, claude-review, CodeRabbit, Vercel).

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