Skip to content

feat: implement metrics page and project layout with navigation tabs#61

Merged
weroperking merged 8 commits intoweroperking:mainfrom
Helal-maker:feature/metrics-page-upstream
Apr 1, 2026
Merged

feat: implement metrics page and project layout with navigation tabs#61
weroperking merged 8 commits intoweroperking:mainfrom
Helal-maker:feature/metrics-page-upstream

Conversation

@Helal-maker
Copy link
Copy Markdown
Contributor

@Helal-maker Helal-maker commented Apr 1, 2026

Summary

  • Implement metrics page with visual charts and project layout with navigation tabs
  • Enhance project initialization by prompting for project name and confirming directory overwrite
  • Fix code review findings - cleanup API fallbacks, fix WebSocket state, remove leaked content, fix broken links, sync Docker versions
  • Add initial documentation including NOTICE, README, and SELF_HOSTED files
  • Simplify bun setup by enabling caching for faster installs
  • Update MinIO images to latest stable versions
  • Refactor deploy command argument handling for clarity
  • Remove redundant WebSocket reconnection logic
  • Refactor code structure for improved readability and maintainability

Summary by CodeRabbit

  • New Features

    • Metrics dashboard with selectable periods, charts, and top-endpoints table
    • Project layout wrapper for project-scoped navigation
  • Improvements

    • Email/password login option in CLI and clearer connectivity errors
    • More resilient WebSocket reconnection and safer message handling
    • Project detail page simplified to focus core cards
    • Date formatting now returns "N/A" for missing/invalid dates
    • Setup check flow improved to reliably detect initial setup
  • Infrastructure

    • Pinned Bun CI with caching; updated Docker images, MinIO settings, and nginx healthcheck
  • Documentation

    • Added extensive specs and README guidance

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@Helal-maker
Copy link
Copy Markdown
Contributor Author

@CodeRabbit full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

Pin Bun versions in CI and Docker, refactor Docker build stages, add Metrics and ProjectLayout pages/routes, change dashboard API/env typings and setup check flow, broaden date util, refactor WebSocket reconnect, extend CLI init/login flows, add env caching and PORT, adjust metrics SQL alias, update compose/healthchecks, and add large spec docs.

Changes

Cohort / File(s) Summary
CI & Dockerfile
/.github/workflows/ci.yml, Dockerfile.project
Pin Bun to 1.3.10 and enable Bun cache in CI; bump Docker base to oven/bun:1.3.10-debian; move dependency install to a deps stage and reuse in builder.
Docker Compose & Healthchecks
docker-compose.dev.yml, docker-compose.yml, docker-compose.self-hosted.yml
Remove top-level version in dev compose; pin MinIO images and add MINIO_DATA_DIR; change nginx healthcheck to wget http://localhost/health.
Dashboard: Pages, Nav & Routing
apps/dashboard/src/pages/MetricsPage.tsx, apps/dashboard/src/layouts/AppLayout.tsx, apps/dashboard/src/routes.tsx
Add MetricsPage with period selector and multiple metrics queries; add top-level "Metrics" nav item and icon swap; introduce nested projects/:projectId routes and register ProjectLayout.
Dashboard: Project UI
apps/dashboard/src/pages/projects/ProjectLayout.tsx, apps/dashboard/src/pages/projects/ProjectDetailPage.tsx
Add ProjectLayout wrapper with tabs/outlet; remove Tabs and header/actions from ProjectDetailPage.
Dashboard: API, Setup & Types
apps/dashboard/src/lib/api.ts, apps/dashboard/src/components/auth/SetupGuard.tsx, apps/dashboard/src/vite-env.d.ts
Make VITE_API_URL required; add checkSetup() hitting /admin/auth/setup-status; wire SetupGuard to use checkSetup().
Dashboard: Utilities
apps/dashboard/src/lib/utils.ts
Broaden formatDate param to accept `null
Client: IaC WebSocket provider
packages/client/src/iac/provider.tsx
Refactor WS to on-demand connect() with exponential backoff, stronger cleanup (isCleaned, clear timers), onerror logging and safe JSON parsing.
Server: Env & Auth
packages/server/src/lib/env.ts, packages/server/src/lib/auth.ts
Add PORT (default "3000") to env schema and memoize validated env; use validateEnv() for JWT secret retrieval.
Server: Routes
packages/server/src/routes/admin/auth.ts, packages/server/src/routes/admin/metrics.ts, packages/server/src/routes/device/index.ts
Add GET /admin/auth/setup-status returning 404 when no admin users; rename SQL alias countrequests in top-endpoints and ORDER BY accordingly; use validateEnv() to derive device URL/port.
CLI: init/login/index
packages/cli/src/commands/init.ts, packages/cli/src/commands/login.ts, packages/cli/src/index.ts
In init: prompt for project name, confirm/cleanup existing dir; in login: add credential-based runApiKeyLogin and --email/--password path and improved connection error messaging; adjust command signatures and public-skip flags.
Server: Metrics SQL
packages/server/src/routes/admin/metrics.ts
Rename aggregation alias to requests and sort by it (query-level change).
Specs & Docs
specs/CodeRabbit_Full_Codebase_review.md, specs/README.md
Add large CodeRabbit review/spec orchestration file and a comprehensive specs/README.md onboarding/architecture doc.
Miscellaneous
docker-compose.*, Dockerfile.project, .github/...
Minor compose/declarative tweaks and CI pinning/caching changes across build and orchestration configs.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant App as Dashboard App
    participant API as Server API
    participant DB as Database

    Browser->>App: Navigate to /metrics
    App->>API: GET /admin/metrics/overview
    App->>API: GET /admin/metrics/latency?period=...
    App->>API: GET /admin/metrics/timeseries?period=...
    App->>API: GET /admin/metrics/top-endpoints?period=...&limit=15
    API->>DB: Aggregate metrics queries (timeseries, latency, top endpoints)
    DB-->>API: rows/results
    API-->>App: JSON responses
    App-->>Browser: Render MetricsPage (stats, charts, table)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Feat/test suite #27 — overlaps CI Bun setup changes (pins and caching) and likely edits to .github/workflows/ci.yml.
  • Feat/cli storage setup #31 — overlaps CLI refactors affecting init, login, and index.ts command wiring.
  • Adding Ingest  #45 — overlaps env/schema changes (PORT and related validation) in packages/server/src/lib/env.ts.

Suggested labels

codex

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: implement metrics page and project layout with navigation tabs' directly matches multiple major features in the changeset: the new MetricsPage component, ProjectLayout wrapper, and tab-based navigation refactoring.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 19

Caution

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

⚠️ Outside diff range comments (1)
apps/dashboard/src/components/auth/SetupGuard.tsx (1)

10-17: ⚠️ Potential issue | 🔴 Critical

checkSetup() sends an invalid request body, breaking setup detection entirely.

The server's /admin/auth/setup endpoint requires { email, password } per Zod validation (lines 73-76 in packages/server/src/routes/admin/auth.ts). checkSetup() sends { _check: true }, which fails validation with HTTP 400. Since 400 !== 410, the function returns true, and SetupGuard (line 12) incorrectly treats this as "setup complete" and redirects to /login even when no admin exists.

Fix one of:

  1. Add a dedicated GET /admin/auth/setup-status endpoint that checks admin existence and returns a dedicated response code.
  2. Modify server to check admin existence before the Zod validation middleware.
  3. Create a separate check endpoint that bypasses body validation entirely.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/auth/SetupGuard.tsx` around lines 10 - 17,
checkSetup() currently POSTs { _check: true } to /admin/auth/setup which fails
Zod validation and makes SetupGuard mistakenly treat setup as complete; add a
dedicated GET endpoint (e.g., GET /admin/auth/setup-status) in the admin auth
routes that queries whether an admin exists (no body, before any Zod body
validation) and returns a clear status (e.g., 200 with { setup: true } or
204/404 when not set). Then update the client checkSetup() to call GET
/admin/auth/setup-status and interpret the response, leaving SetupGuard (which
calls checkSetup()) unchanged so it only redirects to /login when the new
endpoint indicates setup is complete.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/dashboard/src/lib/api.ts`:
- Around line 98-105: The checkSetup function is sending a POST with body {
_check: true } which fails server Zod validation; update checkSetup to call
`${API_BASE}/admin/auth/setup` with a GET (no body, no JSON content-type) and
keep the same status check (res.status !== 410) so the probe doesn't hit
validation, or alternatively add server-side handling for probe requests to
accept this check (e.g., allow GET or a special probe route) if you prefer
server changes; locate function checkSetup in apps/dashboard/src/lib/api.ts and
remove the JSON body and POST usage when implementing this fix.

In `@apps/dashboard/src/pages/MetricsPage.tsx`:
- Around line 62-69: The component MetricsPage uses hardcoded colors (e.g.,
"white", "#ef4444", "#fef2f2") in inline style/class strings for the period
button styling; replace those literals with the appropriate dashboard CSS
variables (e.g., var(--color-*) tokens) so the styles read from the theme
contract (update the inline style background, color, and border values in the
period === p conditional and any similar button/label styles around the other
occurrences referenced in the diff); search within the MetricsPage component for
the period/p toggle rendering and the similar block at the other occurrence and
swap each hardcoded color for the matching --color-... variable used elsewhere
in the dashboard to preserve theme consistency.
- Around line 22-46: The section queries (latency, timeseries, topEndpoints) are
being collapsed into the global isLoading and defaulted to 0/[] which hides
their pending/error states; update the three useQuery responses to also expose
and use their status flags (e.g., latency/isLoading|isFetching|isError,
timeseries/isLoading|isError, topEndpoints/isLoading|isError) instead of
immediately falling back to 0 or [] and render per-section skeletons when those
queries are pending and an EmptyState only after the query has succeeded but
returned empty data; keep the overall PageSkeleton for the overview
(metrics/metrics) but replace the inline fallbacks m/l/ts/endpoints handling so
the UI shows section-level loading (skeleton) and error states (or EmptyState
after successful empty result) for latency, timeseries, and topEndpoints.
- Around line 14-15: Replace the local useState period in the MetricsPage
component with a URL-backed value using useSearchParams: read the "period" query
param on mount, validate it against the Period union/enum (allowed values) and
default to "24h" if invalid, update the search param whenever the user selects a
different period (instead of calling setPeriod), and ensure the UI buttons that
currently call setPeriod (lines around the period button handlers) use the new
setter that updates useSearchParams; also parse/serialize the param consistently
so back/forward, reloads and shared /metrics links reflect the selected period.

In `@apps/dashboard/src/pages/projects/ProjectLayout.tsx`:
- Around line 62-63: The active tab detection uses exact match (currentPath ===
tab.href) so nested routes like /projects/:id/iac/… or
/projects/:id/users/:userId don't mark their parent tab active; update the logic
in ProjectLayout where currentPath and activeTab are computed to use
prefix/segment matching: derive the project-relative path from location.pathname
(currentPath) and set activeTab by checking tabs.find(tab =>
currentPath.startsWith(tab.href)) or by comparing the first path segment (e.g.,
split currentPath and match against tab.href segments) so parent tabs like "iac"
and "users" become active for nested routes.

In `@apps/dashboard/src/routes.tsx`:
- Around line 73-93: Project tabs are detected in ProjectLayout by exact
pathname equality (the exact check on the active tab), so adding routes like
"users/:userId" and detailed "iac/*" paths won't match any tab href and will
fall back to "overview"; update the tab-activation logic in ProjectLayout to
consider prefix matches (e.g., use pathname.startsWith(tab.href) or
react-router's matchPath against the tab href) or otherwise derive active tab by
matching the route basename (like "/projects/:projectId/users" and
"/projects/:projectId/iac") instead of strict equality, and ensure the route
entries (users/:userId and the iac/* routes) remain under the same parent so
prefix matching picks the correct "users" or "iac" tab.

In `@packages/cli/src/commands/init.ts`:
- Around line 1313-1318: The prompt always asks for a project name which ignores
a positional/CLI-provided name; update the init flow to honor
options.projectName by using it when present (non-empty) instead of calling
prompts.text: validate options.projectName with projectNameSchema (same as
projectNameInput) and only invoke prompts.text when no options.projectName was
supplied; refer to the variables projectNameInput, projectNameSchema and the CLI
options object (options.projectName) to locate where to conditionally branch and
reuse the existing validation logic.
- Around line 1324-1335: The overwrite flow in init.ts checks existingDir and
prompts overwrite but never removes existing contents, so leftover files remain;
update the block that handles overwrite (the existingDir/overwrite logic around
projectPath and projectName) to delete or empty the existing directory after the
user confirms (e.g., recursively remove projectPath contents or remove and
recreate projectPath) before proceeding with template copy; ensure errors are
handled/logged and that the code continues to the existing copy routine only
after the directory has been cleaned.

In `@packages/cli/src/commands/login.ts`:
- Around line 145-149: The POST to the wrong endpoint: change the fetch in
login.ts that posts credentials (the call assigning `res`) to use the correct
login route `/admin/auth/login` instead of `/admin/auth`; update the URL
construction using `serverUrl` so the body and headers remain the same and the
request hits the server's login handler.

In `@packages/cli/src/index.ts`:
- Around line 32-43: PUBLIC_COMMANDS is missing required canonical flags and is
inconsistent with the registered version/help options, causing "-V" to be
treated as public but not recognized; update the PUBLIC_COMMANDS array (symbol:
PUBLIC_COMMANDS) to include at minimum ["login", "init", "--version", "--help",
"-V", "-h"] and ensure the same canonical forms used when registering the
version/help options (the code that registers version as "-v, --version" and
help as "--help, -h") are reflected in PUBLIC_COMMANDS so that all public flags
(including "-V" and "-h") map to the registered options.
- Around line 566-574: The CLI currently accepts admin passwords via argv
(.option("--password <password>")) which leaks secrets; remove the password flag
and instead, inside the action handler that calls runApiKeyLogin, obtain the
password securely (first check an env var like ADMIN_PASSWORD, otherwise prompt
the user with a hidden/secure prompt or read from stdin) and then pass that
value to runApiKeyLogin({ serverUrl: opts.url, email: opts.email, password }).
Keep the existing email flag and runLoginCommand path unchanged; replace
references to opts.password in the action with the secure read (e.g., use a
promptHiddenPassword helper or process.env) so no secret is exposed on the
command line.

In `@packages/client/src/iac/provider.tsx`:
- Around line 50-54: The ws.onclose handler fails to clear the wsReady flag
leaving consumers believing a dead socket is usable; update the ws.onclose
callback (the same block that sets wsRef.current = null and schedules reconnect
via timeoutId and connect) to explicitly set wsReady to false (and optionally
clear any related ready-state refs) before scheduling the reconnect and updating
reconnectDelayMs, ensuring the state is consistent when isCleaned is false.

In `@packages/server/src/lib/env.ts`:
- Line 19: Multiple modules (device/index.ts, lib/auth.ts, lib/inngest.ts,
routes/admin/storage.ts, routes/admin/inngest.ts) are directly reading
process.env and bypassing validateEnv(), causing mismatched defaults (e.g., PORT
default 3000 vs device fallback 3001) and potential security issues; update each
module to consume the validated env object returned by validateEnv() instead of
process.env (either by passing env into functions/constructors or importing a
central validated env instance), replace direct process.env.PORT and
process.env.BETTERBASE_JWT_SECRET usages with env.PORT and
env.BETTERBASE_JWT_SECRET, and remove hardcoded fallbacks so the schema default
in validateEnv() is authoritative; ensure lib/inngest.ts and route handlers
accept or import the validated env and refactor any top-level reads to use that
validated object.

In `@specs/CodeRabbit_Full_Codebase_review.md`:
- Around line 106-127: The markdown contains nested triple-backtick fences in
the "Analysis chain" and "agent-prompt" sections which break literal blocks (the
outer ```txt block is closed by the inner ```); fix by replacing the outer
fences with a longer fence (e.g., use four backticks or another delimiter) or by
indenting the inner code blocks so they are not raw triple-backticks inside an
outer fenced block (update the blocks around the "🧩 Analysis chain" and the
agent-prompt sections to use the new fencing).
- Around line 766-780: The file contains an opaque high-entropy payload appended
after the marker "<!-- tips_end -->"; remove that entire trailer (everything
following "<!-- tips_end -->") so the file only contains intended markdown/spec
content, leaving the "<!-- tips_end -->" marker (or truncating at that marker)
and any legitimate closing markdown; ensure no binary/garbage text remains after
the marker.

In `@specs/README.md`:
- Line 645: The footer entry shows an unlinked label "Twitter" among linked
resources; update the footer line that contains "Website • [Documentation] •
[Discord] • [GitHub] • Twitter" so it matches the others by either adding the
correct Twitter URL as a markdown link (e.g., replace Twitter with
[Twitter](https://twitter.com/your_handle)) or remove the "Twitter" token
entirely if no official account exists; ensure the final string preserves the
existing delimiter style (" • ") and formatting.
- Around line 117-121: The example for listPosts uses v.optional(v.boolean())
but never imports the validator symbol; update the snippet so the validator
namespace used in the args (v) is imported alongside query (i.e., add an import
for the validator module used by v) so listPosts's args validator works (locate
the import line with query and the listPosts definition to modify).
- Around line 412-419: Change the fenced code block language from "typescript"
to "bash" for the CLI snippet in the README so the shell commands (the bb rls
add ... block) render and copy correctly; locate the fenced block containing the
lines starting with "// In your schema or via CLI" and replace the opening fence
language token "```typescript" with "```bash" while leaving the snippet content
unchanged.
- Around line 132-139: The example mutation uses validators via the symbol v
(e.g., v.string(), v.id("users")) but the file only imports mutation; update the
imports at the top to also import the validators symbol (v) so createPost can
reference v; locate the createPost definition and ensure the import line
includes v from the same module that provides validators (the same package that
exports mutation) to make v.string and v.id available.

---

Outside diff comments:
In `@apps/dashboard/src/components/auth/SetupGuard.tsx`:
- Around line 10-17: checkSetup() currently POSTs { _check: true } to
/admin/auth/setup which fails Zod validation and makes SetupGuard mistakenly
treat setup as complete; add a dedicated GET endpoint (e.g., GET
/admin/auth/setup-status) in the admin auth routes that queries whether an admin
exists (no body, before any Zod body validation) and returns a clear status
(e.g., 200 with { setup: true } or 204/404 when not set). Then update the client
checkSetup() to call GET /admin/auth/setup-status and interpret the response,
leaving SetupGuard (which calls checkSetup()) unchanged so it only redirects to
/login when the new endpoint indicates setup is complete.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ad9ecb2c-616b-4730-8975-70e936f0f94b

📥 Commits

Reviewing files that changed from the base of the PR and between 748f225 and 218fc00.

📒 Files selected for processing (38)
  • .github/workflows/ci.yml
  • Dockerfile.project
  • apps/dashboard/src/components/auth/SetupGuard.tsx
  • apps/dashboard/src/layouts/AppLayout.tsx
  • apps/dashboard/src/lib/api.ts
  • apps/dashboard/src/lib/utils.ts
  • apps/dashboard/src/pages/MetricsPage.tsx
  • apps/dashboard/src/pages/projects/ProjectDetailPage.tsx
  • apps/dashboard/src/pages/projects/ProjectLayout.tsx
  • apps/dashboard/src/routes.tsx
  • apps/dashboard/src/vite-env.d.ts
  • docker-compose.dev.yml
  • docker-compose.self-hosted.yml
  • docker-compose.yml
  • packages/cli/src/commands/init.ts
  • packages/cli/src/commands/login.ts
  • packages/cli/src/index.ts
  • packages/client/src/iac/provider.tsx
  • packages/server/src/lib/env.ts
  • packages/server/src/routes/admin/metrics.ts
  • specs/03_test_suite.md
  • specs/BETTERBASE.md
  • specs/BetterBase_Competitive_Plan.md
  • specs/BetterBase_Dashboard_Backend_Spec.md
  • specs/BetterBase_Dashboard_Frontend_Spec.md
  • specs/BetterBase_IaC_Phase2_Spec.md
  • specs/BetterBase_IaC_Phase3_Spec.md
  • specs/BetterBase_InfraAsCode_Spec.md
  • specs/BetterBase_Inngest_Dashboard_Spec.md
  • specs/BetterBase_Inngest_Spec.md
  • specs/BetterBase_Observability_Spec.docx.md
  • specs/BetterBase_SelfHosted_Spec.md
  • specs/CODEBASE_MAP.md
  • specs/CONTRIBUTING.md
  • specs/CodeRabbit_Full_Codebase_review.md
  • specs/NOTICE.md
  • specs/README.md
  • specs/SELF_HOSTED.md
💤 Files with no reviewable changes (1)
  • docker-compose.dev.yml

Comment on lines +98 to +105
export async function checkSetup(): Promise<boolean> {
const res = await fetch(`${API_BASE}/admin/auth/setup`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ _check: true }),
});
return res.status !== 410;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Root cause of setup detection bug—server validation rejects this request body.

The { _check: true } body does not satisfy the server's Zod schema ({ email: string, password: string }). This always returns a 400 validation error, never reaching the admin existence check. See related comment on SetupGuard.tsx.

Consider changing to a GET endpoint or adding server-side handling for probe requests.

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

In `@apps/dashboard/src/lib/api.ts` around lines 98 - 105, The checkSetup function
is sending a POST with body { _check: true } which fails server Zod validation;
update checkSetup to call `${API_BASE}/admin/auth/setup` with a GET (no body, no
JSON content-type) and keep the same status check (res.status !== 410) so the
probe doesn't hit validation, or alternatively add server-side handling for
probe requests to accept this check (e.g., allow GET or a special probe route)
if you prefer server changes; locate function checkSetup in
apps/dashboard/src/lib/api.ts and remove the JSON body and POST usage when
implementing this fix.

Comment on lines +22 to +46
const { data: latency } = useQuery({
queryKey: QK.metricsLatency(period),
queryFn: () => api.get<{ latency: any }>(`/admin/metrics/latency?period=${period}`),
refetchInterval: 30_000,
});

const { data: timeseries } = useQuery({
queryKey: QK.metricsTimeseries("requests", period),
queryFn: () => api.get<{ timeseries: any[] }>(`/admin/metrics/timeseries?period=${period}`),
refetchInterval: 30_000,
});

const { data: topEndpoints } = useQuery({
queryKey: QK.metricsTopEndpoints(period),
queryFn: () =>
api.get<{ endpoints: any[] }>(`/admin/metrics/top-endpoints?period=${period}&limit=15`),
refetchInterval: 30_000,
});

if (isLoading) return <PageSkeleton />;

const m = metrics?.metrics;
const l = latency?.latency;
const ts = timeseries?.timeseries ?? [];
const endpoints = topEndpoints?.endpoints ?? [];
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not collapse pending or failed section queries into zero data.

Only the overview query participates in isLoading. latency, timeseries, and topEndpoints immediately fall back to 0 or [], so a slow or failed request renders as 0ms, a blank chart, and “No endpoint data available” instead of a loading or error state. Track those query states separately; render section skeletons while pending and EmptyState only after a successful empty response.

As per coding guidelines, Every list view needs an EmptyState component. No blank pages. Loading states use skeleton components, not spinners.

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

In `@apps/dashboard/src/pages/MetricsPage.tsx` around lines 22 - 46, The section
queries (latency, timeseries, topEndpoints) are being collapsed into the global
isLoading and defaulted to 0/[] which hides their pending/error states; update
the three useQuery responses to also expose and use their status flags (e.g.,
latency/isLoading|isFetching|isError, timeseries/isLoading|isError,
topEndpoints/isLoading|isError) instead of immediately falling back to 0 or []
and render per-section skeletons when those queries are pending and an
EmptyState only after the query has succeeded but returned empty data; keep the
overall PageSkeleton for the overview (metrics/metrics) but replace the inline
fallbacks m/l/ts/endpoints handling so the UI shows section-level loading
(skeleton) and error states (or EmptyState after successful empty result) for
latency, timeseries, and topEndpoints.

Comment on lines +766 to +780
Thanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=Helal-maker/Betterbase&utm_content=8)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>

<!-- tips_end -->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 -->
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove the opaque payload appended after <!-- tips_end -->.

The high-entropy trailer starting on Line 780 is not markdown content and will contaminate any tooling or agent that reads this file as a prompt/spec.

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

In `@specs/CodeRabbit_Full_Codebase_review.md` around lines 766 - 780, The file
contains an opaque high-entropy payload appended after the marker "<!-- tips_end
-->"; remove that entire trailer (everything following "<!-- tips_end -->") so
the file only contains intended markdown/spec content, leaving the "<!--
tips_end -->" marker (or truncating at that marker) and any legitimate closing
markdown; ensure no binary/garbage text remains after the marker.

specs/README.md Outdated

**Built with ❤️ by Weroperking**

Website • [Documentation](../docs/README.md) • [Discord](https://discord.gg/R6Dm6Cgy2E) • [GitHub](https://github.com/weroperking/Betterbase) • Twitter
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Footer contains an unresolvable resource label (Twitter).

Line 645 lists Twitter without a link while all other resources are linked; this is effectively a stale/broken docs entry.

As per coding guidelines, “Do not introduce formatting regressions in docs.”

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

In `@specs/README.md` at line 645, The footer entry shows an unlinked label
"Twitter" among linked resources; update the footer line that contains "Website
• [Documentation] • [Discord] • [GitHub] • Twitter" so it matches the others by
either adding the correct Twitter URL as a markdown link (e.g., replace Twitter
with [Twitter](https://twitter.com/your_handle)) or remove the "Twitter" token
entirely if no official account exists; ensure the final string preserves the
existing delimiter style (" • ") and formatting.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

✅ Actions performed

Full review triggered.

- api.ts: checkSetup uses GET /admin/auth/setup-status instead of POST
- server auth.ts: add GET /admin/auth/setup-status endpoint
- MetricsPage.tsx: useSearchParams for period, CSS vars, per-section loading states
- ProjectLayout.tsx: prefix matching for active tab
- init.ts: honor CLI --projectName, clean directory on overwrite
- login.ts: use correct endpoint /admin/auth/login
- CLI index.ts: fix PUBLIC_COMMANDS, secure password handling
- provider.tsx: clear wsReady in onclose handler
- server env.ts: use validated env, cache results
- CodeRabbit_Full_Codebase_review.md: fix nested fences, remove trailer
- README.md: fix validator imports, change code block language
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (3)
packages/server/src/routes/admin/metrics.ts (1)

93-113: ⚠️ Potential issue | 🟠 Major

Reject invalid limit values before querying.

Line 95 still lets NaN and negative values through (Math.min(NaN, 50) is NaN), so /admin/metrics/top-endpoints?limit=foo or -1 becomes a Postgres error and escapes the handler. Validate/clamp limit before the query and return a 400 instead of relying on the global error path.

As per coding guidelines, "Route handlers must not throw — errors should be caught and return c.json({error}). The global onError handler catches the rest but shouldn't be the primary mechanism."

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

In `@packages/server/src/routes/admin/metrics.ts` around lines 93 - 113, The
top-endpoints handler (metricsRoutes.get("/top-endpoints")) currently allows NaN
or negative values for the limit derived from c.req.query("limit") which causes
a DB error; before calling pool.query (or using the limit variable), parse and
validate the query param into an integer, clamp it to the allowed range (1..50),
and if parsing fails or the value is out of range return a 400 JSON response
(e.g., c.status(400).json({ error: "invalid limit" })) instead of proceeding to
pool.query; update the code paths that use limit so they rely on this validated
value.
apps/dashboard/src/layouts/AppLayout.tsx (1)

66-74: ⚠️ Potential issue | 🟠 Major

Move the global shortcut registration out of render.

This writes window.onkeydown on every render, overwrites any other global key handler, and leaves a stale closure behind when the layout unmounts. Register the shortcut in an effect with addEventListener/removeEventListener instead.

Suggested fix
-import { useState } from "react";
+import { useEffect, useState } from "react";
@@
-	// Global ⌘K
-	if (typeof window !== "undefined") {
-		window.onkeydown = (e) => {
-			if ((e.metaKey || e.ctrlKey) && e.key === "k") {
-				e.preventDefault();
-				setCmdOpen(true);
-			}
-		};
-	}
+	useEffect(() => {
+		const handleKeyDown = (e: KeyboardEvent) => {
+			if ((e.metaKey || e.ctrlKey) && e.key === "k") {
+				e.preventDefault();
+				setCmdOpen(true);
+			}
+		};
+
+		window.addEventListener("keydown", handleKeyDown);
+		return () => window.removeEventListener("keydown", handleKeyDown);
+	}, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/layouts/AppLayout.tsx` around lines 66 - 74, The current
global shortcut registration in AppLayout sets window.onkeydown during render,
overwriting other handlers and causing stale closures; move this logic into a
useEffect inside the AppLayout component, create a named handler that checks
(e.metaKey || e.ctrlKey) && e.key === "k" and calls setCmdOpen(true) after
e.preventDefault(), register it with window.addEventListener('keydown', handler)
and return a cleanup that calls window.removeEventListener('keydown', handler);
also guard for typeof window !== "undefined" and use an empty dependency array
(or include only stable refs) so the listener is added once and removed on
unmount.
apps/dashboard/src/pages/projects/ProjectDetailPage.tsx (1)

27-31: ⚠️ Potential issue | 🟠 Major

Invalidate the projects collection for these mutations.

statusMutation (line 29-30) only refreshes the detail query and omits QK.projects() invalidation. deleteMutation (line 38-40) does not invalidate any query keys. After either mutation, navigating back to /projects will display stale data if the list is cached—status changes won't appear, and deleted projects will remain visible until the cache ages out.

Suggested fix
 const statusMutation = useMutation({
 	mutationFn: (status: string) => api.patch(`/admin/projects/${projectId}`, { status }),
-	onSuccess: () => {
-		queryClient.invalidateQueries({ queryKey: QK.project(projectId!) });
+	onSuccess: async () => {
+		await Promise.all([
+			queryClient.invalidateQueries({ queryKey: QK.project(projectId!) }),
+			queryClient.invalidateQueries({ queryKey: QK.projects() }),
+		]);
 		toast.success("Project status updated");
 	},
 	onError: (err: any) => toast.error(err.message),
 });

 const deleteMutation = useMutation({
 	mutationFn: () => api.delete(`/admin/projects/${projectId}`),
-	onSuccess: () => {
+	onSuccess: async () => {
+		await queryClient.invalidateQueries({ queryKey: QK.projects() });
 		toast.success("Project deleted");
 		navigate("/projects");
 	},
 	onError: (err: any) => toast.error(err.message),
 });

Per guideline: "useMutation onSuccess must call queryClient.invalidateQueries with the relevant QK key. Stale UI after mutation is a bug."

Also applies to: 36-41

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

In `@apps/dashboard/src/pages/projects/ProjectDetailPage.tsx` around lines 27 -
31, The statusMutation and deleteMutation handlers only invalidate the project
detail query and/or nothing, causing the projects list to stay stale; update
both mutation onSuccess callbacks to call queryClient.invalidateQueries for
QK.projects() in addition to any existing invalidations (e.g., keep
QK.project(projectId!) for statusMutation), so after status changes or deletes
the projects collection cache is refreshed; locate the useMutation declarations
named statusMutation and deleteMutation and add a call to
queryClient.invalidateQueries({ queryKey: QK.projects() }) in their onSuccess
handlers.
♻️ Duplicate comments (19)
specs/README.md (4)

117-121: ⚠️ Potential issue | 🟠 Major

Missing validator import in query example (v is undefined).

Line 117 imports only query, but Line 120 uses v.optional(v.boolean()). This snippet is broken as written.

Diff fix
-import { query } from "@betterbase/core/iac"
+import { query, v } from "@betterbase/core/iac"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specs/README.md` around lines 117 - 121, The example uses the validator
helper "v" but the README import only brings in "query"; update the import to
also import the validator (referenced as v) so that v.optional and v.boolean are
defined; modify the import line to include "v" alongside "query" (the symbols to
update are the import statement and the example usage of query/handler where
v.optional(v.boolean()) is used).

132-139: ⚠️ Potential issue | 🟠 Major

Missing validator import in mutation example (v is undefined).

Line 132 imports only mutation, but Lines 136-138 use v.*. Copy-pasting this example fails.

Diff fix
-import { mutation } from "@betterbase/core/iac"
+import { mutation, v } from "@betterbase/core/iac"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specs/README.md` around lines 132 - 139, The example's import only brings in
mutation while the createPost mutation uses the validator namespace v
(v.string(), v.id()), so update the top import that currently imports mutation
to also import v from the same module (i.e., ensure the import includes both
mutation and v) so that the v.* validators used in the createPost declaration
are defined and the snippet can be copy-pasted successfully.

645-645: ⚠️ Potential issue | 🟡 Minor

Footer contains an unresolvable resource label (Twitter).

Line 645 lists Twitter without a link while adjacent resources are linked. Add the official URL or remove the token.

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

In `@specs/README.md` at line 645, The footer contains an unlinked token "Twitter"
in the line "Website • [Documentation](../docs/README.md) •
[Discord](https://discord.gg/R6Dm6Cgy2E) •
[GitHub](https://github.com/weroperking/Betterbase) • Twitter"; either replace
"Twitter" with the official Twitter URL as a markdown link (e.g.
[Twitter](https://twitter.com/your_handle)) or remove the "Twitter" token
entirely so all resources are consistent; update the string in specs/README.md
accordingly.

412-419: ⚠️ Potential issue | 🟡 Minor

Wrong code-fence language for CLI command snippet.

This block is shell, not TypeScript. Keep fence language aligned for rendering/copy behavior.

Diff fix
-```typescript
+```bash
 // In your schema or via CLI
 bb rls add \
   --table posts \
   --name users_own_posts \
   --command SELECT \
   --check "user_id = auth.uid()"
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @specs/README.md around lines 412 - 419, Replace the incorrect TypeScript
code-fence language with a shell language tag for the CLI snippet: change the
opening fence label from "typescript" to "bash" for the block that begins with
"bb rls add \ --table posts \ --name users_own_posts \ --command SELECT
--check "user_id = auth.uid()"" so the snippet is rendered and copied as a
shell command.


</details>

</blockquote></details>
<details>
<summary>packages/cli/src/commands/init.ts (2)</summary><blockquote>

`1324-1335`: _⚠️ Potential issue_ | _🟠 Major_

**Confirmed overwrite still merges with existing contents.**

After Line 1331 returns `true`, the code falls straight through to `copyIaCTemplate(projectPath)`. That helper creates directories recursively and rewrites only known template files, so extra files already in the target survive. Remove or empty `projectPath` before copying.

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @packages/cli/src/commands/init.ts around lines 1324 - 1335, When user
confirms overwrite (the overwrite variable after checking
existingDir/Bun.file(...).exists()), the code must delete or empty the target
projectPath before calling copyIaCTemplate(projectPath); update the branch where
overwrite is true to recursively remove the projectPath directory (or remove all
files within it) and recreate an empty directory (handling errors) so
copyIaCTemplate writes into a clean folder and doesn't merge with preexisting
files; reference the existingDir, overwrite, projectPath and copyIaCTemplate
symbols when making the change.


</details>

---

`1313-1318`: _⚠️ Potential issue_ | _🟠 Major_

**Honor the positional project name in IaC mode.**

Line 1314 always prompts, so `bb init my-app` still blocks interactively and breaks scripted init flows. Use `options.projectName` when present and only prompt as fallback.

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @packages/cli/src/commands/init.ts around lines 1313 - 1318, The prompt for
project name always runs; change init to prefer the CLI positional/flag value by
checking options.projectName first and only calling prompts.text when it's
undefined; validate whichever value is used with projectNameSchema.parse (i.e.,
use options.projectName -> const projectName =
projectNameSchema.parse(options.projectName) else prompt prompts.text ->
projectNameSchema.parse(projectNameInput)), and ensure the variable names
projectNameInput, projectNameSchema, and projectName are used so downstream code
remains unchanged.


</details>

</blockquote></details>
<details>
<summary>packages/server/src/lib/env.ts (1)</summary><blockquote>

`19-19`: _⚠️ Potential issue_ | _🟠 Major_

**Make `PORT` authoritative across the device flow.**

Line 19 adds `PORT` to the validated schema, but `packages/server/src/routes/device/index.ts` Line 27 still builds `verification_uri` from `process.env.PORT ?? 3001`. When `BETTERBASE_PUBLIC_URL` is unset, the server listens on 3000 while the device flow advertises 3001. Route that code through `validateEnv()` instead of reading `process.env` directly.
 
As per coding guidelines, "All env access goes through validateEnv() in src/lib/env.ts. Never access process.env directly in route handlers or lib modules outside of env.ts and db.ts."

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @packages/server/src/lib/env.ts at line 19, The device route is still reading
process.env directly when building verification_uri; replace that usage with the
validated env from validateEnv() so PORT is authoritative. In the device route
(where verification_uri is built) import and call validateEnv() and use
validateEnv().PORT (or destructure PORT) instead of process.env.PORT ?? 3001;
ensure env.ts exports PORT in the schema (z.string().default("3000")) and that
validateEnv() returns that value so the route advertises the same port the
server listens on.


</details>

</blockquote></details>
<details>
<summary>packages/client/src/iac/provider.tsx (1)</summary><blockquote>

`50-54`: _⚠️ Potential issue_ | _🟠 Major_

**Clear `wsReady` in `onclose`.**

Line 52 only mutates a ref. Because the provider publishes `ws: wsRef.current`, that does not rerender consumers, so `wsReady` stays `true` and hooks in `packages/client/src/iac/hooks.ts` Lines 93-125 can keep a dead socket during backoff. Set `wsReady(false)` before scheduling the retry.
  

Verification: inspect the close handler, the provider value, and the hook guard that depends on `wsReady`.

```shell
#!/bin/bash
rg -n -C3 'ws\.onclose|setWsReady\(false\)|value=\{\{ config, ws: wsRef\.current, wsReady|if \(!ws \|\| !wsReady\) return;' packages/client/src/iac/provider.tsx packages/client/src/iac/hooks.ts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/client/src/iac/provider.tsx` around lines 50 - 54, The ws.onclose
handler currently only clears wsRef.current and schedules reconnects, which
leaves the provider's wsReady state true; update the onclose callback in the
provider (the ws.onclose handler around wsRef, isCleaned, connect,
reconnectDelayMs, maxReconnectDelayMs) to call setWsReady(false) before
scheduling the retry so consumers re-render and hooks (that check wsReady) stop
using the dead socket; ensure setWsReady(false) runs early in the handler and
preserves the existing isCleaned guard and reconnect backoff logic.
apps/dashboard/src/pages/projects/ProjectLayout.tsx (1)

62-63: ⚠️ Potential issue | 🟡 Minor

Nested child routes resolve to the wrong tab.

The child routes in apps/dashboard/src/routes.tsx:73-92 include users/:userId and multiple iac/* entries, but activeTab only matches exact hrefs. Those pages therefore fall back to overview instead of keeping users or iac active.

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

In `@apps/dashboard/src/pages/projects/ProjectLayout.tsx` around lines 62 - 63,
The activeTab calculation only matches exact hrefs so nested routes like
users/:userId and iac/* fall back to "overview"; update the logic that sets
activeTab (currently using currentPath = location.pathname and tabs.find((tab)
=> tab.href === currentPath)...) to perform prefix or pattern matching
instead—e.g., check if currentPath.startsWith(tab.href) for tab hrefs that
represent parent routes or use a simple path-match utility to treat hrefs with
trailing "/*" as prefixes—so tabs with hrefs like "users" or "iac" remain active
for nested child routes.
specs/CodeRabbit_Full_Codebase_review.md (2)

780-780: ⚠️ Potential issue | 🟠 Major

Remove the opaque trailer after <!-- tips_end -->.

Everything appended after the marker on Line 780 is non-markdown payload. Leaving it in the file pollutes any parser or agent that consumes this document as a prompt/spec.

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

In `@specs/CodeRabbit_Full_Codebase_review.md` at line 780, The file contains a
large opaque payload appended immediately after the HTML marker <!-- tips_end
-->; remove everything following that marker (the non-markdown trailer blob) so
the document only contains valid markdown/spec content. Locate the marker string
"<!-- tips_end -->" and delete the opaque payload block that follows it (the
long base64/garbled text), or replace it with valid markdown or a clear
placeholder comment; ensure no residual binary/opaque characters remain so
parsers/agents won't ingest the payload.

106-155: ⚠️ Potential issue | 🟠 Major

Use a longer outer fence for the embedded prompt transcripts.

These outer literal blocks are still triple-backtick fences, but they contain raw triple-backtick examples inside them. Line 121, Line 401, and Line 551 terminate the surrounding block early, so the remainder stops being literal prompt text and gets parsed as real markdown/code instead. Use four backticks for the outer fence, or indent the inner examples, everywhere this pattern repeats.

Also applies to: 344-430, 533-603

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

In `@specs/CodeRabbit_Full_Codebase_review.md` around lines 106 - 155, In
specs/CodeRabbit_Full_Codebase_review.md there are outer triple-backtick fenced
blocks that contain inner triple-backtick examples which prematurely terminate
the outer fence; update each offending outer fence (the "embedded prompt
transcripts"/"Analysis chain" blocks) to use a longer fence (e.g., four
backticks ````) or alternatively indent the inner example blocks so they no
longer use raw ``` markers; scan the entire file for the same pattern (including
the duplicated occurrences noted) and fix every instance so the embedded
transcripts remain literal.
apps/dashboard/src/lib/api.ts (1)

98-105: ⚠️ Potential issue | 🔴 Critical

checkSetup() currently makes /setup unreachable.

The current server contract in packages/server/src/routes/admin/auth.ts:68-108 validates { email, password } before it checks whether setup is complete, so { _check: true } is rejected before the 410 branch can run. On top of that, this helper returns true for every non-410 response, while SetupGuard treats true as "setup is complete". The result is a redirect to /login even when no admin exists. This needs a valid probe contract, and the helper should only return true for the setup-complete status.

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

In `@apps/dashboard/src/lib/api.ts` around lines 98 - 105, checkSetup() is sending
a POST with a body that the server validates as auth payload, so the route never
reaches the 410 "setup incomplete" branch and the helper incorrectly treats any
non-410 as "setup complete"; update checkSetup to probe the setup endpoint in
the contract the server uses (no auth payload) by making a GET (remove
headers/body) to `${API_BASE}/admin/auth/setup` and only return true when the
response explicitly indicates setup is complete (e.g., res.status === 200), so
the function (checkSetup) and the consumer (SetupGuard) correctly interpret 410
as "no setup".
packages/cli/src/commands/login.ts (1)

145-149: ⚠️ Potential issue | 🔴 Critical

Wrong login endpoint.

The server exposes email/password login at /admin/auth/login, not /admin/auth. This request will 404 or hit the wrong handler.

Proposed fix
-		const res = await fetch(`${serverUrl}/admin/auth`, {
+		const res = await fetch(`${serverUrl}/admin/auth/login`, {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/login.ts` around lines 145 - 149, The POST request
in the login command is calling the wrong endpoint; update the fetch URL in the
code that constructs the request (the fetch call using serverUrl and body
JSON.stringify({ email: opts.email, password: opts.password })) to POST to
/admin/auth/login instead of /admin/auth so it hits the server's email/password
login handler; ensure the template string uses `${serverUrl}/admin/auth/login`.
apps/dashboard/src/pages/MetricsPage.tsx (4)

22-46: ⚠️ Potential issue | 🟠 Major

Section queries lack individual loading/error states.

latency, timeseries, and topEndpoints silently fall back to 0/[], so a pending or failed request renders as "0ms" or "No endpoint data" instead of a skeleton or error. Track isLoading/isError per query and render section-level skeletons.

Per coding guidelines: "Loading states use skeleton components, not spinners."

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

In `@apps/dashboard/src/pages/MetricsPage.tsx` around lines 22 - 46, The three
data queries (useQuery calls with queryKey QK.metricsLatency,
QK.metricsTimeseries("requests", period), and QK.metricsTopEndpoints) currently
fall back silently (latency?.latency, timeseries?.timeseries,
topEndpoints?.endpoints) and rely on a global isLoading, causing sections to
show "0ms" or "No endpoint data" on pending/error; update each query call to
capture its own isLoading and isError flags (e.g., { data: latency, isLoading:
latencyLoading, isError: latencyError } = useQuery(...)) and change the render
logic to show section-level skeleton components when
latencyLoading/timeseriesLoading/topEndpointsLoading are true and show
per-section error UI when latencyError/timeseriesError/topEndpointsError are
true instead of using the empty/zero fallbacks.

170-177: ⚠️ Potential issue | 🟠 Major

Hardcoded hex colors bypass theme contract.

#ef4444 and #fef2f2 won't adapt to theme changes. Use CSS variables like var(--color-danger) and var(--color-danger-muted).

 <Area
 	type="monotone"
 	dataKey="errors"
-	stroke="#ef4444"
-	fill="#fef2f2"
+	stroke="var(--color-danger)"
+	fill="var(--color-danger-muted)"
 	strokeWidth={2}
 	name="Errors"
 />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/pages/MetricsPage.tsx` around lines 170 - 177, The Area
chart in MetricsPage.tsx uses hardcoded hex colors (stroke="#ef4444" and
fill="#fef2f2") which bypass the theme; update the Area component
(dataKey="errors") to use theme CSS variables instead (e.g.,
stroke="var(--color-danger)" and fill="var(--color-danger-muted)") or your app's
token names, and add sensible fallback values if needed to ensure colors render
when variables are missing.

14-15: ⚠️ Potential issue | 🟠 Major

Period filter must use useSearchParams, not useState.

The period resets on refresh, back/forward navigation, and shared links. Per coding guidelines: "Query params synced to URL via useSearchParams. Never use useState for filter state that should be bookmarkable."

Proposed fix
-import { useState } from "react";
+import { useSearchParams } from "react-router-dom";

 type Period = "24h" | "7d" | "30d";
+const VALID_PERIODS: Period[] = ["24h", "7d", "30d"];

 export default function MetricsPage() {
-	const [period, setPeriod] = useState<Period>("24h");
+	const [searchParams, setSearchParams] = useSearchParams();
+	const rawPeriod = searchParams.get("period");
+	const period: Period = VALID_PERIODS.includes(rawPeriod as Period)
+		? (rawPeriod as Period)
+		: "24h";
+
+	const setPeriod = (p: Period) => setSearchParams({ period: p });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/pages/MetricsPage.tsx` around lines 14 - 15, The period
filter currently uses local React state (const [period, setPeriod] =
useState<Period>("24h")) which loses value on refresh/navigation; replace this
with URL-synced state using React Router's useSearchParams: read the "period"
query param inside MetricsPage, default to "24h" when absent and coerce to the
Period type, and on changes update the query param via setSearchParams instead
of calling setPeriod; update any handlers/components that reference period or
setPeriod to use the query-backed value and setter so the filter is bookmarkable
and survives navigation.

65-69: ⚠️ Potential issue | 🟡 Minor

Hardcoded "white" color.

Line 67 uses "white" which bypasses the theme contract. Use a token like var(--color-text-inverse) or var(--color-text-on-brand).

 style={{
 	background: period === p ? "var(--color-brand)" : "var(--color-surface)",
-	color: period === p ? "white" : "var(--color-text-secondary)",
+	color: period === p ? "var(--color-text-inverse)" : "var(--color-text-secondary)",
 	border: `1px solid ${period === p ? "var(--color-brand)" : "var(--color-border)"}`,
 }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/pages/MetricsPage.tsx` around lines 65 - 69, The inline
style in MetricsPage (the style object where background is set based on period
=== p) uses the hardcoded string "white" for the selected period text color;
replace that literal with the theme token (e.g. var(--color-text-inverse) or
var(--color-text-on-brand)) so the component respects the design system. Locate
the style block in MetricsPage.tsx (the style applied where background: period
=== p ? "var(--color-brand)" ...) and change the color branch that currently
returns "white" to the chosen CSS variable token, ensuring the border logic
remains unchanged.
packages/cli/src/index.ts (2)

566-574: ⚠️ Potential issue | 🟠 Major

Password via --password leaks to shell history and ps output.

Credentials on the command line are visible in ~/.bash_history, process listings, and audit logs. For headless login, read from BETTERBASE_PASSWORD env var or prompt interactively with hidden input.

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

In `@packages/cli/src/index.ts` around lines 566 - 574, The CLI currently accepts
a plaintext --password which leaks to shell history and process listings; update
the action handler for the top-level command so that instead of reading
opts.password you first read process.env.BETTERBASE_PASSWORD and only if that is
unset prompt the user interactively (hidden input) for the password, then pass
that secret into runApiKeyLogin({ serverUrl: opts.url, email: opts.email,
password })—do not read opts.password from the command-line; also remove or
deprecate the .option("--password") flag (or mark it hidden) to prevent users
from passing credentials on the command line, and keep runLoginCommand({
serverUrl: opts.url }) unchanged for the non-headless flow.

32-43: ⚠️ Potential issue | 🟡 Minor

-V remains unregistered but listed as public.

Line 78 registers version as -v, --version. Adding -V to PUBLIC_COMMANDS bypasses auth but still results in "unknown option" from Commander since -V isn't an alias for version.

Either register -V as an alias on line 78:

-.version(packageJson.version, "-v, --version", "display the CLI version")
+.version(packageJson.version, "-v, -V, --version", "display the CLI version")

Or remove -V from PUBLIC_COMMANDS.

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

In `@packages/cli/src/index.ts` around lines 32 - 43, PUBLIC_COMMANDS contains
"-V" but the CLI registers the version flag only as "-v, --version" (the version
command registration uses "-v, --version"), so "-V" is treated as unknown; fix
by either removing "-V" from the PUBLIC_COMMANDS array or registering "-V" as an
alias for the version flag in the command registration (update the version
option declaration to include "-V" alongside "-v, --version" so Commander
recognizes it).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/dashboard/src/pages/MetricsPage.tsx`:
- Around line 16-39: The queries in MetricsPage (the useQuery calls using
QK.metricsOverview, QK.metricsLatency, QK.metricsTimeseries,
QK.metricsTopEndpoints) are typed with any; define and export concrete response
interfaces (e.g., OverviewMetrics, LatencyMetrics, TimeseriesBucket[],
EndpointStats[]) that mirror the server shapes from
packages/server/src/routes/admin/metrics.ts, then replace the generic type args
on each api.get call (for example api.get<{ metrics: OverviewMetrics }>,
api.get<{ latency: LatencyMetrics }>, api.get<{ timeseries: TimeseriesBucket[]
}>, api.get<{ endpoints: EndpointStats[] }>) and update the local data variable
types accordingly so the queries and downstream usage are strongly typed.

In `@packages/cli/src/index.ts`:
- Around line 555-560: The .action handler on the branch command redundantly
falls back to process.cwd() even though .option("-p, --project-root <path>",
..., process.cwd()) already provides that default; remove the extra fallback by
using the options.projectRoot value directly when constructing projectRoot (or
pass options.projectRoot into runBranchCommand and let caller handle undefined),
referencing the branch command's .option/.action and the runBranchCommand
invocation to locate where to change.

In `@specs/README.md`:
- Line 20: Update the "Last Updated" metadata string in the README (the line
containing "Last Updated") from 2026-03-30 to the actual edit date 2026-04-01,
and ensure any rendered branding/metadata sections in the same README are
updated to match this new timestamp so the doc’s metadata remains consistent
with the PR changes.

---

Outside diff comments:
In `@apps/dashboard/src/layouts/AppLayout.tsx`:
- Around line 66-74: The current global shortcut registration in AppLayout sets
window.onkeydown during render, overwriting other handlers and causing stale
closures; move this logic into a useEffect inside the AppLayout component,
create a named handler that checks (e.metaKey || e.ctrlKey) && e.key === "k" and
calls setCmdOpen(true) after e.preventDefault(), register it with
window.addEventListener('keydown', handler) and return a cleanup that calls
window.removeEventListener('keydown', handler); also guard for typeof window !==
"undefined" and use an empty dependency array (or include only stable refs) so
the listener is added once and removed on unmount.

In `@apps/dashboard/src/pages/projects/ProjectDetailPage.tsx`:
- Around line 27-31: The statusMutation and deleteMutation handlers only
invalidate the project detail query and/or nothing, causing the projects list to
stay stale; update both mutation onSuccess callbacks to call
queryClient.invalidateQueries for QK.projects() in addition to any existing
invalidations (e.g., keep QK.project(projectId!) for statusMutation), so after
status changes or deletes the projects collection cache is refreshed; locate the
useMutation declarations named statusMutation and deleteMutation and add a call
to queryClient.invalidateQueries({ queryKey: QK.projects() }) in their onSuccess
handlers.

In `@packages/server/src/routes/admin/metrics.ts`:
- Around line 93-113: The top-endpoints handler
(metricsRoutes.get("/top-endpoints")) currently allows NaN or negative values
for the limit derived from c.req.query("limit") which causes a DB error; before
calling pool.query (or using the limit variable), parse and validate the query
param into an integer, clamp it to the allowed range (1..50), and if parsing
fails or the value is out of range return a 400 JSON response (e.g.,
c.status(400).json({ error: "invalid limit" })) instead of proceeding to
pool.query; update the code paths that use limit so they rely on this validated
value.

---

Duplicate comments:
In `@apps/dashboard/src/lib/api.ts`:
- Around line 98-105: checkSetup() is sending a POST with a body that the server
validates as auth payload, so the route never reaches the 410 "setup incomplete"
branch and the helper incorrectly treats any non-410 as "setup complete"; update
checkSetup to probe the setup endpoint in the contract the server uses (no auth
payload) by making a GET (remove headers/body) to `${API_BASE}/admin/auth/setup`
and only return true when the response explicitly indicates setup is complete
(e.g., res.status === 200), so the function (checkSetup) and the consumer
(SetupGuard) correctly interpret 410 as "no setup".

In `@apps/dashboard/src/pages/MetricsPage.tsx`:
- Around line 22-46: The three data queries (useQuery calls with queryKey
QK.metricsLatency, QK.metricsTimeseries("requests", period), and
QK.metricsTopEndpoints) currently fall back silently (latency?.latency,
timeseries?.timeseries, topEndpoints?.endpoints) and rely on a global isLoading,
causing sections to show "0ms" or "No endpoint data" on pending/error; update
each query call to capture its own isLoading and isError flags (e.g., { data:
latency, isLoading: latencyLoading, isError: latencyError } = useQuery(...)) and
change the render logic to show section-level skeleton components when
latencyLoading/timeseriesLoading/topEndpointsLoading are true and show
per-section error UI when latencyError/timeseriesError/topEndpointsError are
true instead of using the empty/zero fallbacks.
- Around line 170-177: The Area chart in MetricsPage.tsx uses hardcoded hex
colors (stroke="#ef4444" and fill="#fef2f2") which bypass the theme; update the
Area component (dataKey="errors") to use theme CSS variables instead (e.g.,
stroke="var(--color-danger)" and fill="var(--color-danger-muted)") or your app's
token names, and add sensible fallback values if needed to ensure colors render
when variables are missing.
- Around line 14-15: The period filter currently uses local React state (const
[period, setPeriod] = useState<Period>("24h")) which loses value on
refresh/navigation; replace this with URL-synced state using React Router's
useSearchParams: read the "period" query param inside MetricsPage, default to
"24h" when absent and coerce to the Period type, and on changes update the query
param via setSearchParams instead of calling setPeriod; update any
handlers/components that reference period or setPeriod to use the query-backed
value and setter so the filter is bookmarkable and survives navigation.
- Around line 65-69: The inline style in MetricsPage (the style object where
background is set based on period === p) uses the hardcoded string "white" for
the selected period text color; replace that literal with the theme token (e.g.
var(--color-text-inverse) or var(--color-text-on-brand)) so the component
respects the design system. Locate the style block in MetricsPage.tsx (the style
applied where background: period === p ? "var(--color-brand)" ...) and change
the color branch that currently returns "white" to the chosen CSS variable
token, ensuring the border logic remains unchanged.

In `@apps/dashboard/src/pages/projects/ProjectLayout.tsx`:
- Around line 62-63: The activeTab calculation only matches exact hrefs so
nested routes like users/:userId and iac/* fall back to "overview"; update the
logic that sets activeTab (currently using currentPath = location.pathname and
tabs.find((tab) => tab.href === currentPath)...) to perform prefix or pattern
matching instead—e.g., check if currentPath.startsWith(tab.href) for tab hrefs
that represent parent routes or use a simple path-match utility to treat hrefs
with trailing "/*" as prefixes—so tabs with hrefs like "users" or "iac" remain
active for nested child routes.

In `@packages/cli/src/commands/init.ts`:
- Around line 1324-1335: When user confirms overwrite (the overwrite variable
after checking existingDir/Bun.file(...).exists()), the code must delete or
empty the target projectPath before calling copyIaCTemplate(projectPath); update
the branch where overwrite is true to recursively remove the projectPath
directory (or remove all files within it) and recreate an empty directory
(handling errors) so copyIaCTemplate writes into a clean folder and doesn't
merge with preexisting files; reference the existingDir, overwrite, projectPath
and copyIaCTemplate symbols when making the change.
- Around line 1313-1318: The prompt for project name always runs; change init to
prefer the CLI positional/flag value by checking options.projectName first and
only calling prompts.text when it's undefined; validate whichever value is used
with projectNameSchema.parse (i.e., use options.projectName -> const projectName
= projectNameSchema.parse(options.projectName) else prompt prompts.text ->
projectNameSchema.parse(projectNameInput)), and ensure the variable names
projectNameInput, projectNameSchema, and projectName are used so downstream code
remains unchanged.

In `@packages/cli/src/commands/login.ts`:
- Around line 145-149: The POST request in the login command is calling the
wrong endpoint; update the fetch URL in the code that constructs the request
(the fetch call using serverUrl and body JSON.stringify({ email: opts.email,
password: opts.password })) to POST to /admin/auth/login instead of /admin/auth
so it hits the server's email/password login handler; ensure the template string
uses `${serverUrl}/admin/auth/login`.

In `@packages/cli/src/index.ts`:
- Around line 566-574: The CLI currently accepts a plaintext --password which
leaks to shell history and process listings; update the action handler for the
top-level command so that instead of reading opts.password you first read
process.env.BETTERBASE_PASSWORD and only if that is unset prompt the user
interactively (hidden input) for the password, then pass that secret into
runApiKeyLogin({ serverUrl: opts.url, email: opts.email, password })—do not read
opts.password from the command-line; also remove or deprecate the
.option("--password") flag (or mark it hidden) to prevent users from passing
credentials on the command line, and keep runLoginCommand({ serverUrl: opts.url
}) unchanged for the non-headless flow.
- Around line 32-43: PUBLIC_COMMANDS contains "-V" but the CLI registers the
version flag only as "-v, --version" (the version command registration uses "-v,
--version"), so "-V" is treated as unknown; fix by either removing "-V" from the
PUBLIC_COMMANDS array or registering "-V" as an alias for the version flag in
the command registration (update the version option declaration to include "-V"
alongside "-v, --version" so Commander recognizes it).

In `@packages/client/src/iac/provider.tsx`:
- Around line 50-54: The ws.onclose handler currently only clears wsRef.current
and schedules reconnects, which leaves the provider's wsReady state true; update
the onclose callback in the provider (the ws.onclose handler around wsRef,
isCleaned, connect, reconnectDelayMs, maxReconnectDelayMs) to call
setWsReady(false) before scheduling the retry so consumers re-render and hooks
(that check wsReady) stop using the dead socket; ensure setWsReady(false) runs
early in the handler and preserves the existing isCleaned guard and reconnect
backoff logic.

In `@packages/server/src/lib/env.ts`:
- Line 19: The device route is still reading process.env directly when building
verification_uri; replace that usage with the validated env from validateEnv()
so PORT is authoritative. In the device route (where verification_uri is built)
import and call validateEnv() and use validateEnv().PORT (or destructure PORT)
instead of process.env.PORT ?? 3001; ensure env.ts exports PORT in the schema
(z.string().default("3000")) and that validateEnv() returns that value so the
route advertises the same port the server listens on.

In `@specs/CodeRabbit_Full_Codebase_review.md`:
- Line 780: The file contains a large opaque payload appended immediately after
the HTML marker <!-- tips_end -->; remove everything following that marker (the
non-markdown trailer blob) so the document only contains valid markdown/spec
content. Locate the marker string "<!-- tips_end -->" and delete the opaque
payload block that follows it (the long base64/garbled text), or replace it with
valid markdown or a clear placeholder comment; ensure no residual binary/opaque
characters remain so parsers/agents won't ingest the payload.
- Around line 106-155: In specs/CodeRabbit_Full_Codebase_review.md there are
outer triple-backtick fenced blocks that contain inner triple-backtick examples
which prematurely terminate the outer fence; update each offending outer fence
(the "embedded prompt transcripts"/"Analysis chain" blocks) to use a longer
fence (e.g., four backticks ````) or alternatively indent the inner example
blocks so they no longer use raw ``` markers; scan the entire file for the same
pattern (including the duplicated occurrences noted) and fix every instance so
the embedded transcripts remain literal.

In `@specs/README.md`:
- Around line 117-121: The example uses the validator helper "v" but the README
import only brings in "query"; update the import to also import the validator
(referenced as v) so that v.optional and v.boolean are defined; modify the
import line to include "v" alongside "query" (the symbols to update are the
import statement and the example usage of query/handler where
v.optional(v.boolean()) is used).
- Around line 132-139: The example's import only brings in mutation while the
createPost mutation uses the validator namespace v (v.string(), v.id()), so
update the top import that currently imports mutation to also import v from the
same module (i.e., ensure the import includes both mutation and v) so that the
v.* validators used in the createPost declaration are defined and the snippet
can be copy-pasted successfully.
- Line 645: The footer contains an unlinked token "Twitter" in the line "Website
• [Documentation](../docs/README.md) • [Discord](https://discord.gg/R6Dm6Cgy2E)
• [GitHub](https://github.com/weroperking/Betterbase) • Twitter"; either replace
"Twitter" with the official Twitter URL as a markdown link (e.g.
[Twitter](https://twitter.com/your_handle)) or remove the "Twitter" token
entirely so all resources are consistent; update the string in specs/README.md
accordingly.
- Around line 412-419: Replace the incorrect TypeScript code-fence language with
a shell language tag for the CLI snippet: change the opening fence label from
"typescript" to "bash" for the block that begins with "bb rls add \ --table
posts \ --name users_own_posts \ --command SELECT \ --check \"user_id =
auth.uid()\"" so the snippet is rendered and copied as a shell command.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4438aeaa-d973-4dd2-bf01-2638a651b255

📥 Commits

Reviewing files that changed from the base of the PR and between 748f225 and 218fc00.

📒 Files selected for processing (38)
  • .github/workflows/ci.yml
  • Dockerfile.project
  • apps/dashboard/src/components/auth/SetupGuard.tsx
  • apps/dashboard/src/layouts/AppLayout.tsx
  • apps/dashboard/src/lib/api.ts
  • apps/dashboard/src/lib/utils.ts
  • apps/dashboard/src/pages/MetricsPage.tsx
  • apps/dashboard/src/pages/projects/ProjectDetailPage.tsx
  • apps/dashboard/src/pages/projects/ProjectLayout.tsx
  • apps/dashboard/src/routes.tsx
  • apps/dashboard/src/vite-env.d.ts
  • docker-compose.dev.yml
  • docker-compose.self-hosted.yml
  • docker-compose.yml
  • packages/cli/src/commands/init.ts
  • packages/cli/src/commands/login.ts
  • packages/cli/src/index.ts
  • packages/client/src/iac/provider.tsx
  • packages/server/src/lib/env.ts
  • packages/server/src/routes/admin/metrics.ts
  • specs/03_test_suite.md
  • specs/BETTERBASE.md
  • specs/BetterBase_Competitive_Plan.md
  • specs/BetterBase_Dashboard_Backend_Spec.md
  • specs/BetterBase_Dashboard_Frontend_Spec.md
  • specs/BetterBase_IaC_Phase2_Spec.md
  • specs/BetterBase_IaC_Phase3_Spec.md
  • specs/BetterBase_InfraAsCode_Spec.md
  • specs/BetterBase_Inngest_Dashboard_Spec.md
  • specs/BetterBase_Inngest_Spec.md
  • specs/BetterBase_Observability_Spec.docx.md
  • specs/BetterBase_SelfHosted_Spec.md
  • specs/CODEBASE_MAP.md
  • specs/CONTRIBUTING.md
  • specs/CodeRabbit_Full_Codebase_review.md
  • specs/NOTICE.md
  • specs/README.md
  • specs/SELF_HOSTED.md
💤 Files with no reviewable changes (1)
  • docker-compose.dev.yml

Comment on lines +16 to +39
const { data: metrics, isLoading } = useQuery({
queryKey: QK.metricsOverview(),
queryFn: () => api.get<{ metrics: any }>("/admin/metrics/overview"),
refetchInterval: 30_000,
});

const { data: latency } = useQuery({
queryKey: QK.metricsLatency(period),
queryFn: () => api.get<{ latency: any }>(`/admin/metrics/latency?period=${period}`),
refetchInterval: 30_000,
});

const { data: timeseries } = useQuery({
queryKey: QK.metricsTimeseries("requests", period),
queryFn: () => api.get<{ timeseries: any[] }>(`/admin/metrics/timeseries?period=${period}`),
refetchInterval: 30_000,
});

const { data: topEndpoints } = useQuery({
queryKey: QK.metricsTopEndpoints(period),
queryFn: () =>
api.get<{ endpoints: any[] }>(`/admin/metrics/top-endpoints?period=${period}&limit=15`),
refetchInterval: 30_000,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider typing API responses.

All four queries use any for response shapes, losing type safety. The server returns well-defined structures (see packages/server/src/routes/admin/metrics.ts). Define interfaces for OverviewMetrics, LatencyMetrics, TimeseriesBucket, and EndpointStats.

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

In `@apps/dashboard/src/pages/MetricsPage.tsx` around lines 16 - 39, The queries
in MetricsPage (the useQuery calls using QK.metricsOverview, QK.metricsLatency,
QK.metricsTimeseries, QK.metricsTopEndpoints) are typed with any; define and
export concrete response interfaces (e.g., OverviewMetrics, LatencyMetrics,
TimeseriesBucket[], EndpointStats[]) that mirror the server shapes from
packages/server/src/routes/admin/metrics.ts, then replace the generic type args
on each api.get call (for example api.get<{ metrics: OverviewMetrics }>,
api.get<{ latency: LatencyMetrics }>, api.get<{ timeseries: TimeseriesBucket[]
}>, api.get<{ endpoints: EndpointStats[] }>) and update the local data variable
types accordingly so the queries and downstream usage are strongly typed.

Comment on lines 555 to 560
branch
.argument("[project-root]", "project root directory", process.cwd())
.option("-p, --project-root <path>", "project root directory", process.cwd())
.action(async (options) => {
.action(async (options: { projectRoot?: string }) => {
const projectRoot = options.projectRoot || process.cwd();
await runBranchCommand([], projectRoot);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Redundant fallback.

Line 558 falls back to process.cwd(), but Commander already applies the default from line 556. Not a bug, just dead code.

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

In `@packages/cli/src/index.ts` around lines 555 - 560, The .action handler on the
branch command redundantly falls back to process.cwd() even though .option("-p,
--project-root <path>", ..., process.cwd()) already provides that default;
remove the extra fallback by using the options.projectRoot value directly when
constructing projectRoot (or pass options.projectRoot into runBranchCommand and
let caller handle undefined), referencing the branch command's .option/.action
and the runBranchCommand invocation to locate where to change.


*Database • Authentication • Realtime Subscriptions • Storage • Serverless Functions • Vector Search*

**Last Updated: 2026-03-30**
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Last Updated is stale for this PR’s doc change.

Line 20 says 2026-03-30, but this PR was created on 2026-04-01 and adds this README. Update the timestamp to the actual edit date to keep doc metadata trustworthy.

As per coding guidelines, “Keep ‘Last Updated’ and rendered branding sections consistent if you touch docs.”

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

In `@specs/README.md` at line 20, Update the "Last Updated" metadata string in the
README (the line containing "Last Updated") from 2026-03-30 to the actual edit
date 2026-04-01, and ensure any rendered branding/metadata sections in the same
README are updated to match this new timestamp so the doc’s metadata remains
consistent with the PR changes.

@weroperking weroperking merged commit 6051622 into weroperking:main Apr 1, 2026
1 of 3 checks passed
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