Skip to content

[codex] Publish app admin social updates#113

Merged
therealityreport merged 9 commits into
mainfrom
codex/publish/trr-app/20260705-174033-clean
Jul 6, 2026
Merged

[codex] Publish app admin social updates#113
therealityreport merged 9 commits into
mainfrom
codex/publish/trr-app/20260705-174033-clean

Conversation

@therealityreport

@therealityreport therealityreport commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • Publish admin social UI and data-flow updates.
  • Gate the new media-queue/operator controls behind the existing admin social feature flags so default admin sessions stay quiet.
  • Remove generated local handoff/status churn from this replacement PR scope.

Validation

  • git diff --check origin/main...HEAD
  • pnpm -C apps/web exec vitest run tests/admin-social-page-auth-bypass.test.tsx
  • pnpm -C apps/web exec tsc -p tsconfig.typecheck.json --noEmit
  • pnpm run web:validate:quick

Notes

  • Full production app build was not run because the TRR workspace instructions require explicit approval for that command.
  • Supersedes [codex] Publish app admin social updates #112, which was opened before subagent review cleanup.

Summary by CodeRabbit

  • New Features

    • Added new admin dashboard views for catalog backfill, comments recovery, and media-queue health.
    • Introduced new status panels for Portless, Vercel preview readiness, and cleanup checks.
    • Added a performance benchmark command and a new set of shared UI components.
  • Bug Fixes

    • Improved route handling and redirects for social/admin pages.
    • Updated local development URLs and host handling for smoother navigation.
  • Chores

    • Refreshed dependencies, analytics, and test coverage.

- Admin social account profile + dashboard load-time instrumentation
- New media-mirror drain/recover, comments repair-auth, social-growth, and
  completion-summary API routes; media-queue snapshots
- shadcn-style UI primitives (alert/badge/card/input/separator), trr-app flags
- Vercel analytics + speed-insights in root layout; allowedDevOrigins
- postgres pool connection-string resolver; portless/api-default updates; tests
Copilot AI review requested due to automatic review settings July 5, 2026 23:04
@vercel

vercel Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
trr-app Ready Ready Preview, Comment Jul 6, 2026 12:43am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@therealityreport, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 29 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 82844439-52a9-4cfa-88d9-31f42b802531

📥 Commits

Reviewing files that changed from the base of the PR and between 048473c and 83778e5.

📒 Files selected for processing (2)
  • apps/web/eslint.config.mjs
  • apps/web/tests/social-account-profile-page.runtime.test.tsx
📝 Walkthrough

Walkthrough

This PR bundles many independent changes: Portless/admin host URL routing simplification, dev-dashboard Vercel/Portless readiness widgets, new Instagram catalog/comments mockup dashboards, a social media queue ingestion UI with backend routes, expanded social account profile types/API routes, relay-checkpoint comments recovery, SocialBlade refresh polling, unified show auto-refresh, Reddit alias redirects, new UI primitives, shared live-resource polling fixes, brand font scoring refactor, timezone/growth utilities, and dependency/script updates.

Changes

Admin Host Routing & Dashboards

Layer / File(s) Summary
Portless URL defaults & routing
apps/web/src/lib/admin/admin-url-defaults.ts, apps/web/src/proxy.ts, apps/web/src/app/admin/page.tsx, apps/web/src/components/admin/AdminGlobalHeader.tsx, apps/web/src/lib/server/admin/portless-status.ts, apps/web/next.config.ts, apps/web/playwright.config.ts, apps/web/README.md, scripts/portless-banner.mjs
Admin URLs drop the legacy /admin suffix, route-definition constants centralize app/admin/api/wordle URLs, proxy rewrite/redirect logic and dev origins are updated, and portless service status parsing is added, with matching tests.
Dev dashboard Vercel/Portless widgets
apps/web/src/lib/server/admin/dev-dashboard-service.ts, apps/web/src/app/admin/dev-dashboard/page.tsx
New Vercel preview readiness/cleanup doctor server helpers feed new dashboard sections showing readiness checks and portless service/route status.
Instagram mockup dashboards
apps/web/src/app/admin/dev-dashboard/instagram-catalog-backfill-mockup/..., apps/web/src/app/admin/dev-dashboard/instagram-comments-mockup/..., apps/web/src/components/admin/instagram/InstagramCatalogBackfillMockupView.tsx, apps/web/src/app/social/.../catalog/alt-1/page.tsx
New read-only command-center pages poll live catalog/comments run APIs and render progress, shard health, and history.

Social Ingestion, Profile API & Recovery

Layer / File(s) Summary
Media queue ingestion UI & routes
apps/web/src/app/admin/social/page.tsx, apps/web/src/app/api/admin/trr-api/social/ingest/media-mirror/..., apps/web/src/app/api/admin/social/media-queue/snapshots/route.ts
Feature-flagged media queue badge/drilldown UI with recovery/drain actions backed by new proxy routes.
Social account profile types & API routes
apps/web/src/lib/admin/social-account-profile.ts, apps/web/src/app/api/admin/trr-api/social/profiles/..., apps/web/src/app/api/admin/trr-api/social-growth/...
Extended type contracts and multiple routes for recent runs, progress, completion summary, live totals, snapshots, and SocialBlade call/cookie/history endpoints.
Instagram comments relay-checkpoint recovery
apps/web/src/components/admin/instagram/InstagramCommentsPanel.tsx
Replaces audit-cursor recovery with public-relay checkpoint recovery and an Incomplete Fill preview flow.
SocialBlade cast comparison refresh
apps/web/src/components/admin/cast-socialblade-comparison.tsx, e2e fixtures/specs
Adds Modal call-tracked refresh states (queued/running/still_running), cookie health, and job history panels.

Other Areas

Layer / File(s) Summary
Show auto-refresh unification
apps/web/src/app/admin/trr-shows/...
Replaces per-target refresh triggers with a unified full show auto-refresh flow.
Reddit alias redirects
apps/web/src/app/admin/social/reddit/...
Converts legacy re-export pages into redirecting async pages.
New UI primitives
apps/web/src/components/ui/{alert,badge,card,input,separator}.tsx
Adds Alert, Badge, Card, Input, and Separator components.
Shared live resource polling, font scoring, timezone/growth utilities, auth finish page, misc infra & scripts
apps/web/src/lib/admin/shared-live-resource.ts, apps/web/src/lib/fonts/brand-fonts/scoring*.ts, apps/web/src/lib/admin/social-timezone.ts, apps/web/src/app/auth/finish/page.tsx, scripts/vercel.sh, apps/web/scripts/perf-admin-api.mjs
Miscellaneous fixes, refactors, and tooling additions with supporting tests.

Estimated code review effort: 5 (Critical) | ~150 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant CastSocialBladeComparison
  participant RefreshBatchAPI
  participant ModalCallStatusAPI
  Operator->>CastSocialBladeComparison: trigger batch refresh
  CastSocialBladeComparison->>RefreshBatchAPI: POST /refresh-batch
  RefreshBatchAPI-->>CastSocialBladeComparison: accepted entries with callId
  loop polling
    CastSocialBladeComparison->>ModalCallStatusAPI: fetch call status by callId
    ModalCallStatusAPI-->>CastSocialBladeComparison: queued/running/completed/failed
  end
  CastSocialBladeComparison-->>Operator: render status rows and history
Loading

Possibly related PRs

  • therealityreport/trr-app#109: Shares the same admin-origin/host allowlist and portless admin URL updates as the routing changes in this PR.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is related to the PR, but it is too vague and generic to describe the main changes clearly. Use a more specific title that names the primary change, such as the new admin social routes, UI, or media-queue controls.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/publish/trr-app/20260705-174033-clean

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.

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Codex Exhaustive Code Review

Findings

  1. High: stale recovery accepts unsafe numeric controls
    recover-stale/route.ts forwards Number(...) || fallback for stale_after_seconds, recover_limit, and dispatch_limit. Negative values are truthy, so a direct admin POST can send stale_after_seconds: -1 or negative limits to the backend. For a recovery endpoint, that can recover jobs that are not actually stale and duplicate dispatch in-flight media work.
    Smallest fix: use the same positive integer normalization as drain-account, ideally with sane upper bounds, and reject invalid values with 400 instead of forwarding them.

  2. Medium: client feature flags cannot be enabled reliably
    trr-app-flags.ts reads process.env through dynamic keys like env[definition.envVar]. This module is imported by the client admin social page, and Next client builds only reliably inline NEXT_PUBLIC_* values through static property access. In practice, the default-off flags such as NEXT_PUBLIC_TRR_FLAG_ADMIN_SOCIAL_INGESTION_UI and NEXT_PUBLIC_TRR_FLAG_ADMIN_SOCIAL_SCRAPER_TRIGGERS can stay stuck at their defaults in the browser, so the new UI cannot be enabled via env as intended.
    Smallest fix: build the default env object with explicit static reads, e.g. NEXT_PUBLIC_TRR_FLAG_ADMIN_SOCIAL_INGESTION_UI: process.env.NEXT_PUBLIC_TRR_FLAG_ADMIN_SOCIAL_INGESTION_UI, then pass that object into the generic parser.

Validation

Reviewed only e5bc156933761a3fc7174d7a3e1cd14f553c9a24...2a03fa51bc0122ec670bc1813f6277c11821495f. Ran git diff --stat, git diff --find-renames, and git diff --check; diff check passed. I could not run Vitest/Playwright because this checkout has no node_modules installed.

Copilot AI 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.

Pull request overview

Publishes the next iteration of admin social updates (UI + supporting API routes), while keeping higher-risk ingestion/operator controls gated behind explicit admin-social flags and improving local-dev diagnostics around Portless/Vercel tooling.

Changes:

  • Adds new admin social proxy routes + media-queue snapshot listing/reading, with accompanying unit/E2E coverage.
  • Introduces centralized trr-app-flags for safe-by-default admin/social feature gating.
  • Enhances local dev-dashboard diagnostics (Portless status, Vercel preview readiness + nested-link doctor) and refines admin-host proxy behavior.

frontend_surface

  • Admin social pages/components, admin host proxy middleware behavior, and dev-dashboard diagnostics UI/services.

rendering_boundaries

  • Client components: new/updated admin UI surfaces and shared polling behavior.
  • Route handlers: multiple new /api/admin/... handlers for social/admin proxying and workspace snapshot reads.
  • Middleware/proxy: apps/web/src/proxy.ts routing/rewrites.

contract_dependencies

  • TRR-Backend admin social endpoints (social proxy paths, SocialBlade admin endpoints, ingest/media-mirror endpoints).
  • New public env flags: NEXT_PUBLIC_TRR_FLAG_ADMIN_SOCIAL_* (and existing admin social flags).
  • Local workspace artifacts under .logs/workspace/... for snapshot/readiness tooling.

validation_run

  • git diff --check origin/main...HEAD
  • pnpm -C apps/web exec vitest run tests/admin-social-page-auth-bypass.test.tsx
  • pnpm -C apps/web exec tsc -p tsconfig.typecheck.json --noEmit
  • pnpm run web:validate:quick

residual_ui_risks

  • Badge is missing "use client" and likely misuses Radix Slot (Slot.Root), which can cause Next.js build/runtime failures and/or runtime crashes when asChild is used (blocking).
  • live-profile-total route currently masks all upstream failures as a 200 degraded payload; consider narrowing fallback behavior to timeout/unreachable classes to avoid hiding real upstream 4xx/5xx errors.

Reviewed changes

Copilot reviewed 89 out of 92 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
scripts/test-vercel-guard.sh Adds more complete guard-only and explicit-prod opt-in coverage for Vercel wrapper behavior.
scripts/portless-banner.mjs Updates Portless banner URLs to treat clean admin root as the canonical entry.
pnpm-workspace.yaml Adds overrides and release-age exclusions for specific dependency versions.
package.json Bumps Firebase/Firebase Admin and firebase-tools versions at repo root.
apps/web/tests/trr-app-flags.test.ts Adds unit coverage for the new centralized app flags module.
apps/web/tests/social-media-queue-snapshots-route.test.ts Tests media-queue snapshot listing and safe filename validation.
apps/web/tests/social-ingest-media-mirror-recover-route.test.ts Tests recover-stale media-mirror proxy route validation + forwarding.
apps/web/tests/social-ingest-media-mirror-drain-account-route.test.ts Tests drain-account proxy route validation + forwarding.
apps/web/tests/social-growth-cookie-health-route.test.ts Tests SocialBlade cookie health proxy behavior and error propagation.
apps/web/tests/social-account-profile-snapshot-route.test.ts Updates snapshot expectations around progress timeout + caching behavior.
apps/web/tests/social-account-profile-page.hashtag-preview.test.ts Adjusts hashtag preview behavior test inputs for new assignment status param.
apps/web/tests/social-account-profile-completion-summary-route.test.ts Adds route tests for completion summary backed by Postgres query.
apps/web/tests/social-account-profile-catalog-diagnostics.test.ts Adds unit coverage for catalog progress diagnostics rows.
apps/web/tests/social-account-profile-backfill-command.test.ts Updates backfill selected tasks defaults to include media.
apps/web/tests/social-account-live-profile-total-route.test.ts Adds test for fast degraded fallback behavior on live total timeout/error.
apps/web/tests/social-account-catalog-run-progress-route.test.ts Updates fast polling timeout expectations to use progress timeout constant.
apps/web/tests/social-account-catalog-recent-runs-route.test.ts Adds tests for recent catalog runs route and attached followup coercion.
apps/web/tests/social-account-catalog-alt-page.test.ts Adds tests for the new catalog alt-1 page routing/canonicalization.
apps/web/tests/show-refresh-health-center-wiring.test.ts Ensures show detail page auto-refresh wiring is present and stable.
apps/web/tests/shared-live-resource-polling.test.tsx Adds test to prevent duplicate polls when multiple subscribers refetch in-flight.
apps/web/tests/postgres-connection-string-resolution.test.ts Adds coverage for stripping pg SSL query params in pool connection string.
apps/web/tests/instagram-catalog-backfill-mockup-page.test.tsx Adds component-level tests for the Instagram catalog backfill mockup page.
apps/web/tests/finish.flow.test.tsx Adds Finish page tests for API-provided show list + fallback behavior.
apps/web/tests/e2e/admin-fixtures.ts Expands mocked admin API to support social growth + season social routes.
apps/web/tests/e2e/admin-cast-tabs-smoke.spec.ts Adds E2E smoke for SocialBlade refresh modal queued/running call id behavior.
apps/web/tests/dev-dashboard-vercel-preview-readiness-service.test.ts Adds tests for reading preview-readiness artifact and nested-link doctor.
apps/web/tests/covered-shows-page-no-fanout-wiring.test.ts Locks in show auto-refresh target fanout + navigation param behavior.
apps/web/tests/cast-socialblade-snapshot-wiring.test.ts Wiring tests for polling + modal refresh status tracking and e2e alignment.
apps/web/tests/brand-font-matching-scoring.test.ts Adds tests for scoring maxima extraction + null glyph artifact handling.
apps/web/tests/admin-social-page-auth-bypass.test.tsx Extends admin social auth bypass test to cover new ingestion/media queue controls behind flags.
apps/web/tests/admin-portless-status.test.ts Adds parsing coverage for portless service status output + route expectations.
apps/web/tests/admin-host-middleware.test.ts Updates middleware tests for rewrite vs redirect behavior and legacy URL redirects.
apps/web/tests/admin-api-references-generator.test.ts Ensures dynamic query templates normalize without leaking template sources.
apps/web/src/proxy.ts Refines admin host proxy parsing/rewrites, adds API passthrough, and supports catalog alt variants.
apps/web/src/lib/trr-app-flags.ts Introduces a single module for app/admin feature flags with safe defaults + parsing.
apps/web/src/lib/server/postgres.ts Adds pg pool connection string sanitizer to avoid SSL query overriding explicit SSL config.
apps/web/src/lib/server/admin/portless-status.ts Adds Portless service-status parsing and includes it in status snapshot.
apps/web/src/lib/server/admin/dev-dashboard-service.ts Adds Vercel preview readiness + cleanup doctor data to dev dashboard service.
apps/web/src/lib/fonts/brand-fonts/scoring.ts Refactors scoring profiles into shared module and propagates visual diagnostics in results.
apps/web/src/lib/fonts/brand-fonts/scoring-profiles.ts New shared scoring profile constants/maxima for UI + tests.
apps/web/src/lib/data/shows.ts Extends local show list with additional shows used in Finish flow.
apps/web/src/lib/admin/shared-live-resource.ts Improves poll request merging and avoids duplicate polling while requests are in-flight.
apps/web/src/lib/admin/api-references/generator.ts Normalizes/strips query-template fragments from generated path patterns.
apps/web/src/lib/admin/admin-url-defaults.ts Centralizes Portless route definitions/links/status routes and health URL constants.
apps/web/src/components/ui/separator.tsx Adds separator UI primitive.
apps/web/src/components/ui/input.tsx Adds input UI primitive.
apps/web/src/components/ui/card.tsx Adds card UI primitives used by admin mockups/views.
apps/web/src/components/ui/badge.tsx Adds badge UI primitive used by admin mockups/views.
apps/web/src/components/ui/alert.tsx Adds alert UI primitives used by admin mockups/views.
apps/web/src/components/admin/social-week/WeekDetailPageView.tsx Adjusts snapshot polling gating to avoid unnecessary polling during comment syncing transitions.
apps/web/src/components/admin/design-system/BrandFontMatchesPanel.tsx Uses shared scoring maxima to render score breakdown bars consistently.
apps/web/src/components/admin/cast-content-section.tsx Allows dev admin bypass when fetching cast-role members in admin UI flows.
apps/web/src/components/admin/AdminGlobalHeader.tsx Updates Portless fallback warning messaging and canonical admin URL reference.
apps/web/src/app/social/[platform]/[handle]/catalog/alt-1/page.tsx Adds catalog alt-1 page variant with canonicalization/redirect behavior.
apps/web/src/app/layout.tsx Adds Vercel Analytics + Speed Insights to root layout.
apps/web/src/app/auth/finish/page.tsx Uses API-provided show list when available, falling back to local list.
apps/web/src/app/api/admin/trr-api/social/profiles/[platform]/[handle]/snapshot/route.ts Adjusts progress timeout constant usage and removes forced refresh-on-active-work behavior.
apps/web/src/app/api/admin/trr-api/social/profiles/[platform]/[handle]/live-profile-total/route.ts Adds fast timeout and degraded fallback payload behavior for live totals.
apps/web/src/app/api/admin/trr-api/social/profiles/[platform]/[handle]/comments/runs/[runId]/repair-auth/route.ts Adds proxy route to repair comments-run auth via backend.
apps/web/src/app/api/admin/trr-api/social/profiles/[platform]/[handle]/comments/runs/[runId]/public-recovery/route.ts Adds proxy route to start public recovery for a comments run.
apps/web/src/app/api/admin/trr-api/social/profiles/[platform]/[handle]/comments/runs/[runId]/authenticated-followup/route.ts Adds proxy route to start authenticated follow-up for a comments run.
apps/web/src/app/api/admin/trr-api/social/profiles/[platform]/[handle]/catalog/runs/recent/route.ts Adds Postgres-backed recent runs route with caching and attached followup normalization.
apps/web/src/app/api/admin/trr-api/social/profiles/[platform]/[handle]/catalog/runs/[runId]/progress/route.ts Simplifies progress timeout selection and reporting to use shared progress timeout.
apps/web/src/app/api/admin/trr-api/social/ingest/media-mirror/recover-stale/route.ts Adds validated proxy route for run-scoped stale media recovery.
apps/web/src/app/api/admin/trr-api/social/ingest/media-mirror/drain-account/route.ts Adds validated proxy route for run-scoped account drain operations.
apps/web/src/app/api/admin/trr-api/social-growth/history/route.ts Adds internal-admin-auth SocialBlade history proxy route.
apps/web/src/app/api/admin/trr-api/social-growth/cookies/health/route.ts Adds internal-admin-auth SocialBlade cookie health proxy route.
apps/web/src/app/api/admin/trr-api/social-growth/calls/[callId]/route.ts Adds internal-admin-auth SocialBlade call-status proxy route.
apps/web/src/app/api/admin/social/media-queue/snapshots/route.ts Adds admin-only API for listing/reading media queue snapshot artifacts safely.
apps/web/src/app/admin/trr-shows/page.tsx Expands auto-refresh fanout targets and propagates pre-navigation refresh signals.
apps/web/src/app/admin/trr-shows/[showId]/page.tsx Switches to auto “full refresh once per show view” logic with sessionStorage guard.
apps/web/src/app/admin/page.tsx Updates Portless URL card to use centralized route defs and richer diagnostics snapshot.
apps/web/src/app/admin/dev-dashboard/instagram-catalog-backfill-mockup/page.tsx Adds a dedicated dev-dashboard mockup page for catalog backfill UI.
apps/web/src/app/admin/dev-dashboard/_components/DevDashboardShell.tsx Adds nav entry for the new catalog backfill dev-dashboard page.
apps/web/scripts/perf-admin-api.mjs Adds a local perf harness script for low-concurrency admin API benchmarking.
apps/web/README.md Updates documentation to point to clean Portless admin root URL.
apps/web/playwright.config.ts Aligns base URL/default webServer env to use localhost rather than 127.0.0.1.
apps/web/package.json Adds Vercel analytics deps, bumps several deps/devDeps, adds perf script.
apps/web/next.config.ts Adds allowedDevOrigins for local dev hostnames.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +4
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"

...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot.Root : "span"
Comment on lines +89 to +93
return NextResponse.json(buildLiveProfileTotalFallback(platform, handle, "backend_timeout_or_error"), {
headers: {
"x-trr-cache": "fallback",
"x-trr-cacheable": "0",
},

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

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/components/admin/instagram/InstagramCommentsPanel.tsx (1)

869-900: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Stop the relay-checkpoint auto-refresh from re-running on every filter keystroke. The auto-load effect depends on refreshRelayCheckpointRecovery, and that callback depends on relayCheckpointShowFilter, so typing in the filter issues a fetch per character and can let stale responses overwrite newer state. Keep the mount/auto-refresh path stable and read the latest filter only from the manual Refresh action.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/admin/instagram/InstagramCommentsPanel.tsx` around
lines 869 - 900, The relay-checkpoint auto-refresh is re-running on every
`relayCheckpointShowFilter` keystroke because `refreshRelayCheckpointRecovery`
is recreated from that state and is used by the `useEffect` auto-load path. Make
the effect stable so it only auto-loads on mount/support changes, and remove the
filter from the auto-refresh dependency chain in `InstagramCommentsPanel` while
still reading the latest filter when the user explicitly clicks Refresh. Keep
the fetch logic in `refreshRelayCheckpointRecovery` and the `useEffect` trigger
separated so stale responses from intermediate keystrokes cannot overwrite newer
state.
🟡 Minor comments (8)
apps/web/src/components/admin/instagram/InstagramCommentsPanel.tsx-1714-1721 (1)

1714-1721: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Show-filter input has no accessible name.

This input only carries a placeholder, which is not a reliable accessible name for assistive tech (the main posts search at Line 1994-1995 uses an sr-only label). Add an aria-label (or associated <label>).

🛠 Proposed fix
                 <input
                   type="search"
+                  aria-label="Filter relay checkpoint targets by show"
                   value={relayCheckpointShowFilter}
                   onChange={(event) => setRelayCheckpointShowFilter(event.target.value)}
                   placeholder="Show filter"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/admin/instagram/InstagramCommentsPanel.tsx` around
lines 1714 - 1721, The show-filter search field in InstagramCommentsPanel lacks
an accessible name because it only relies on a placeholder. Update the input
bound to relayCheckpointShowFilter to include an aria-label or associate it with
a visible/sr-only label, matching the approach used by the main posts search in
this component. Keep the existing behavior and styling intact while ensuring the
input is properly announced by assistive technologies.
apps/web/src/lib/server/admin/dev-dashboard-service.ts-175-183 (1)

175-183: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle .vercel detection with path components. root.endsWith("/.vercel") is POSIX-only, so collectVercelProjectFiles() will miss valid project links on Windows. Use a path-segment check instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/server/admin/dev-dashboard-service.ts` around lines 175 -
183, The `.vercel` project-root check in `collectVercelProjectFiles()` is using
a POSIX-only suffix match, so it can miss valid links on Windows. Update the
path detection logic to inspect path components via the existing path utilities
instead of relying on `root.endsWith("/.vercel")`, and keep the behavior
localized to the `collectVercelProjectFiles` flow.
apps/web/src/app/api/admin/trr-api/social-growth/calls/[callId]/route.ts-24-24 (1)

24-24: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Forward the admin context to the backend. requireAdmin(request) returns the authenticated admin, but buildInternalAdminHeaders() is called without that context here, so the outbound request omits the admin identity metadata. Pass a VerifiedAdminContext through so SocialBlade actions can be attributed server-side.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/api/admin/trr-api/social-growth/calls/`[callId]/route.ts at
line 24, The admin identity is not being forwarded to the backend because the
result of requireAdmin(request) is not passed into buildInternalAdminHeaders()
in the [callId] route handler. Update the handler to capture the
VerifiedAdminContext returned by requireAdmin(request), then pass that context
into buildInternalAdminHeaders so the outbound SocialBlade request includes the
admin metadata for server-side attribution.
apps/web/src/app/admin/dev-dashboard/instagram-comments-mockup/page.tsx-45-45 (1)

45-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

"Overview" sidebar link points to a non-existent anchor.

navItems[0] ("Overview") slugifies to #overview, but no element in the page has id="overview" — only active-run (Line 529), workers (Line 612), coverage (Line 676), and actions (Line 575) exist. Clicking "Overview" does nothing.

Proposed fix
-        <section className="px-4 py-5 sm:px-6 lg:px-8">
+        <section id="overview" className="px-4 py-5 sm:px-6 lg:px-8">

Also applies to: 457-469, 479-479

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/admin/dev-dashboard/instagram-comments-mockup/page.tsx` at
line 45, The first entry in navItems (“Overview”) points to a missing anchor, so
the sidebar link cannot navigate anywhere. Update the dev dashboard page’s
section anchors or nav mapping in instagram-comments-mockup/page.tsx so the
“Overview” item targets an actual element id, or rename/remove it if there is no
corresponding section. Verify the nav item generation and the section headings
for active-run, workers, coverage, and actions stay aligned.
apps/web/src/app/admin/dev-dashboard/instagram-comments-mockup/page.tsx-84-87 (1)

84-87: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

readFiniteNumber treats explicit null as 0 instead of "no data".

Number(null) evaluates to 0, which is finite, so an explicit null (a common "not yet available" API signal) is silently coerced to 0 rather than propagating as null. Downstream consumers like completedPosts !== null && totalPosts !== null (Lines 270-272, 363-366) rely on null to distinguish "waiting for data" from "zero progress" — with this bug, a payload field explicitly set to null would render as 0 of N posts checked instead of Waiting for post progress, misrepresenting run state.

Proposed fix
 const readFiniteNumber = (value: unknown): number | null => {
+  if (value === null || value === undefined) return null;
   const numeric = Number(value);
   return Number.isFinite(numeric) ? numeric : null;
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/admin/dev-dashboard/instagram-comments-mockup/page.tsx`
around lines 84 - 87, `readFiniteNumber` is coercing explicit null values to 0,
which breaks the “no data yet” signal used by the dashboard. Update the helper
in `page.tsx` so it returns null when the input is null or undefined before
calling Number(), and keep the finite-number check for real numeric inputs. This
will preserve the null semantics relied on by the progress rendering logic that
distinguishes waiting states from zero progress.
scripts/vercel.sh-429-434 (1)

429-434: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

latest.json copy can fail for custom --output/TRR_VERCEL_PREVIEW_READY_OUTPUT paths.

write_preview_ready_artifact only mkdir -ps the directory of $output_path. The subsequent cp "$output_path" "$WORKSPACE_ROOT/.logs/workspace/vercel-preview-ready/latest.json" at Line 518 targets a hardcoded directory that is never created explicitly — it only exists if a prior default-path run created it. If a caller always supplies a custom --output/TRR_VERCEL_PREVIEW_READY_OUTPUT (e.g. in CI) and that directory has never been populated by a default run, cp fails and (assuming set -e is active at the top of the script, consistent with the set +e/set -e toggling pattern used elsewhere) the whole preview-ready command fails even though all three checks (Web Analytics, Speed Insights, deployments) succeeded.

🐛 Proposed fix
+  mkdir -p "$WORKSPACE_ROOT/.logs/workspace/vercel-preview-ready"
   cp "$output_path" "$WORKSPACE_ROOT/.logs/workspace/vercel-preview-ready/latest.json"

Also applies to: 517-519

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/vercel.sh` around lines 429 - 434, The preview-ready artifact flow in
write_preview_ready_artifact can fail when copying to the hardcoded latest.json
path because that directory is never created for custom output paths. Update the
logic around write_preview_ready_artifact and the latest.json copy so the
destination directory under .logs/workspace/vercel-preview-ready is created
before cp runs, regardless of whether default_preview_ready_output or a custom
--output/TRR_VERCEL_PREVIEW_READY_OUTPUT path was used.
apps/web/scripts/perf-admin-api.mjs-92-97 (1)

92-97: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

--max-p95-ms never trips with autocannon JSON output. percentileMs(parsed, "p95") only looks for latency.p95/latency.pp95, but autocannon emits p2_5, p50, p75, p90, p97_5, p99, p99_9, p99_99, p99_999; p95 is always null, so the max-p95 guard is skipped. Use p97_5 here or rename the flag to match the available percentile.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/scripts/perf-admin-api.mjs` around lines 92 - 97, The max-p95 check
in percentileMs is reading a percentile name that autocannon does not emit, so
the guard in perf-admin-api.mjs never sees a value. Update the logic used by the
max-p95 path to read the autocannon percentile that corresponds to p95 behavior,
such as p97_5, or rename the flag to match the available percentile; keep the
change localized around percentileMs and the code that calls it for
--max-p95-ms.
apps/web/src/app/admin/social/page.tsx-337-377 (1)

337-377: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

run.active falsy-zero bug undercounts/overcounts active jobs.

toNonNegativeInt(run.active) || <sum of stage counts> treats a legitimate 0 returned from run.active the same as "not provided", falling through to recompute active from stage buckets. If the backend reports active: 0 while stale stage-bucket rows haven't been cleared yet, the badge/dropdown would show a non-zero "active" count for a run that's actually idle — misleading during recovery/drain decisions. Contrast with getFirstNonNegativeInt elsewhere in this file, which correctly distinguishes null/undefined from an explicit 0.

🐛 Proposed fix
-      const active =
-        toNonNegativeInt(run.active) ||
-        SOCIAL_MEDIA_QUEUE_STAGES.reduce((total, stage) => {
-          const bucket = run.stages?.[stage];
-          return (
-            total +
-            queueBucketCount(bucket, "queued") +
-            queueBucketCount(bucket, "pending") +
-            queueBucketCount(bucket, "running") +
-            queueBucketCount(bucket, "retrying")
-          );
-        }, 0);
+      const activeFromField =
+        run.active === null || run.active === undefined
+          ? null
+          : toNonNegativeInt(run.active);
+      const active =
+        activeFromField ??
+        SOCIAL_MEDIA_QUEUE_STAGES.reduce((total, stage) => {
+          const bucket = run.stages?.[stage];
+          return (
+            total +
+            queueBucketCount(bucket, "queued") +
+            queueBucketCount(bucket, "pending") +
+            queueBucketCount(bucket, "running") +
+            queueBucketCount(bucket, "retrying")
+          );
+        }, 0);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/admin/social/page.tsx` around lines 337 - 377, The
active-count fallback in buildMediaQueueRunOptions incorrectly treats an
explicit 0 from run.active as missing because it uses a falsy check, which can
cause stale stage buckets to override a real idle state. Update the run.active
handling to distinguish null/undefined from a valid zero, similar to
getFirstNonNegativeInt in this file, and only fall back to summing
SOCIAL_MEDIA_QUEUE_STAGES when run.active is not provided. Keep the fix
localized to buildMediaQueueRunOptions and preserve the existing
runId/status/timestamp logic.
🧹 Nitpick comments (19)
apps/web/tests/public-route-boundary.test.ts (1)

53-65: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Test doesn't validate the actual redirect target format.

The assertions only check for redirect( and import presence, not the constructed path. This test would pass even with the missing s prefix bug found in all four alias pages (seasonNumber should be encoded as s${seasonNumber}, not just ${seasonNumber}). Consider asserting the literal s${encodeURIComponent( (or similar) pattern to lock in the canonical URL shape.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/tests/public-route-boundary.test.ts` around lines 53 - 65, The
public-route-boundary test only checks that a redirect exists, so it misses
regressions in the redirect target shape. Update the assertions in the public
route shim test to verify the actual canonical season path format built by the
alias pages, especially the `s${...}` prefix in the redirect target. Use the
existing `LEGACY_ADMIN_REDDIT_SEASON_ROUTE_FILES` loop and tighten the `source`
expectations so it matches the literal encoded
`s${encodeURIComponent(...)}`-style pattern rather than only `redirect(`.
apps/web/src/lib/server/admin/dev-dashboard-service.ts (2)

332-352: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Unbounded recursive scan walks the entire app source tree on every dashboard load.

collectVercelProjectFiles recurses into every non-pruned subdirectory of TRR_APP_ROOT (only .git, node_modules, .next, dist, build, .turbo, .venv, __pycache__ are excluded) looking for a .vercel/project.json, which in practice only lives at package roots (e.g. apps/web/.vercel). This means a full filesystem walk of src/, tests/, etc. runs synchronously on every getDevDashboardData() call with no caching.

Consider capping recursion depth or only scanning known package roots (e.g. apps/*, packages/*) instead of a full recursive walk.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/server/admin/dev-dashboard-service.ts` around lines 332 -
352, The collectVercelProjectFiles scan in dev-dashboard-service is doing an
unbounded recursive walk from TRR_APP_ROOT, which can traverse src, tests, and
other non-package trees on every dashboard load. Update
collectVercelProjectFiles (and its caller in getDevDashboardData) to limit the
search scope by either capping recursion depth or only iterating known package
roots such as apps/* and packages/*, while still finding .vercel/project.json
via the existing collectVercelProjectFiles logic and VERCEL_SCAN_PRUNED_DIRS
pruning.

1084-1096: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

New readiness collectors bypass the existing allSettled resilience pattern.

repoStatusesPromise/taskPlansPromise are intentionally wrapped in Promise.allSettled so one failing collector doesn't blank the whole dashboard. The three new promises (portlessStatusPromise, vercelPreviewReadinessPromise, vercelCleanupDoctorPromise) are awaited via plain Promise.all, so if any of them ever rejects unexpectedly, the entire getDevDashboardData() call fails and the repo/task data that would otherwise render successfully is lost too.

The current implementations appear to internally catch their own errors, so this isn't actively broken today, but it's an inconsistency worth closing for defense-in-depth.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/server/admin/dev-dashboard-service.ts` around lines 1084 -
1096, The new readiness collectors are not using the same resilience pattern as
the existing dashboard collectors. Update getDevDashboardData() so
portlessStatusPromise, vercelPreviewReadinessPromise, and
vercelCleanupDoctorPromise are handled with allSettled-style protection like
repoStatusesPromise and taskPlansPromise, then extract their values defensively
instead of relying on plain Promise.all. Keep the existing symbols
getPortlessStatus(), readVercelPreviewReadinessArtifact(), and
readVercelCleanupDoctor grouped in the same guarded flow so one unexpected
rejection does not fail the entire dashboard load.
apps/web/tests/dev-dashboard-vercel-preview-readiness-service.test.ts (1)

1-117: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Good coverage of the happy paths; consider adding negative-path tests.

Coverage is limited to "artifact missing" and "artifact present and valid" for readiness, and "stale project" for the cleanup doctor. Error paths in dev-dashboard-service.ts (invalid JSON, non-object JSON, unreadable project.json, non-zero check exit status producing errors[]) aren't exercised.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/tests/dev-dashboard-vercel-preview-readiness-service.test.ts` around
lines 1 - 117, Add negative-path tests for the dev dashboard helpers in
readVercelPreviewReadinessArtifact and readVercelCleanupDoctor, since current
coverage only checks missing/valid artifacts and one stale-link case. In
apps/web/tests/dev-dashboard-vercel-preview-readiness-service.test.ts, cover
invalid latest.json JSON, non-object JSON, and a non-zero check status so the
resulting errors[] is asserted. Also add a cleanup-doctor case for unreadable or
malformed .vercel/project.json so readVercelCleanupDoctor handles it correctly.
apps/web/src/app/admin/dev-dashboard/page.tsx (1)

882-885: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded admin URL duplicates an existing shared constant.

"https://admin.trr.localhost" is hardcoded here, but apps/web/src/components/admin/AdminGlobalHeader.tsx already references a PORTLESS_ADMIN_DASHBOARD_URL constant for exactly this "clean admin URL" concept. If that constant's value ever changes (different TLD, port, etc.), this string will silently drift out of sync.

Consider importing and reusing PORTLESS_ADMIN_DASHBOARD_URL instead of the literal string.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/admin/dev-dashboard/page.tsx` around lines 882 - 885, The
“Clean admin URL” text in the dev dashboard is hardcoding the portless admin
host instead of reusing the shared constant. Update the dev-dashboard page to
import and use PORTLESS_ADMIN_DASHBOARD_URL, matching the pattern already used
in AdminGlobalHeader. Replace the literal URL in the Portless Browser Readiness
section so the admin URL stays consistent if the shared value changes.
apps/web/src/app/api/admin/trr-api/social/profiles/[platform]/[handle]/comments/runs/[runId]/authenticated-followup/route.ts (1)

1-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Near-identical boilerplate across three comments-recovery routes.

This handler, public-recovery/route.ts, and repair-auth/route.ts differ only in the backend path segment and error strings. Consider extracting a small factory (e.g. createCommentsRunProxyRoute(action, fallbackError)) to avoid triple-maintaining auth, timeout, and error-handling logic.

♻️ Example factory sketch
// apps/web/src/lib/server/trr-api/comments-run-proxy.ts
export function createCommentsRunProxyRoute(action: string, fallbackError: string) {
  return async function POST(request: NextRequest, context: RouteContext) {
    const { platform, handle, runId } = await context.params;
    try {
      await requireAdmin(request);
      const body = await request.text();
      const data = await fetchSocialBackendJson(
        `/profiles/${encodeURIComponent(platform)}/${encodeURIComponent(handle)}/comments/runs/${encodeURIComponent(runId)}/${action}`,
        { method: "POST", headers: { "Content-Type": "application/json" }, body, fallbackError, retries: 0, timeoutMs: 210_000 },
      );
      return NextResponse.json(data);
    } catch (error) {
      return socialProxyErrorResponse(error, `[api] ${fallbackError}`);
    }
  };
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/app/api/admin/trr-api/social/profiles/`[platform]/[handle]/comments/runs/[runId]/authenticated-followup/route.ts
around lines 1 - 38, This route duplicates the same comments-run proxy
boilerplate found in the other recovery handlers, so extract a shared factory to
centralize auth, request body forwarding, timeout, retries, and error handling.
Create a helper such as createCommentsRunProxyRoute that takes the action
segment and fallback error text, and reuse it from POST instead of keeping
separate near-identical implementations in this handler and the other
comments-recovery routes. Keep the unique behavior limited to the action path
and error strings while preserving requireAdmin, fetchSocialBackendJson, and
socialProxyErrorResponse usage.
apps/web/src/app/api/admin/trr-api/social-growth/calls/[callId]/route.ts (1)

1-73: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared SocialBlade proxy boilerplate.

This handler, cookies/health/route.ts, and history/route.ts all repeat the identical requireAdmin → build backend URL → buildInternalAdminHeaders → timeoutSafeFetch → error-mapping structure, differing only in path, timeout constant, and log string. Since socialblade-proxy.ts already centralizes payload/timeout building, consider extending it with a shared proxySocialBladeGet(path, { timeoutMs, timeoutName, logLabel }) helper to remove ~40 duplicated lines per route and keep future error-handling fixes in one place.

♻️ Sketch of a shared helper
// socialblade-proxy.ts
export async function proxySocialBladeGet(
  request: NextRequest,
  path: string,
  opts: { timeoutMs: number; timeoutName: string; logLabel: string },
) {
  await requireAdmin(request);
  const backendUrl = getBackendApiUrl(path);
  if (!backendUrl) {
    return NextResponse.json({ error: "Backend API not configured (TRR_API_URL)" }, { status: 502 });
  }
  let headers: Headers;
  try {
    headers = buildInternalAdminHeaders();
  } catch {
    return NextResponse.json({ error: "Backend auth not configured" }, { status: 502 });
  }
  const upstream = await timeoutSafeFetch(backendUrl, { headers, timeoutMs: opts.timeoutMs, timeoutName: opts.timeoutName });
  const data = await upstream.json().catch(() => ({ error: "Invalid response from backend" }));
  if (!upstream.ok) {
    return NextResponse.json(buildSocialBladeBackendErrorPayload(data, `Backend returned ${upstream.status}`), { status: upstream.status });
  }
  return NextResponse.json(data);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/api/admin/trr-api/social-growth/calls/`[callId]/route.ts
around lines 1 - 73, The SocialBlade call status route repeats the same
requireAdmin, backend URL, auth header, timeout fetch, and backend error-mapping
flow that already exists in the other SocialBlade routes. Move that shared
GET-proxy logic into a reusable helper in socialblade-proxy.ts, such as
proxySocialBladeGet, and have this handler call it with the route-specific path,
timeout settings, and log label. Keep the existing
buildSocialBladeBackendErrorPayload and buildSocialBladeTimeoutResponse behavior
centralized in the helper so future changes only need to be made once.
apps/web/tests/social-growth-cookie-health-route.test.ts (1)

37-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for auth-failure, misconfiguration, and timeout branches.

Current tests only cover the success path and backend-error passthrough. The route also branches on requireAdmin throwing "forbidden"/"unauthorized", getBackendApiUrl returning null (502), buildInternalAdminHeaders throwing (502), and isTimeoutSafeFetchTimeoutError returning true (504 via buildSocialBladeTimeoutResponse). None of these are exercised, leaving the newly-added auth/error-mapping logic untested.

✅ Example additional cases
it("maps forbidden errors to 403", async () => {
  requireAdminMock.mockRejectedValue(new Error("forbidden"));
  const response = await GET(new NextRequest("http://localhost/api/admin/trr-api/social-growth/cookies/health"));
  expect(response.status).toBe(403);
});

it("returns 504 on upstream timeout", async () => {
  const timeoutError = new Error("timeout");
  timeoutSafeFetchMock.mockRejectedValue(timeoutError);
  isTimeoutSafeFetchTimeoutErrorMock.mockReturnValue(true);
  const response = await GET(new NextRequest("http://localhost/api/admin/trr-api/social-growth/cookies/health"));
  expect(response.status).toBe(504);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/tests/social-growth-cookie-health-route.test.ts` around lines 37 -
115, The current social-growth cookie health route tests only cover the happy
path and backend error passthrough; add cases in the GET-related test suite to
exercise the auth and error-mapping branches. Specifically, in
social-growth-cookie-health-route.test.ts, cover requireAdminMock rejecting with
"forbidden" and "unauthorized", getBackendApiUrlMock returning null,
buildInternalAdminHeadersMock throwing, and timeoutSafeFetchMock rejecting while
isTimeoutSafeFetchTimeoutErrorMock returns true. Assert the corresponding
response statuses and any timeout response behavior from
buildSocialBladeTimeoutResponse so the route’s newly added failure handling
stays covered.
apps/web/src/app/admin/dev-dashboard/instagram-comments-mockup/page.tsx (2)

202-260: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Overlapping polling requests aren't guarded; consider the shared live-resource helper instead of ad hoc setInterval.

refreshDashboard has no in-flight guard (no ref/AbortController), and the useEffect fires it immediately then every 5s via window.setInterval regardless of whether the previous call is still pending. If a request takes longer than REFRESH_INTERVAL_MS, concurrent fetches can resolve out of order and stale data can overwrite fresher state. This PR's stack already introduces apps/web/src/lib/admin/shared-live-resource.ts specifically to merge shared polling requests and dispose cleanly — reusing it here would avoid re-implementing the same race-prone pattern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/admin/dev-dashboard/instagram-comments-mockup/page.tsx`
around lines 202 - 260, `refreshDashboard` is doing ad hoc polling with
`window.setInterval` and no in-flight protection, which can create overlapping
requests and stale state updates. Refactor the polling logic in `page.tsx` to
use the shared live-resource helper from `shared-live-resource.ts` instead of
manually scheduling `load`; wire the helper around
`refreshDashboard`/`useEffect` so only one request is active at a time and
cleanup/disposal happens correctly on unmount or dependency changes.

514-517: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Full page reload instead of client-side navigation.

window.location.href = canonicalCommentsUrl forces a full browser reload, whereas the "Back to current dashboard" link (Lines 483-489) already uses next/link's Link for the same kind of navigation. Using Link here keeps the transition consistent and avoids the unnecessary reload cost.

Proposed fix
-                <Button onClick={() => { window.location.href = canonicalCommentsUrl; }}>
-                  <PlayIcon data-icon="inline-start" />
-                  Open live dashboard
-                </Button>
+                <Button asChild>
+                  <Link href={canonicalCommentsUrl}>
+                    <PlayIcon data-icon="inline-start" />
+                    Open live dashboard
+                  </Link>
+                </Button>

(Adjust if the local Button component doesn't support asChild; a plain styled Link works equally well.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/admin/dev-dashboard/instagram-comments-mockup/page.tsx`
around lines 514 - 517, The Open live dashboard action in the
instagram-comments-mockup page is using window.location.href, which causes a
full reload instead of client-side navigation. Update the Button interaction in
the same area as the existing Back to current dashboard Link so it uses
next/link navigation to canonicalCommentsUrl, or render a Link styled like the
Button if Button does not support asChild. Keep the change localized to the Open
live dashboard control and preserve the current icon/text.
apps/web/src/lib/admin/social-account-profile.ts (1)

871-910: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated public-recovery field set across retry_progress and top-level SocialAccountCommentsRunProgress.

The six fields public_comments_recovery_pending_target_count, public_comments_recovery_pending_target_source_ids, public_recovery_bucket, public_comments_approval_required_target_count, public_comments_approval_required_target_source_ids, authenticated_followup_bucket are declared identically inside retry_progress (Lines 877-882) and again at the top level (Lines 904-909), alongside largest_remaining_gaps appearing at three separate locations (828, 883). Extracting a shared type would prevent these copies from drifting.

♻️ Suggested consolidation
+type SocialAccountCommentsPublicRecoveryState = {
+  public_comments_recovery_pending_target_count?: number | null;
+  public_comments_recovery_pending_target_source_ids?: string[];
+  public_recovery_bucket?: Record<string, unknown> | null;
+  public_comments_approval_required_target_count?: number | null;
+  public_comments_approval_required_target_source_ids?: string[];
+  authenticated_followup_bucket?: Record<string, unknown> | null;
+};

Then spread it with & SocialAccountCommentsPublicRecoveryState at both call sites instead of repeating the six fields.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/admin/social-account-profile.ts` around lines 871 - 910, The
`SocialAccountCommentsRunProgress` shape repeats the same public-recovery fields
in both `retry_progress` and the top-level type, and `largest_remaining_gaps` is
also duplicated in multiple places. Extract the shared public-recovery/retry
state into a reusable type near `SocialAccountCommentsRunProgress`, then compose
it into both `retry_progress` and the top-level declaration so the field list is
defined once and can’t drift.
apps/web/src/components/admin/instagram/InstagramCatalogBackfillMockupView.tsx (1)

646-663: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider the shared live-resource polling utility for consistency.

This component implements its own manual polling coordinator (interval + in-flight ref + request-id ref) that mirrors what a shared live-resource polling utility introduced elsewhere in this PR appears designed to centralize. Consolidating would reduce duplicated lifecycle/race-guard logic across admin dashboards.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/components/admin/instagram/InstagramCatalogBackfillMockupView.tsx`
around lines 646 - 663, The InstagramCatalogBackfillMockupView polling effect is
duplicating the same interval, cancellation, and in-flight/request-id
coordination that the shared live-resource polling utility is meant to
centralize. Replace the manual useEffect-based polling in this component with
the shared polling helper/hook used elsewhere in the PR, wiring it to
refreshDashboard and the existing access/loading conditions so the lifecycle and
race-guard logic live in one place.
apps/web/src/lib/server/admin/portless-status.ts (1)

111-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider asserting the new wordle route entry in tests.

PORTLESS_STATUS_ROUTES now includes a wordle entry, so buildPortlessStatusSnapshot will emit a 4th route object. The existing tests only assert the admin.trr wildcard case; adding a quick assertion for the wordle entry (present/absent based on portless list output) would guard this addition against regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/server/admin/portless-status.ts` around lines 111 - 115,
Update the tests around buildPortlessStatusSnapshot to cover the new wordle
route in PORTLESS_STATUS_ROUTES, since it now emits a 4th route object. Add an
assertion in the existing portless-status test coverage that verifies the wordle
entry is included or omitted based on the portless list input, alongside the
current admin.trr wildcard checks. Use the buildPortlessStatusSnapshot and
PORTLESS_STATUS_ROUTES symbols to locate the relevant test cases.
apps/web/src/proxy.ts (2)

1067-1070: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Dead isApiPath check inside strictHostRouting block.

The new early return at Line 1067-1069 (if (isApiPath(pathname)) return NextResponse.next();) unconditionally exits for every /api//api/* path before execution ever reaches the strictHostRouting block. This makes the isApiPath(pathname) check at Line 1083-1085 unreachable dead code — it can never evaluate to true anymore.

♻️ Proposed cleanup
   if (strictHostRouting && onCanonicalAdminHost && !onAdminUiPath) {
-    if (isApiPath(pathname)) {
-      return NextResponse.next();
-    }
     if (!adminOrigin) {
       return NextResponse.json({ error: "Admin origin is not configured." }, { status: 403 });
     }
     return NextResponse.redirect(new URL("/", adminOrigin), 307);
   }

Also applies to: 1082-1090

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/proxy.ts` around lines 1067 - 1070, The `isApiPath(pathname)`
branch inside `strictHostRouting` in `proxy.ts` is now unreachable because the
earlier `/api` early return already handles those paths. Remove the dead check
from the `strictHostRouting` block and keep the API bypass only in the existing
top-level guard so the control flow stays consistent and the duplicate condition
is eliminated.

1092-1095: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid hardcoding the Portless admin host here

admin.trr.localhost is already centralized as PORTLESS_ADMIN_ORIGIN in admin-url-defaults.ts; derive the host from that shared constant or add a shared host helper so this check stays in sync with the canonical Portless admin URL.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/proxy.ts` around lines 1092 - 1095, The redirect check in
proxy.ts hardcodes the Portless admin host, so update the onCanonicalAdminHost /
canonicalAdminHost logic to derive the hostname from the shared
PORTLESS_ADMIN_ORIGIN in admin-url-defaults.ts or a shared helper instead of
comparing against a literal. Keep the existing NextResponse.redirect behavior,
but make the host check reuse the centralized admin URL source so it stays
aligned with the canonical Portless admin origin.
scripts/test-vercel-guard.sh (1)

107-173: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

No coverage for the implicit-deploy production bypass.

All production-targeting cases here explicitly include the deploy token (deploy --prod, deploy --target=production variants). None exercise a bare "$2" --prod (Vercel's implicit-deploy form, no deploy token) which — per the companion review comment on scripts/vercel.sh — currently bypasses vercel_deploy_targets_production entirely. Once that's fixed, add a case like:

run_fail_contains \
  "implicit deploy production opt-in required" \
  "TRR_VERCEL_ALLOW_PROD=1" \
  bash -c 'cd "$1" && TRR_VERCEL_PROJECT_GUARD="$3" TRR_VERCEL_GUARD_ONLY=1 "$2" --prod' \
  _ "$APP_ROOT" "$VERCEL_SH" "$FAKE_GUARD"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/test-vercel-guard.sh` around lines 107 - 173, The test coverage in
test-vercel-guard.sh misses Vercel’s implicit production deploy form, where the
command is invoked as "$2" --prod without an explicit deploy token. Add a guard
assertion in the existing production-targeting section using run_fail_contains
with the same TRR_VERCEL_PROJECT_GUARD and TRR_VERCEL_GUARD_ONLY setup, and
point it at the "$2" --prod path so it verifies the production opt-in
requirement is enforced for implicit deploys too.
scripts/vercel.sh (1)

269-297: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Scope-fallback wrapper merges stdout/stderr on the success path, losing stream separation the caller relies on.

On a successful primary attempt, run_vercel_cli_with_scope_fallback captures the CLI's combined stdout+stderr (via 2>&1) into $output and re-emits it all through printf to stdout only. Callers in run_preview_ready (e.g. Line 496, 504, 512) redirect this wrapper's stdout/stderr into separate files ($web_stdout/$web_stderr, etc.), which the embedded Python script later parses for JSON (parse_json_from_cli_output). Any warnings/progress text the CLI would normally send to stderr now lands in the stdout file instead, adding noise to the JSON-boundary heuristic (raw.find("{")/raw.rfind("}")) and leaving the corresponding stderr file empty. Only the fallback (team-id) branch preserves the original stream separation.

Also applies to: 494-515

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/vercel.sh` around lines 269 - 297, The scope-fallback helper in
run_vercel_cli_with_scope_fallback is collapsing stdout and stderr on the
primary success path, which breaks callers that depend on separate streams.
Update the wrapper so run_vercel_cli output is preserved per stream: keep stdout
and stderr distinct when the primary scope attempt succeeds, and only use
combined capture for detecting the specific scope-missing error before retrying.
Make sure the retry/fallback path still works, and verify the run_preview_ready
callers and parse_json_from_cli_output continue receiving clean stdout with
warnings/progress routed to stderr.
apps/web/scripts/perf-admin-api.mjs (1)

121-129: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Unpinned autocannon version via npx --yes undermines run-to-run comparability.

Each invocation resolves whatever autocannon version npx fetches at runtime. For a script meant to track latency regressions over time, an unpinned dependency version can introduce noise unrelated to the code under test. Consider pinning, e.g. npx --yes autocannon@<version>, mirroring how run_vercel_cli pins the Vercel CLI version elsewhere in this PR.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/scripts/perf-admin-api.mjs` around lines 121 - 129, The autocannon
invocation used by the perf admin script is not version-pinned, so each run may
use a different runtime release and skew latency comparisons. Update the
spawnSync call that launches npx/autocannon in the perf admin flow to use a
fixed autocannon version, matching the pinned-tool pattern used by
run_vercel_cli elsewhere in the PR, so repeated benchmark runs remain
comparable.
apps/web/src/app/api/admin/trr-api/social/profiles/[platform]/[handle]/catalog/runs/[runId]/progress/route.ts (1)

102-129: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Rename fast_progress_timeout The fast and normal paths share SOCIAL_PROXY_PROGRESS_TIMEOUT_MS, so this reason no longer reflects a special fast timeout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/app/api/admin/trr-api/social/profiles/`[platform]/[handle]/catalog/runs/[runId]/progress/route.ts
around lines 102 - 129, The degraded progress reason used in the route handler
is misleading because the fast and normal paths both use
SOCIAL_PROXY_PROGRESS_TIMEOUT_MS, so fast_progress_timeout no longer represents
a distinct timeout case. Update the progress route logic around isFastPoll,
buildDegradedProgressPayload, and markProgressDegraded to use a more accurate
reason string for this fallback, and ensure any associated consumers or tests
are updated to match the new, generic timeout/degraded naming.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ca60f9ba-ebfb-4333-b77b-6b2c0decec24

📥 Commits

Reviewing files that changed from the base of the PR and between e5bc156 and 048473c.

⛔ Files ignored due to path filters (2)
  • apps/web/src/lib/admin/api-references/generated/inventory.ts is excluded by !**/generated/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (106)
  • apps/web/README.md
  • apps/web/next.config.ts
  • apps/web/package.json
  • apps/web/playwright.config.ts
  • apps/web/scripts/perf-admin-api.mjs
  • apps/web/src/app/admin/dev-dashboard/_components/DevDashboardShell.tsx
  • apps/web/src/app/admin/dev-dashboard/instagram-catalog-backfill-mockup/page.tsx
  • apps/web/src/app/admin/dev-dashboard/instagram-comments-mockup/page.tsx
  • apps/web/src/app/admin/dev-dashboard/page.tsx
  • apps/web/src/app/admin/page.tsx
  • apps/web/src/app/admin/social/page.tsx
  • apps/web/src/app/admin/social/reddit/[communitySlug]/[showSlug]/s[seasonNumber]/[windowKey]/[detailSlug]/page.tsx
  • apps/web/src/app/admin/social/reddit/[communitySlug]/[showSlug]/s[seasonNumber]/[windowKey]/page.tsx
  • apps/web/src/app/admin/social/reddit/[communitySlug]/[showSlug]/s[seasonNumber]/[windowKey]/post/[postId]/page.tsx
  • apps/web/src/app/admin/social/reddit/[communitySlug]/[showSlug]/s[seasonNumber]/page.tsx
  • apps/web/src/app/admin/trr-shows/[showId]/page.tsx
  • apps/web/src/app/admin/trr-shows/page.tsx
  • apps/web/src/app/api/admin/social/media-queue/snapshots/route.ts
  • apps/web/src/app/api/admin/trr-api/social-growth/calls/[callId]/route.ts
  • apps/web/src/app/api/admin/trr-api/social-growth/cookies/health/route.ts
  • apps/web/src/app/api/admin/trr-api/social-growth/history/route.ts
  • apps/web/src/app/api/admin/trr-api/social/ingest/media-mirror/drain-account/route.ts
  • apps/web/src/app/api/admin/trr-api/social/ingest/media-mirror/recover-stale/route.ts
  • apps/web/src/app/api/admin/trr-api/social/profiles/[platform]/[handle]/catalog/runs/[runId]/progress/route.ts
  • apps/web/src/app/api/admin/trr-api/social/profiles/[platform]/[handle]/catalog/runs/recent/route.ts
  • apps/web/src/app/api/admin/trr-api/social/profiles/[platform]/[handle]/comments/runs/[runId]/authenticated-followup/route.ts
  • apps/web/src/app/api/admin/trr-api/social/profiles/[platform]/[handle]/comments/runs/[runId]/public-recovery/route.ts
  • apps/web/src/app/api/admin/trr-api/social/profiles/[platform]/[handle]/comments/runs/[runId]/repair-auth/route.ts
  • apps/web/src/app/api/admin/trr-api/social/profiles/[platform]/[handle]/completion-summary/route.ts
  • apps/web/src/app/api/admin/trr-api/social/profiles/[platform]/[handle]/live-profile-total/route.ts
  • apps/web/src/app/api/admin/trr-api/social/profiles/[platform]/[handle]/snapshot/route.ts
  • apps/web/src/app/auth/finish/page.tsx
  • apps/web/src/app/layout.tsx
  • apps/web/src/app/social/[platform]/[handle]/catalog/alt-1/page.tsx
  • apps/web/src/components/admin/AdminGlobalHeader.tsx
  • apps/web/src/components/admin/SocialAccountProfileHashtagTimelineChart.tsx
  • apps/web/src/components/admin/SocialAccountProfilePage.tsx
  • apps/web/src/components/admin/cast-content-section.tsx
  • apps/web/src/components/admin/cast-socialblade-comparison.tsx
  • apps/web/src/components/admin/design-system/BrandFontMatchesPanel.tsx
  • apps/web/src/components/admin/instagram/InstagramCatalogBackfillMockupView.tsx
  • apps/web/src/components/admin/instagram/InstagramCommentsPanel.tsx
  • apps/web/src/components/admin/season-social-analytics-section.tsx
  • apps/web/src/components/admin/social-growth-section.tsx
  • apps/web/src/components/admin/social-week/WeekDetailPageView.tsx
  • apps/web/src/components/ui/alert.tsx
  • apps/web/src/components/ui/badge.tsx
  • apps/web/src/components/ui/card.tsx
  • apps/web/src/components/ui/input.tsx
  • apps/web/src/components/ui/separator.tsx
  • apps/web/src/lib/admin/admin-url-defaults.ts
  • apps/web/src/lib/admin/api-references/generator.ts
  • apps/web/src/lib/admin/shared-live-resource.ts
  • apps/web/src/lib/admin/social-account-profile.ts
  • apps/web/src/lib/admin/social-timezone.ts
  • apps/web/src/lib/data/shows.ts
  • apps/web/src/lib/fonts/brand-fonts/scoring-profiles.ts
  • apps/web/src/lib/fonts/brand-fonts/scoring.ts
  • apps/web/src/lib/server/admin/dev-dashboard-service.ts
  • apps/web/src/lib/server/admin/portless-status.ts
  • apps/web/src/lib/server/postgres.ts
  • apps/web/src/lib/trr-app-flags.ts
  • apps/web/src/proxy.ts
  • apps/web/tests/admin-api-references-generator.test.ts
  • apps/web/tests/admin-host-middleware.test.ts
  • apps/web/tests/admin-portless-status.test.ts
  • apps/web/tests/admin-social-page-auth-bypass.test.tsx
  • apps/web/tests/brand-font-matching-scoring.test.ts
  • apps/web/tests/cast-socialblade-snapshot-wiring.test.ts
  • apps/web/tests/covered-shows-page-no-fanout-wiring.test.ts
  • apps/web/tests/dev-dashboard-vercel-preview-readiness-service.test.ts
  • apps/web/tests/e2e/admin-cast-tabs-smoke.spec.ts
  • apps/web/tests/e2e/admin-fixtures.ts
  • apps/web/tests/finish.flow.test.tsx
  • apps/web/tests/instagram-catalog-backfill-mockup-page.test.tsx
  • apps/web/tests/postgres-connection-string-resolution.test.ts
  • apps/web/tests/prefixed-pathname.test.ts
  • apps/web/tests/public-route-boundary.test.ts
  • apps/web/tests/shared-env-contract.test.ts
  • apps/web/tests/shared-live-resource-polling.test.tsx
  • apps/web/tests/show-admin-routes.test.ts
  • apps/web/tests/show-refresh-health-center-wiring.test.ts
  • apps/web/tests/shows-route-config.test.ts
  • apps/web/tests/social-account-catalog-alt-page.test.ts
  • apps/web/tests/social-account-catalog-recent-runs-route.test.ts
  • apps/web/tests/social-account-catalog-run-progress-route.test.ts
  • apps/web/tests/social-account-hashtag-timeline.runtime.test.tsx
  • apps/web/tests/social-account-live-profile-total-route.test.ts
  • apps/web/tests/social-account-profile-backfill-command.test.ts
  • apps/web/tests/social-account-profile-catalog-diagnostics.test.ts
  • apps/web/tests/social-account-profile-completion-summary-route.test.ts
  • apps/web/tests/social-account-profile-page.hashtag-preview.test.ts
  • apps/web/tests/social-account-profile-page.runtime.test.tsx
  • apps/web/tests/social-account-profile-route.test.ts
  • apps/web/tests/social-account-profile-snapshot-route.test.ts
  • apps/web/tests/social-growth-cookie-health-route.test.ts
  • apps/web/tests/social-ingest-media-mirror-drain-account-route.test.ts
  • apps/web/tests/social-ingest-media-mirror-recover-route.test.ts
  • apps/web/tests/social-media-queue-snapshots-route.test.ts
  • apps/web/tests/social-timezone.test.ts
  • apps/web/tests/trr-app-flags.test.ts
  • package.json
  • pnpm-workspace.yaml
  • scripts/portless-banner.mjs
  • scripts/test-vercel-guard.sh
  • scripts/vercel.sh

Comment on lines +861 to +876
const ready =
snapshot?.status === "ok" &&
serviceReady &&
appRouteReady &&
adminRouteReady &&
apiRouteReady &&
snapshot.uses_static_aliases !== true;
const blockers = [
service?.installed === false ? "Portless service is not installed" : null,
service?.proxy_on_443 === false ? "Portless secure proxy is not responding" : null,
service?.wildcard === false ? "Wildcard routing is off" : null,
!appRouteReady ? "TRR app route is missing" : null,
!apiRouteReady ? "TRR API route is missing" : null,
snapshot?.uses_static_aliases ? "Static aliases are still active" : null,
snapshot?.status === "unavailable" ? snapshot.raw_error || "Portless route list is unavailable" : null,
].filter((item): item is string => Boolean(item));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

adminRouteReady gates readiness but has no matching blocker message.

ready requires adminRouteReady to be true (line 866), but the blockers array never adds a message when adminRouteReady is false. If every other check passes and only the admin route is missing, ready becomes false ("Blocked" badge) while blockers.length === 0, so the "What is blocking Browser" explanation section (which is gated on blockers.length > 0) never renders — leaving the admin with a "Blocked" badge and no explanation.

🐛 Proposed fix
   const blockers = [
     service?.installed === false ? "Portless service is not installed" : null,
     service?.proxy_on_443 === false ? "Portless secure proxy is not responding" : null,
     service?.wildcard === false ? "Wildcard routing is off" : null,
     !appRouteReady ? "TRR app route is missing" : null,
+    !adminRouteReady ? "Admin route is missing" : null,
     !apiRouteReady ? "TRR API route is missing" : null,
     snapshot?.uses_static_aliases ? "Static aliases are still active" : null,
     snapshot?.status === "unavailable" ? snapshot.raw_error || "Portless route list is unavailable" : null,
   ].filter((item): item is string => Boolean(item));
📝 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 ready =
snapshot?.status === "ok" &&
serviceReady &&
appRouteReady &&
adminRouteReady &&
apiRouteReady &&
snapshot.uses_static_aliases !== true;
const blockers = [
service?.installed === false ? "Portless service is not installed" : null,
service?.proxy_on_443 === false ? "Portless secure proxy is not responding" : null,
service?.wildcard === false ? "Wildcard routing is off" : null,
!appRouteReady ? "TRR app route is missing" : null,
!apiRouteReady ? "TRR API route is missing" : null,
snapshot?.uses_static_aliases ? "Static aliases are still active" : null,
snapshot?.status === "unavailable" ? snapshot.raw_error || "Portless route list is unavailable" : null,
].filter((item): item is string => Boolean(item));
const ready =
snapshot?.status === "ok" &&
serviceReady &&
appRouteReady &&
adminRouteReady &&
apiRouteReady &&
snapshot.uses_static_aliases !== true;
const blockers = [
service?.installed === false ? "Portless service is not installed" : null,
service?.proxy_on_443 === false ? "Portless secure proxy is not responding" : null,
service?.wildcard === false ? "Wildcard routing is off" : null,
!appRouteReady ? "TRR app route is missing" : null,
!adminRouteReady ? "Admin route is missing" : null,
!apiRouteReady ? "TRR API route is missing" : null,
snapshot?.uses_static_aliases ? "Static aliases are still active" : null,
snapshot?.status === "unavailable" ? snapshot.raw_error || "Portless route list is unavailable" : null,
].filter((item): item is string => Boolean(item));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/admin/dev-dashboard/page.tsx` around lines 861 - 876,
`ready` depends on `adminRouteReady`, but `blockers` in
`admin/dashboard/page.tsx` never reports that failure, which can leave the UI
blocked with no explanation. Update the `blockers` array next to `ready` so it
includes a message when `adminRouteReady` is false, matching the existing
`appRouteReady` and `apiRouteReady` checks, and keep the message consistent with
the route naming used in this section.

Comment on lines +1188 to +1211
disabled={!runIdValid || recovering}
className="min-h-10"
>
<RefreshCw aria-hidden="true" className={recovering ? "animate-spin" : ""} />
Recover stale
</Button>
<input
type="text"
value={drainAccountHandle}
onChange={(event) => onDrainAccountHandleChange(event.target.value)}
placeholder="Account"
aria-label="Drain media account"
className="min-h-10 rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm text-zinc-900 shadow-sm outline-none transition focus:border-cyan-500 focus:ring-2 focus:ring-cyan-500/20"
/>
<Button
type="button"
onClick={onDrain}
disabled={!runIdValid || draining}
className="min-h-10"
variant="secondary"
>
<RefreshCw aria-hidden="true" className={draining ? "animate-spin" : ""} />
Drain Bravo media
</Button>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Recover/Drain buttons silently no-op when adminSocialScraperTriggersEnabled is off.

MediaQueueDrilldownPanel never receives adminSocialScraperTriggersEnabled as a prop, so its disabled conditions at Lines 1188 and 1205 only check runIdValid/recovering/draining. Meanwhile recoverStaleMediaQueueJobs (Line 2974) and drainBravoMediaQueueJobs (Line 3057) both return immediately when !adminSocialScraperTriggersEnabled, without setting any error/message state. So whenever adminSocialIngestionUi is on but adminSocialScraperTriggers is off, an admin can click "Recover stale" / "Drain Bravo media" with a valid run ID and see nothing happen — no spinner, no error, no confirmation. This is especially risky during an incident where an operator might assume the action succeeded (or is pending) when it never fired. No test in admin-social-page-auth-bypass.test.tsx covers this flag combination.

🐛 Proposed fix
   const recoverStaleMediaQueueJobs = async () => {
-    if (!user || !adminSocialScraperTriggersEnabled) return;
+    if (!user) return;
+    if (!adminSocialScraperTriggersEnabled) {
+      setMediaRecoveryError("Scraper triggers are disabled in this environment.");
+      return;
+    }
     const runId = mediaRecoveryRunId.trim();
   const drainBravoMediaQueueJobs = async () => {
-    if (!user || !adminSocialScraperTriggersEnabled) return;
+    if (!user) return;
+    if (!adminSocialScraperTriggersEnabled) {
+      setMediaDrainError("Scraper triggers are disabled in this environment.");
+      return;
+    }
     const runId = mediaRecoveryRunId.trim();

And disable the buttons proactively by threading the flag into the panel:

 const MediaQueueDrilldownPanel = ({
   summary,
   runId,
   stage,
+  actionsEnabled,
   ...
 }: {
   ...
+  actionsEnabled: boolean;
   ...
 }) => {
   ...
   <Button
     type="button"
     onClick={onRecover}
-    disabled={!runIdValid || recovering}
+    disabled={!runIdValid || recovering || !actionsEnabled}
   ...
   <Button
     type="button"
     onClick={onDrain}
-    disabled={!runIdValid || draining}
+    disabled={!runIdValid || draining || !actionsEnabled}

(pass actionsEnabled={adminSocialScraperTriggersEnabled} at the call site around Line 3612).

Also applies to: 2973-3155

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/admin/social/page.tsx` around lines 1188 - 1211,
Recover/Drain currently no-op when adminSocialScraperTriggersEnabled is off
because MediaQueueDrilldownPanel doesn’t receive that flag, so its buttons stay
enabled even though recoverStaleMediaQueueJobs and drainBravoMediaQueueJobs
return immediately. Thread the flag into MediaQueueDrilldownPanel as
actionsEnabled from the call site, and use it in the disabled logic for the
“Recover stale” and “Drain Bravo media” buttons so they’re proactively disabled
whenever the actions are unavailable.

Comment on lines +2871 to +2872
const appFlags = getTrrAppFlags();
const adminSocialIngestionUiEnabled = appFlags.adminSocialIngestionUi;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd trr-app-flags.ts
cat -n apps/web/src/lib/trr-app-flags.ts 2>/dev/null | sed -n '1,140p'

Repository: therealityreport/trr-app

Length of output: 3481


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the page and flag helper usage
ast-grep outline apps/web/src/app/admin/social/page.tsx --view expanded || true
printf '\n--- page snippet ---\n'
sed -n '1,220p' apps/web/src/app/admin/social/page.tsx
printf '\n--- flag helper usages ---\n'
rg -n "getTrrAppFlags|getTrrAppFlagValue|TRR_APP_FLAGS|NEXT_PUBLIC_TRR_FLAG_" apps/web/src -S

Repository: therealityreport/trr-app

Length of output: 12855


🌐 Web query:

Next.js environment variables dynamic access process.env bracket notation client bundle static analyzable NEXT_PUBLIC

💡 Result:

In Next.js, environment variables prefixed with NEXT_PUBLIC_ are designed to be accessible in client-side code [1][2]. To ensure these values are correctly bundled, Next.js utilizes a static analysis process during the build phase (e.g., via Webpack's DefinePlugin or Turbopack) that scans for literal references to process.env.NEXT_PUBLIC_... and replaces them with their corresponding hard-coded values [3][4][1]. Because of this static replacement mechanism, the following limitations apply to client-side code: 1. Bracket Notation and Dynamic Access: Using dynamic keys or bracket notation (e.g., process.env[myVariable]) does not work for NEXT_PUBLIC_ variables [3][1]. The build process cannot determine the value of the key at build time, so it cannot perform the required static replacement, resulting in undefined [3][1]. 2. Destructuring: Object destructuring (e.g., const { NEXT_PUBLIC_KEY } = process.env) is generally unsupported for these variables in client bundles because the bundler cannot match the specific property access pattern required for replacement [3][1][5]. 3. Build-Time Inlining: Since the replacement happens at build time, these values are "baked" into your client bundle [1][2]. Changing an environment variable on your server or hosting platform (like Vercel) after a build will not update the client-side value; you must trigger a new build and deployment for the change to take effect [1][2]. To work around these limitations if dynamic access is strictly required, you can manually create an object in your code that maps the necessary environment variables and use that object instead of relying directly on process.env [6]. Additionally, keep in mind that non-prefixed variables are strictly server-side only and are never included in the client bundle, which is a security feature to prevent sensitive secrets from being exposed to the browser [1][2]. Top results: [3], [4], [1], [2]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf -- '--- next config files ---\n'
fd -H 'next.config.*' apps/web .
printf -- '\n--- env injection references ---\n'
rg -n "NEXT_PUBLIC_TRR_FLAG_|env:\\s*\\{|publicRuntimeConfig|runtimeConfig|process\\.env\\[" apps/web -S

printf -- '\n--- next.config excerpt ---\n'
for f in $(fd -H 'next.config.*' apps/web .); do
  echo "FILE: $f"
  sed -n '1,220p' "$f"
  echo
done

Repository: therealityreport/trr-app

Length of output: 26131


Resolve getTrrAppFlags() for client bundles

apps/web/src/lib/trr-app-flags.ts looks up process.env dynamically (env[definition.envVar]), so NEXT_PUBLIC_* values won’t be inlined in this use client page. That can leave adminSocialIngestionUi false in the browser and create a server/client mismatch. Switch to statically referenced process.env.NEXT_PUBLIC_* access or pass the resolved flags from the server.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/admin/social/page.tsx` around lines 2871 - 2872, The client
page is calling getTrrAppFlags(), but that helper resolves env vars dynamically
via env[definition.envVar], which won’t inline NEXT_PUBLIC_* values in a use
client bundle. Update the admin social page to avoid dynamic flag resolution in
the browser by either reading statically referenced process.env.NEXT_PUBLIC_*
values or passing the resolved flags down from the server, so
adminSocialIngestionUi stays consistent between server and client.

Comment on lines +17 to +21
redirect(
`/${encodeURIComponent(showSlug)}/social/reddit/${encodeURIComponent(communitySlug)}/${encodeURIComponent(
seasonNumber,
)}/${encodeURIComponent(windowKey)}/${encodeURIComponent(detailSlug)}`,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== file list ==\n'
git ls-files 'apps/web/src/app/admin/social/reddit/**' | sed -n '1,120p'

printf '\n== target file lines ==\n'
wc -l apps/web/src/app/admin/social/reddit/[communitySlug]/[showSlug]/s[seasonNumber]/[windowKey]/[detailSlug]/page.tsx
cat -n apps/web/src/app/admin/social/reddit/[communitySlug]/[showSlug]/s[seasonNumber]/[windowKey]/[detailSlug]/page.tsx | sed -n '1,120p'

printf '\n== search for social reddit route patterns ==\n'
rg -n --hidden --glob '!**/node_modules/**' 's\[seasonNumber\]|seasonNumber' apps/web/src/app/admin/social/reddit -S

Repository: therealityreport/trr-app

Length of output: 4264


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== route tree ==\n'
find apps/web/src/app/admin/social/reddit -maxdepth 6 -type f | sort | sed -n '1,200p'

printf '\n== relevant page files ==\n'
fd -a 'page.tsx' apps/web/src/app/admin/social/reddit | sed -n '1,200p'

Repository: therealityreport/trr-app

Length of output: 2611


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== no-season detail page ==\n'
cat -n apps/web/src/app/admin/social/reddit/[communitySlug]/[showSlug]/[windowKey]/[detailSlug]/page.tsx | sed -n '1,160p'

printf '\n== seasonal index page ==\n'
cat -n apps/web/src/app/admin/social/reddit/[communitySlug]/[showSlug]/s[seasonNumber]/[windowKey]/page.tsx | sed -n '1,160p'

printf '\n== non-season index page ==\n'
cat -n apps/web/src/app/admin/social/reddit/[communitySlug]/[showSlug]/[windowKey]/page.tsx | sed -n '1,160p'

printf '\n== seasonal detail page ==\n'
cat -n apps/web/src/app/admin/social/reddit/[communitySlug]/[showSlug]/s[seasonNumber]/[windowKey]/[detailSlug]/page.tsx | sed -n '1,160p'

Repository: therealityreport/trr-app

Length of output: 1950


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== public social/reddit route tree ==\n'
find apps/web/src/app -path '*/social/reddit/*' -type f | sort | sed -n '1,240p'

printf '\n== route files with season folders ==\n'
rg -n --hidden --glob '!**/node_modules/**' 's\[seasonNumber\]|\[seasonNumber\]' apps/web/src/app -S

Repository: therealityreport/trr-app

Length of output: 5988


Add the s prefix to the season segment
This redirect should build .../s${seasonNumber}/...; the current path uses just the raw season number and won’t match the seasonal route.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/app/admin/social/reddit/`[communitySlug]/[showSlug]/s[seasonNumber]/[windowKey]/[detailSlug]/page.tsx
around lines 17 - 21, The redirect in the page.tsx handler is missing the
seasonal route prefix, so it builds the wrong path. Update the redirect in the
page component to include the `s` prefix before `seasonNumber` when constructing
the social/reddit URL, keeping the other encoded segments unchanged.

Comment on lines +15 to +19
redirect(
`/${encodeURIComponent(showSlug)}/social/reddit/${encodeURIComponent(communitySlug)}/${encodeURIComponent(
seasonNumber,
)}/${encodeURIComponent(windowKey)}/${encodeURIComponent(postId)}`,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the route file and related sibling pages.
git ls-files 'apps/web/src/app/admin/social/reddit/**' | sort

printf '\n--- route references for season/window/post ---\n'
rg -n --hidden --glob '!**/node_modules/**' --glob '!**/.next/**' \
  's\$\{encodeURIComponent\(seasonNumber\)\}|seasonNumber|windowKey|postId|communitySlug|showSlug|social/reddit' \
  apps/web/src/app/admin/social/reddit

printf '\n--- files named page.tsx under this route tree ---\n'
fd -a 'page\.tsx$' apps/web/src/app/admin/social/reddit

Repository: therealityreport/trr-app

Length of output: 7834


Add the s prefix in this redirect. The target path should match the s[seasonNumber] route; without s${encodeURIComponent(seasonNumber)} this redirect points to a non-existent URL.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/app/admin/social/reddit/`[communitySlug]/[showSlug]/s[seasonNumber]/[windowKey]/post/[postId]/page.tsx
around lines 15 - 19, The redirect in the post page route is missing the
required season segment prefix, so it does not match the s[seasonNumber] route.
Update the redirect URL construction in the post page handler to include the
literal s prefix before the encoded season number, alongside the existing
showSlug, communitySlug, windowKey, and postId segments.

</div>
<span className="rounded-md bg-white px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-zinc-600 ring-1 ring-inset ring-zinc-200">
{profileLabel(breakdown.profile)}
{breakdown.profile}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Raw profile enum shown instead of human-readable label.

With profileLabel removed, this now renders breakdown.profile directly, so admins see raw values like explicit-mapping-visual or metadata-only instead of a formatted label. Consider keeping a small display-label mapping (can live alongside getScoreBreakdownMaxima in scoring-profiles.ts for reuse) rather than exposing the internal enum verbatim.

💡 Suggested fix
-        <span className="rounded-md bg-white px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-zinc-600 ring-1 ring-inset ring-zinc-200">
-          {breakdown.profile}
-        </span>
+        <span className="rounded-md bg-white px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-zinc-600 ring-1 ring-inset ring-zinc-200">
+          {getScoreBreakdownProfileLabel(breakdown.profile)}
+        </span>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/admin/design-system/BrandFontMatchesPanel.tsx` at
line 193, The profile text in BrandFontMatchesPanel is now rendering the raw
breakdown.profile enum instead of a user-friendly label. Update the display
logic in BrandFontMatchesPanel to use a human-readable label again, and add or
reuse a small profile-to-label mapping in scoring-profiles.ts alongside
getScoreBreakdownMaxima so the internal enum values like explicit-mapping-visual
or metadata-only are not shown directly to admins.

Comment on lines +133 to +161
const readFiniteNumber = (value: unknown): number | null => {
const numeric = Number(value);
return Number.isFinite(numeric) ? numeric : null;
};

const readRecord = (value: unknown): Record<string, unknown> | null => {
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : null;
};

const readString = (value: unknown): string | null => {
return typeof value === "string" && value.trim() ? value.trim() : null;
};

const readRecordNumber = (record: Record<string, unknown> | null | undefined, keys: string[]): number | null => {
if (!record) return null;
for (const key of keys) {
const value = readFiniteNumber(record[key]);
if (value !== null) return value;
}
return null;
};

const firstFiniteNumber = (...values: unknown[]): number | null => {
for (const value of values) {
const numeric = readFiniteNumber(value);
if (numeric !== null) return numeric;
}
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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

readFiniteNumber mishandles explicit null, breaking fallback chains.

Number(null) evaluates to 0 in JavaScript, so readFiniteNumber(null) returns 0 instead of null. Since many payload fields are typed number | null (e.g. post_progress.completed_posts, shard_index), this makes firstFiniteNumber treat "not yet computed" as "0" and stop looking at later fallback candidates (line 670-674), and makes shard rows with shard_index: null render as "Shard 1" instead of falling back to the row-index label (lines 428-436).

🐛 Proposed fix
 const readFiniteNumber = (value: unknown): number | null => {
-  const numeric = Number(value);
-  return Number.isFinite(numeric) ? numeric : null;
+  if (value === null || value === undefined) return null;
+  const numeric = Number(value);
+  return Number.isFinite(numeric) ? numeric : null;
 };
📝 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 readFiniteNumber = (value: unknown): number | null => {
const numeric = Number(value);
return Number.isFinite(numeric) ? numeric : null;
};
const readRecord = (value: unknown): Record<string, unknown> | null => {
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : null;
};
const readString = (value: unknown): string | null => {
return typeof value === "string" && value.trim() ? value.trim() : null;
};
const readRecordNumber = (record: Record<string, unknown> | null | undefined, keys: string[]): number | null => {
if (!record) return null;
for (const key of keys) {
const value = readFiniteNumber(record[key]);
if (value !== null) return value;
}
return null;
};
const firstFiniteNumber = (...values: unknown[]): number | null => {
for (const value of values) {
const numeric = readFiniteNumber(value);
if (numeric !== null) return numeric;
}
return null;
};
const readFiniteNumber = (value: unknown): number | null => {
if (value === null || value === undefined) return null;
const numeric = Number(value);
return Number.isFinite(numeric) ? numeric : null;
};
const readRecord = (value: unknown): Record<string, unknown> | null => {
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : null;
};
const readString = (value: unknown): string | null => {
return typeof value === "string" && value.trim() ? value.trim() : null;
};
const readRecordNumber = (record: Record<string, unknown> | null | undefined, keys: string[]): number | null => {
if (!record) return null;
for (const key of keys) {
const value = readFiniteNumber(record[key]);
if (value !== null) return value;
}
return null;
};
const firstFiniteNumber = (...values: unknown[]): number | null => {
for (const value of values) {
const numeric = readFiniteNumber(value);
if (numeric !== null) return numeric;
}
return null;
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/components/admin/instagram/InstagramCatalogBackfillMockupView.tsx`
around lines 133 - 161, `readFiniteNumber` currently treats explicit null as 0,
which breaks fallback logic in `firstFiniteNumber` and the shard labeling path.
Update `readFiniteNumber` in `InstagramCatalogBackfillMockupView` to return null
for null/undefined inputs before calling Number(), while still accepting valid
numeric strings and finite numbers. This will let `firstFiniteNumber` continue
past absent values and allow the shard row logic to fall back correctly when
`shard_index` is null.

Comment on lines +177 to +206
function stripPathQueryTemplate(raw: string): string {
let templateDepth = 0;
for (let index = 0; index < raw.length; index += 1) {
const current = raw[index];
const next = raw[index + 1];
if (current === "$" && next === "{") {
if (templateDepth === 0) {
const expression = raw.slice(index + 2);
if (
/^\s*(?:query|search|searchParams|queryParams|urlSearchParams)\b/i.test(expression) ||
/^\s*[^}]*\?\s*`?\?/.test(expression) ||
/^\s*[^}]*:\s*`?\s*`/.test(expression)
) {
return raw.slice(0, index);
}
}
templateDepth += 1;
index += 1;
continue;
}
if (current === "}" && templateDepth > 0) {
templateDepth -= 1;
continue;
}
if (templateDepth === 0 && (current === "?" || current === "#")) {
return raw.slice(0, index);
}
}
return raw;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for existing UI request call sites using ?? or ?. inside template path segments,
# which would currently be mis-truncated by stripPathQueryTemplate.
rg -nP '\$\{[^}]*\?\?[^}]*\}' apps/web/src --type=ts --type=tsx -g '!**/tests/**'
rg -nP '\$\{[^}]*\?\.[^}]*\?\?[^}]*\}' apps/web/src --type=ts --type=tsx -g '!**/tests/**'

Repository: therealityreport/trr-app

Length of output: 218


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the function and nearby normalization logic.
file='apps/web/src/lib/admin/api-references/generator.ts'
wc -l "$file"
sed -n '150,250p' "$file"

# Find call sites / related helpers.
rg -n "stripPathQueryTemplate|normalizeStringPathPattern|pathPattern" apps/web/src/lib/admin/api-references -g '*.ts'

# Search for template literals in request paths that use nullish coalescing / optional chaining.
rg -n '\$\{[^}]*\?\?[^}]*\}' apps/web/src --glob '*.ts' --glob '*.tsx' -g '!**/tests/**'
rg -n '\$\{[^}]*\?\.[^}]*\?\?[^}]*\}' apps/web/src --glob '*.ts' --glob '*.tsx' -g '!**/tests/**'

Repository: therealityreport/trr-app

Length of output: 50381


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the file structure first, then inspect only the relevant ranges.
file='apps/web/src/lib/admin/api-references/generator.ts'
ast-grep outline "$file" --view expanded

Repository: therealityreport/trr-app

Length of output: 5209


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import re

patterns = [
    (r'^\s*[^}]*\?\s*`?\?', "optional-backtick regex"),
    (r'^\s*[^}]*\?\s*`\s*\?', "required-backtick regex"),
]

samples = [
    "id ?? 'default'",
    "obj?.id ?? 'x'",
    "cond ? `?${foo}` : bar",
    "cond ? `?${foo}?` : bar",
    "cond ? ?",
    "query ? `?${foo}` : bar",
]

for sample in samples:
    print(f"SAMPLE: {sample!r}")
    for pat, name in patterns:
        print(name, bool(re.search(pat, sample)))
    print()
PY

Repository: therealityreport/trr-app

Length of output: 690


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the exact function and the surrounding normalize helper.
nl -ba apps/web/src/lib/admin/api-references/generator.ts | sed -n '170,235p'

Repository: therealityreport/trr-app

Length of output: 202


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for any code that constructs path-like templates with nullish coalescing
# so we can judge whether the alleged false positive is reachable.
rg -n '\$\{[^}]*\?\?[^}]*\}' apps/web --glob '*.ts' --glob '*.tsx' --glob '*.js' --glob '*.jsx' -g '!**/tests/**'

Repository: therealityreport/trr-app

Length of output: 35090


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='apps/web/src/lib/admin/api-references/generator.ts'

echo '--- function ---'
nl -ba "$file" | sed -n '177,220p'

echo '--- occurrences of nullish coalescing in template expressions under apps/web/src ---'
rg -n '\$\{[^}]*\?\?[^}]*\}' apps/web/src --glob '*.ts' --glob '*.tsx' -g '!**/tests/**' || true

echo '--- occurrences of optional chaining + nullish coalescing in template expressions under apps/web/src ---'
rg -n '\$\{[^}]*\?\.[^}]*\?\?[^}]*\}' apps/web/src --glob '*.ts' --glob '*.tsx' -g '!**/tests/**' || true

echo '--- occurrences of query-builder-style templates ---'
rg -n '\?\s*`?\?\$\{' apps/web/src --glob '*.ts' --glob '*.tsx' -g '!**/tests/**' || true

Repository: therealityreport/trr-app

Length of output: 219


Require a backtick in the query-builder heuristic

The second regex also matches plain ?? expressions like ${obj?.id ?? 'x'}, so stripPathQueryTemplate can truncate a legitimate path segment and drop everything after it. Require the backtick here.

Proposed fix
-          /^\s*[^}]*\?\s*`?\?/.test(expression) ||
+          /^\s*[^}]*\?\s*`\s*\?/.test(expression) ||
📝 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
function stripPathQueryTemplate(raw: string): string {
let templateDepth = 0;
for (let index = 0; index < raw.length; index += 1) {
const current = raw[index];
const next = raw[index + 1];
if (current === "$" && next === "{") {
if (templateDepth === 0) {
const expression = raw.slice(index + 2);
if (
/^\s*(?:query|search|searchParams|queryParams|urlSearchParams)\b/i.test(expression) ||
/^\s*[^}]*\?\s*`?\?/.test(expression) ||
/^\s*[^}]*:\s*`?\s*`/.test(expression)
) {
return raw.slice(0, index);
}
}
templateDepth += 1;
index += 1;
continue;
}
if (current === "}" && templateDepth > 0) {
templateDepth -= 1;
continue;
}
if (templateDepth === 0 && (current === "?" || current === "#")) {
return raw.slice(0, index);
}
}
return raw;
}
function stripPathQueryTemplate(raw: string): string {
let templateDepth = 0;
for (let index = 0; index < raw.length; index += 1) {
const current = raw[index];
const next = raw[index + 1];
if (current === "$" && next === "{") {
if (templateDepth === 0) {
const expression = raw.slice(index + 2);
if (
/^\s*(?:query|search|searchParams|queryParams|urlSearchParams)\b/i.test(expression) ||
/^\s*[^}]*\?\s*`\s*\?/.test(expression) ||
/^\s*[^}]*:\s*`?\s*`/.test(expression)
) {
return raw.slice(0, index);
}
}
templateDepth += 1;
index += 1;
continue;
}
if (current === "}" && templateDepth > 0) {
templateDepth -= 1;
continue;
}
if (templateDepth === 0 && (current === "?" || current === "#")) {
return raw.slice(0, index);
}
}
return raw;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/admin/api-references/generator.ts` around lines 177 - 206,
The query-builder heuristic in stripPathQueryTemplate is too broad and can match
plain nullish-coalescing expressions like ${obj?.id ?? 'x'}, causing valid path
content to be truncated. Tighten the second regex in stripPathQueryTemplate so
it only treats template-based query builders as matches by requiring a backtick
in that branch, while preserving the existing checks for
query/search/queryParams/urlSearchParams handling.

Comment on lines +48 to +50
function getDefaultEnv(): FlagEnv {
return typeof process === "undefined" ? EMPTY_ENV : process.env;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate consumers of the flag helpers to confirm client vs server usage
rg -n --type=ts --type=tsx -B2 -A5 'getTrrAppFlagValue|getTrrAppFlags\(' apps/web/src
rg -n '"use client"' apps/web/src/app/admin/social/page.tsx

Repository: therealityreport/trr-app

Length of output: 206


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- trr-app-flags.ts ---'
nl -ba apps/web/src/lib/trr-app-flags.ts | sed -n '1,220p'

echo '--- usages ---'
rg -n -B2 -A6 'getTrrAppFlagValue|getTrrAppFlags' apps/web/src || true

echo '--- admin/social page ---'
nl -ba apps/web/src/app/admin/social/page.tsx | sed -n '1,220p'

Repository: therealityreport/trr-app

Length of output: 227


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- trr-app-flags.ts ---'
sed -n '1,220p' apps/web/src/lib/trr-app-flags.ts

echo '--- usages ---'
rg -n -B2 -A6 'getTrrAppFlagValue|getTrrAppFlags' apps/web/src || true

echo '--- admin/social page ---'
sed -n '1,220p' apps/web/src/app/admin/social/page.tsx

Repository: therealityreport/trr-app

Length of output: 12557


Client flag lookups need literal env access. apps/web/src/app/admin/social/page.tsx is a "use client" component, so getTrrAppFlags() runs in the browser. With getDefaultEnv() returning process.env, env[definition.envVar] stays dynamic and these NEXT_PUBLIC_* flags fall back to the hardcoded defaults. Read each key with a literal process.env.NEXT_PUBLIC_... access or pass the resolved values in from the server.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/trr-app-flags.ts` around lines 48 - 50, The client-side flag
lookup is using dynamic env access, so `getTrrAppFlags()` in the `"use client"`
flow cannot reliably read `NEXT_PUBLIC_*` values. Update `getDefaultEnv()` and
the flag resolution path in `trr-app-flags.ts` so client reads use literal
`process.env.NEXT_PUBLIC_...` access (or receive resolved values from the
server) instead of `env[definition.envVar]`. Keep the fix focused around
`getDefaultEnv()` and `getTrrAppFlags()` so the browser sees the actual public
flag values.

Comment thread scripts/vercel.sh
Comment on lines +201 to +256
vercel_deploy_targets_production() {
local arg
local after_deploy=0
local option_value_for=""
local skip_next=0

for arg in "$@"; do
if [[ "$skip_next" -eq 1 ]]; then
if [[ "$after_deploy" -eq 1 && "$option_value_for" == "--target" && "$arg" == "production" ]]; then
return 0
fi
skip_next=0
option_value_for=""
continue
fi

if [[ "$after_deploy" -eq 1 ]]; then
case "$arg" in
--prod | --prod=true | --target=production)
return 0
;;
--target)
skip_next=1
option_value_for="$arg"
;;
--*=*)
continue
;;
-*)
if option_takes_value "$arg"; then
skip_next=1
option_value_for="$arg"
fi
;;
esac
continue
fi

case "$arg" in
--*=*)
continue
;;
-*)
if option_takes_value "$arg"; then
skip_next=1
option_value_for="$arg"
fi
continue
;;
esac

[[ "$arg" == "deploy" ]] && after_deploy=1
done

return 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.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files scripts/vercel.sh
wc -l scripts/vercel.sh
ast-grep outline scripts/vercel.sh --view expanded
rg -n "vercel_deploy_targets_production|first_vercel_command|option_takes_value|TRR_VERCEL_ALLOW_PROD|vercel_project_guard_required" scripts/vercel.sh
sed -n '1,220p' scripts/vercel.sh
sed -n '220,360p' scripts/vercel.sh

Repository: therealityreport/trr-app

Length of output: 9172


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '560,610p' scripts/vercel.sh
sed -n '1,220p' scripts/vercel.sh | nl -ba | sed -n '170,260p'

Repository: therealityreport/trr-app

Length of output: 1885


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '560,610p' scripts/vercel.sh
nl -ba scripts/vercel.sh | sed -n '177,256p'

Repository: therealityreport/trr-app

Length of output: 1885


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
path = Path('scripts/vercel.sh')
for i, line in enumerate(path.read_text().splitlines(), 1):
    if 177 <= i <= 256:
        print(f"{i:4d}: {line}")
PY

Repository: therealityreport/trr-app

Length of output: 2209


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "scripts/vercel\.sh|vercel\.sh|TRR_VERCEL_ALLOW_PROD|--prod|--target=production|deploy" -g '!*.min.*' .

Repository: therealityreport/trr-app

Length of output: 33971


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "scripts/vercel\.sh|vercel\.sh|TRR_VERCEL_ALLOW_PROD|--prod|--target=production|deploy" .

Repository: therealityreport/trr-app

Length of output: 33971


🌐 Web query:

Vercel CLI deploy command omit deploy subcommand --prod top-level flag documentation

💡 Result:

In the Vercel CLI, the deploy subcommand is the default command, meaning it can be omitted [1][2]. For example, running vercel in a project directory is equivalent to running vercel deploy [1]. The --prod flag is used to create a production deployment [2][3]. When you omit the deploy subcommand, you can still apply the --prod flag directly to the base vercel command to trigger a production deployment [3]. For example: - vercel --prod is equivalent to vercel deploy --prod [2][3]. By default, running vercel (or vercel deploy) without the --prod flag creates a preview deployment [3]. Using the --prod flag explicitly informs Vercel to create a production deployment, which updates your production domains upon success [3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
path = Path('scripts/vercel.sh')
for i, line in enumerate(path.read_text().splitlines(), 1):
    if 608 <= i <= 640:
        print(f"{i:4d}: {line}")
PY

Repository: therealityreport/trr-app

Length of output: 315


Production deploy guard should treat bare vercel as deploy (scripts/vercel.sh:201-256,601-605) vercel_deploy_targets_production() only starts scanning for --prod/--target=production after a literal deploy, but Vercel accepts vercel --prod as a production deploy too. That lets ./scripts/vercel.sh --prod skip the TRR_VERCEL_ALLOW_PROD check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/vercel.sh` around lines 201 - 256, The production-deploy detection in
vercel_deploy_targets_production() only begins after seeing a literal deploy
subcommand, so bare vercel invocations like vercel --prod are missed and bypass
the TRR_VERCEL_ALLOW_PROD guard. Update the argument scan in
vercel_deploy_targets_production() to treat the no-subcommand form of vercel as
equivalent to deploy when checking for production flags, while still preserving
the existing handling for deploy and option values. Make sure the logic in
scripts/vercel.sh that gates production deploys uses this updated detection
path.

@therealityreport therealityreport merged commit e722b89 into main Jul 6, 2026
4 checks passed
@therealityreport therealityreport deleted the codex/publish/trr-app/20260705-174033-clean branch July 6, 2026 00:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants