Skip to content

feat: add remix cli preview and move routes to follow remix conventions#135

Merged
mcansh merged 10 commits into
mainfrom
logan/remix-cli
Apr 4, 2026
Merged

feat: add remix cli preview and move routes to follow remix conventions#135
mcansh merged 10 commits into
mainfrom
logan/remix-cli

Conversation

@mcansh
Copy link
Copy Markdown
Owner

@mcansh mcansh commented Apr 4, 2026

  • chore: install remix cli preview and install skills
  • chore: add types for node and vite/client in tsconfig
  • feat: move routes to follow remix conventions
  • feat: move routes to follow remix conventions

Summary by CodeRabbit

  • New Features

    • Added a Remix application scaffolding command for quickly bootstrapping projects
    • Reworked authentication pages and flows (login, register, forgot/reset password)
  • Documentation

    • Added comprehensive Remix UI and project-layout guides covering components, hydration, animations, mixins, and testing
  • Developer Experience

    • Introduced import aliasing and editor/CI hooks, plus linting and pre-commit formatting integrations to improve code quality and consistency

mcansh added 4 commits April 4, 2026 01:05
Signed-off-by: Logan McAnsh <logan@mcan.sh>
Signed-off-by: Logan McAnsh <logan@mcan.sh>
Signed-off-by: Logan McAnsh <logan@mcan.sh>
Signed-off-by: Logan McAnsh <logan@mcan.sh>
Copilot AI review requested due to automatic review settings April 4, 2026 05:19
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 4, 2026

⚠️ No Changeset found

Latest commit: c0cd487

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 4, 2026

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

This PR adds agent skills and documentation for Remix layout/UI, a TypeScript bootstrap script to generate a Remix app skeleton, introduces oxlint plugins and CI/editor hooks, migrates many imports to #app//#test/ path aliases, restructures auth into modular controllers under app/controllers/auth/, and converts numerous top-level let bindings to const.

Changes

Cohort / File(s) Summary
Agent Skills & Bootstrap
.agents/skills/remix-project-layout/SKILL.md, .agents/skills/remix-project-layout/scripts/bootstrap_remix_application.ts, .agents/skills/remix-project-layout/tsconfig.json, .agents/skills/remix-ui/SKILL.md, .agents/skills/remix-ui/references/*
Adds skill docs describing project layout and UI patterns plus a CLI TypeScript bootstrap that scaffolds a Remix app; includes skill-specific tsconfig.
Auth Restructure
app/auth/controller.tsx (removed), app/controllers/auth/controller.tsx, app/controllers/auth/login/controller.tsx, app/controllers/auth/register/controller.tsx, app/controllers/auth/forgot-password/controller.tsx, app/controllers/auth/reset-password/controller.tsx, app/controllers/auth/controller.test.ts
Replaces a monolithic auth controller with modular controllers for login, register, forgot/reset password, and logout; tests updated accordingly.
Path Alias Migration
multiple app/... and #test/... files (e.g., app/controllers/*, app/components/*, app/models/*, app/utils/*, tests)
Mass conversion of relative imports to #app/ / #test/ alias imports (some with explicit .ts extensions) across many files.
Let→Const & Local Mutability
app/routes.ts, app/router.ts, app/entry.browser.ts, app/utils/session.ts, app/utils/redirect.ts, mocks/*, server.ts, tests, and various utils
Converted many module-scope let bindings to const and adjusted local loop/test variables between const/let to match new style rules.
Custom Lint Plugins
oxlint-plugins/prefer-import-alias-plugin.ts, oxlint-plugins/prefer-let-locals-plugin.ts
Adds oxlint plugins enforcing import aliases for deep relative imports and const/let placement rules; both supply fixes/suggestions.
Vite & Tooling Config
vite.config.ts, package.json, pnpm-workspace.yaml, tsconfig.json, .vscode/settings.json, .vite-hooks/pre-commit, AGENTS.md
Adds staged task runner, registers lint plugins and rules, adds #* imports map, devDependencies, workspace entries, VS Code formatter settings, pre-commit hook, and CI guidance.
Middleware / Utilities / Components
app/middleware/auth.ts, app/components/*, app/constants.ts, app/models/*, app/utils/*, app/controllers/*
Primarily import-path updates to aliases, minor refactors (let→const), small reformatting — behavior preserved except removed user helpers in app/models/user.ts.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(220,230,255,0.5)
    participant Client
    participant Router
    participant LoginController
    participant SessionStore
    participant DB
  end
  Client->>Router: POST /auth/login (credentials)
  Router->>LoginController: route -> actions.action(context)
  LoginController->>DB: verifyCredentials(email, password)
  DB-->>LoginController: user|null
  alt user found
    LoginController->>SessionStore: completeAuth(session, { userId })
    SessionStore-->>LoginController: session updated
    LoginController-->>Router: redirect to post-auth URL (with session cookie)
    Router-->>Client: 302 redirect (Set-Cookie)
  else auth failed
    LoginController->>SessionStore: flash("Invalid email or password")
    LoginController-->>Router: redirect back to login (with flashed error)
    Router-->>Client: 302 redirect (Set-Cookie)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Possibly related PRs

Suggested reviewers

  • Copilot
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.37% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the two main changes: adding Remix CLI preview support and restructuring routes to follow Remix conventions.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch logan/remix-cli

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.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces Remix CLI preview support and reorganizes application controllers/routes to better align with Remix project layout conventions, alongside adding agent “skills” documentation and a bootstrap script.

Changes:

  • Added @remix-run/cli from a preview GitHub ref and updated workspace/lockfiles.
  • Moved/rewired controllers to a convention-based app/controllers/** structure (including nested auth controllers) and updated router imports.
  • Added vp staged integration (Vite config + pre-commit hook) and expanded .agents/skills/** docs/scripts.

Reviewed changes

Copilot reviewed 31 out of 35 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
vite.config.ts Adds vp staged config (but needs fmt override paths updated for moved files).
tsconfig.json Adds Node + Vite client ambient types.
pnpm-workspace.yaml Adds catalog entry for preview @remix-run/cli.
pnpm-lock.yaml Locks preview CLI tarball and related snapshot entries.
package.json Adds @remix-run/cli dependency via catalog.
app/router.ts Updates controller import paths to new app/controllers/** layout and maps health route.
app/controllers/home/page.tsx Updates relative imports after controller move.
app/controllers/home/local-schema.ts Adds TS suppression comments for indexed ~run calls.
app/controllers/home/local-form-schema.ts Adds a local Standard Schema-compatible FormData/URLSearchParams parser/validator.
app/controllers/home/controller.tsx Updates imports to match new controller folder structure.
app/controllers/home/controller.test.ts Updates import paths after move (but mocks need specifier fixes).
app/controllers/history/not-found-page.tsx Updates relative imports after controller move.
app/controllers/history/history-page.tsx Updates relative imports after controller move.
app/controllers/history/game.tsx Updates relative imports after controller move.
app/controllers/history/controller.tsx Updates relative imports after controller move.
app/controllers/health.tsx Adds a new health endpoint action (currently builds fetch URL from Host headers).
app/controllers/auth/controller.tsx Replaces monolithic auth controller with nested sub-controllers (has unused imports).
app/controllers/auth/controller.test.ts Updates import paths after move (but mocks need specifier fixes).
app/controllers/auth/login/controller.tsx New nested login controller.
app/controllers/auth/register/controller.tsx New nested register controller.
app/controllers/auth/forgot-password/controller.tsx New nested forgot-password controller.
app/controllers/auth/reset-password/controller.tsx New nested reset-password controller.
app/auth/controller.tsx Removes the previous monolithic auth controller implementation.
AGENTS.md Adds CI integration guidance for voidzero-dev/setup-vp.
.vite-hooks/pre-commit Adds hook to run vp staged.
.agents/skills/remix-ui/SKILL.md Introduces “remix-ui” agent skill documentation.
.agents/skills/remix-ui/references/testing-patterns.md Adds UI testing reference material.
.agents/skills/remix-ui/references/mixins-styling-events.md Adds mixins/events/styling reference material.
.agents/skills/remix-ui/references/hydration-frames-navigation.md Adds hydration/frames/navigation reference material.
.agents/skills/remix-ui/references/create-mixins.md Adds mixin authoring reference material.
.agents/skills/remix-ui/references/component-model.md Adds component model reference material.
.agents/skills/remix-ui/references/animate-elements.md Adds animation reference material.
.agents/skills/remix-project-layout/tsconfig.json Adds TS config for the bootstrap script/tooling.
.agents/skills/remix-project-layout/SKILL.md Introduces “remix-project-layout” agent skill documentation.
.agents/skills/remix-project-layout/scripts/bootstrap_remix_application.ts Adds a script to scaffold a convention-based Remix app layout.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (2)

app/controllers/home/controller.test.ts:11

  • These mocks are targeting "./models/game.ts" (and similarly "./models/user.ts"), but the code under test imports from "../../models/...". Because the specifiers don’t match, the mocks won’t apply. Update the vi.mock specifiers to match the actual import paths used by app/controllers/home/controller.tsx.
    app/controllers/auth/controller.test.ts:8
  • This test mocks "./models/user.ts", but the auth/login/register controllers import the user model via different specifiers (e.g., "../../models/user.ts" or "../../../models/user"). With mismatched specifiers, the mock won’t intercept the real module. Adjust the vi.mock path(s) to match the module specifier actually imported by the code under test.

Comment thread vite.config.ts
Comment thread app/controllers/auth/controller.tsx Outdated
Comment thread app/controllers/auth/login/controller.tsx Outdated
Comment thread app/controllers/auth/register/controller.tsx
@mcansh mcansh force-pushed the logan/remix-cli branch from 7677eb7 to 35db8cc Compare April 4, 2026 05:28
Copy link
Copy Markdown
Contributor

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

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

Inline comments:
In @.agents/skills/remix-ui/references/animate-elements.md:
- Line 1: The top-level heading "## Animating Elements (`remix/component`)" must
be changed to an H1 to satisfy markdownlint MD041; replace the leading "##" with
a single "#" so the line reads "# Animating Elements (`remix/component`)" and
ensure there are no preceding blank lines before this heading.

In @.agents/skills/remix-ui/references/component-model.md:
- Line 1: The markdown heading "Component Model" is currently using a level-2
heading (`##`), violating MD041; change the opening heading token for "Component
Model" from `##` to a level-1 heading `#` so the file's top-level heading is H1
and satisfies the lint rule (MD041).

In @.agents/skills/remix-ui/references/create-mixins.md:
- Line 1: The top-level heading currently uses H2; update the opening line in
create-mixins.md from "## Creating Mixins (`remix/component`)" to a level-1
heading by changing it to "# Creating Mixins (`remix/component`)" so the file
satisfies MD041 (top-level heading required).

In @.agents/skills/remix-ui/references/hydration-frames-navigation.md:
- Line 1: The file's top-level heading uses "## Hydration" which violates MD041;
update the initial heading to use a single H1 by replacing the leading "##" with
"#" so the first line reads "# Hydration" (ensure the heading text "Hydration"
remains unchanged and only the heading level is adjusted).

In @.agents/skills/remix-ui/references/mixins-styling-events.md:
- Line 1: Replace the first-line level-2 heading with a top-level H1 by changing
the leading "##" to a single "#" for "Host Elements" (i.e., make the first line
"# Host Elements") and ensure it is the very first line in the file with no
preceding blank lines so MD041 (first-line-heading) is satisfied.

In @.agents/skills/remix-ui/references/testing-patterns.md:
- Line 1: The file's top-level heading uses "## Testing" which triggers MD041
(first heading should be H1); change the first-line heading to a single hash
(e.g., "# Testing") so the document begins with an H1, leaving the rest of the
content unchanged and ensuring the heading text remains "Testing".

In @.vite-hooks/pre-commit:
- Line 1: The pre-commit hook file .vite-hooks/pre-commit contains the correct
command (vp staged) but Git won't run it by default; either set core.hooksPath
to ".vite-hooks" in vite.config.ts or ensure the repo setup docs/Review
Checklist instruct developers to run the vp config command after cloning; modify
vite.config.ts to include core.hooksPath: ".vite-hooks" (or add a clear setup
step telling devs to run vp config) so hooks are wired automatically for new
clones.

In `@app/controllers/auth/controller.test.ts`:
- Around line 6-7: The mock specifier passed to vi.mock does not match the
actual module path used by importActual, so update the vi.mock call to target
the same module string as the importActual call: change the mock target in
vi.mock to "../../models/user.ts" so the mock binds correctly to the module
referenced by importActual (the vi.mock(..., async (importActual) => { let
actual = await importActual<typeof import("../../models/user.ts")>() }) block).

In `@app/controllers/auth/forgot-password/controller.tsx`:
- Around line 46-60: This block is rendering the live reset token into HTML (the
token variable used with routes.auth.resetPassword.index.href), which leaks
reset tokens; remove this UI path so the token is never embedded in a response —
instead return a generic success/notice message to the requester and
generate/send the reset URL server-side via email. Concretely: delete or disable
the conditional that renders the token-based link (the JSX using token and
routes.auth.resetPassword.index.href), ensure the password reset URL is produced
and emailed on the server/controller side only, and leave the page showing a
non-sensitive confirmation message like "If an account with that email exists,
you will receive reset instructions." Ensure no other code paths render the
token variable.

In `@app/controllers/auth/login/controller.tsx`:
- Around line 9-11: The controller currently imports shared schema utilities via
localSchema.parse which couples auth/login to the home controller; extract the
shared validation primitive(s) (e.g., parse) from "../../home/local-schema" into
a neutral module under app/utils/ or app/schemas/ (create an exported function
or re-export for parse), then update the auth/login controller to import parse
from the new module instead of localSchema; ensure any related types or helper
functions used by parse are moved or re-exported so the auth/login controller no
longer depends on the home controller.

In `@app/controllers/auth/register/controller.tsx`:
- Line 15: The Document component's head prop currently sets the page title to
"Login - Remix Wordle"; update the title to reflect registration (e.g.,
"Register - Remix Wordle" or "Sign Up - Remix Wordle") wherever Document is used
with head={<title>Login - Remix Wordle</title>} (both occurrences of that
Document usage), so the page shows the correct register/signup title.
- Line 118: The session is being set with the wrong shape — change the call to
session.set("auth", { auth: user.id }) so it stores { userId: user.id } instead,
because parseAuthSession expects { userId: s.string() } and the auth middleware
reads result.value.userId; update the register controller's session.set to match
the login controller's usage and ensure any downstream code that reads
session.get("auth") continues to expect userId.

In `@app/controllers/auth/reset-password/controller.tsx`:
- Line 18: The page title is incorrect in the reset-password view: update the
<title> passed to the Document component (the JSX instance Document url={url}
head={<title>...>}) to read "Reset Password - Remix Wordle" instead of "Login -
Remix Wordle"; make the same change for the second Document/title occurrence
used later in this file so both instances show the reset-password title.
- Around line 72-77: The reset flow is broken because resetPassword in
app/models/user.ts currently bypasses validation and always returns true, making
the error branch in the controller (reset-password controller using
resetPassword(params.token, password)) unreachable; restore the validation logic
in resetPassword so it verifies the token, expiry, and actually updates the user
password (return true on success, false on failure) or throw a clear error and
have the controller handle it, and update the controller to handle the real
failure case accordingly; also update the page title strings in the
reset-password controller (currently "Login" at the title locations) to "Reset
Password" so the UI reflects the correct page.

In `@app/controllers/home/controller.test.ts`:
- Around line 9-10: The vi.mock specifiers are wrong: change the module
specifiers passed to vi.mock (the "./models/..." strings) to the correct
relative paths used in the importActual generics (../../models/...), so the
mocks target the real model modules; update both vi.mock calls in
controller.test.ts (the ones mocking game and the other model referenced with
importActual) to use "../../models/<module>.ts" to match the importActual type
references.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c5de30b9-3076-4a7b-9b1a-ea718041b264

📥 Commits

Reviewing files that changed from the base of the PR and between 31108a9 and 35db8cc.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (35)
  • .agents/skills/remix-project-layout/SKILL.md
  • .agents/skills/remix-project-layout/scripts/bootstrap_remix_application.ts
  • .agents/skills/remix-project-layout/tsconfig.json
  • .agents/skills/remix-ui/SKILL.md
  • .agents/skills/remix-ui/references/animate-elements.md
  • .agents/skills/remix-ui/references/component-model.md
  • .agents/skills/remix-ui/references/create-mixins.md
  • .agents/skills/remix-ui/references/hydration-frames-navigation.md
  • .agents/skills/remix-ui/references/mixins-styling-events.md
  • .agents/skills/remix-ui/references/testing-patterns.md
  • .vite-hooks/pre-commit
  • AGENTS.md
  • app/auth/controller.tsx
  • app/controllers/auth/controller.test.ts
  • app/controllers/auth/controller.tsx
  • app/controllers/auth/forgot-password/controller.tsx
  • app/controllers/auth/login/controller.tsx
  • app/controllers/auth/register/controller.tsx
  • app/controllers/auth/reset-password/controller.tsx
  • app/controllers/health.tsx
  • app/controllers/history/controller.tsx
  • app/controllers/history/game.tsx
  • app/controllers/history/history-page.tsx
  • app/controllers/history/not-found-page.tsx
  • app/controllers/home/controller.test.ts
  • app/controllers/home/controller.tsx
  • app/controllers/home/local-form-schema.ts
  • app/controllers/home/local-schema.ts
  • app/controllers/home/page.tsx
  • app/middleware/auth.ts
  • app/router.ts
  • package.json
  • pnpm-workspace.yaml
  • tsconfig.json
  • vite.config.ts
💤 Files with no reviewable changes (1)
  • app/auth/controller.tsx

Comment thread .agents/skills/remix-ui/references/animate-elements.md
Comment thread .agents/skills/remix-ui/references/component-model.md
Comment thread .agents/skills/remix-ui/references/create-mixins.md
Comment thread .agents/skills/remix-ui/references/hydration-frames-navigation.md
Comment thread .agents/skills/remix-ui/references/mixins-styling-events.md
Comment thread app/controllers/auth/register/controller.tsx Outdated
Comment thread app/controllers/auth/register/controller.tsx
Comment thread app/controllers/auth/reset-password/controller.tsx Outdated
Comment thread app/controllers/auth/reset-password/controller.tsx Outdated
Comment thread app/controllers/home/controller.test.ts Outdated
mcansh added 5 commits April 4, 2026 01:38
Signed-off-by: Logan McAnsh <logan@mcan.sh>
Signed-off-by: Logan McAnsh <logan@mcan.sh>
Signed-off-by: Logan McAnsh <logan@mcan.sh>
Signed-off-by: Logan McAnsh <logan@mcan.sh>
Signed-off-by: Logan McAnsh <logan@mcan.sh>
Copy link
Copy Markdown
Contributor

@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 (1)
app/models/user.ts (1)

55-67: ⚠️ Potential issue | 🔴 Critical

Critical: Missing await on async function call.

Line 56 calls the async function getUserByEmail(email) without await, and createPasswordResetToken is not declared as async. This assigns a Promise<User | null> to user instead of the resolved value. The subsequent check on line 57 (if (!user)) will always be falsy because Promises are truthy objects, breaking the intended logic.

🐛 Proposed fix
-export function createPasswordResetToken(email: string): string | undefined {
-	let user = getUserByEmail(email)
+export async function createPasswordResetToken(email: string): Promise<string | undefined> {
+	let user = await getUserByEmail(email)
 	if (!user) return undefined
 
 	let token = Math.random().toString(36).substring(2, 15)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/models/user.ts` around lines 55 - 67, The createPasswordResetToken
function calls the async getUserByEmail(email) without awaiting it, so change
createPasswordResetToken to an async function, await getUserByEmail(email), and
update its return type to Promise<string | undefined>; inside the async function
use "const user = await getUserByEmail(email)" and keep the existing null check
and token generation logic (ensure callers of createPasswordResetToken are
updated to handle a Promise).
♻️ Duplicate comments (9)
app/controllers/auth/controller.test.ts (1)

6-7: ⚠️ Potential issue | 🔴 Critical

Fix vi.mock specifier mismatch (Line 6).

vi.mock("./models/user.ts") will not mock the module referenced as "../../models/user.ts" at Line 7. Use the same specifier string in both places.

🔧 Proposed fix
-vi.mock("./models/user.ts", async (importActual) => {
+vi.mock("../../models/user.ts", async (importActual) => {
 	let actual = await importActual<typeof import("../../models/user.ts")>()
#!/bin/bash
set -euo pipefail

# Verifies that each vi.mock("...") target matches the corresponding importActual<typeof import("...")>() path.
python - <<'PY'
import re, pathlib
p = pathlib.Path("app/controllers/auth/controller.test.ts")
t = p.read_text()
pattern = re.compile(r'vi\.mock\("([^"]+)"[\s\S]*?importActual<[^>]*import\("([^"]+)"\)', re.M)
found = False
for i, (mock_path, actual_path) in enumerate(pattern.findall(t), 1):
    found = True
    status = "OK" if mock_path == actual_path else "MISMATCH"
    print(f"{i}. mock={mock_path} actual={actual_path} => {status}")
if not found:
    print("No vi.mock/importActual pairs found.")
PY
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/auth/controller.test.ts` around lines 6 - 7, The vi.mock
specifier string doesn't match the importActual specifier causing the mock to
not apply; update the vi.mock call so its first argument string equals the
string used inside importActual<typeof import("...")>(), e.g. change
vi.mock("./models/user.ts", ...) to use the exact same module specifier used in
importActual (or vice versa) so the mock pair (vi.mock and importActual)
reference the identical module name; ensure this consistency for the
vi.mock(...) and importActual<typeof import("...")>() pair in the test file.
app/controllers/home/controller.test.ts (1)

10-11: ⚠️ Potential issue | 🔴 Critical

Fix both vi.mock target specifiers to match importActual paths.

At Lines 10-11 and 38-39, the mock target strings ("./models/...") do not match the typed actual import paths ("../../models/..."), so mocks can fail to apply.

🔧 Proposed fix
-vi.mock("./models/game.ts", async (importActual) => {
+vi.mock("../../models/game.ts", async (importActual) => {
 	let actual = await importActual<typeof import("../../models/game.ts")>()
-vi.mock("./models/user.ts", async (importActual) => {
+vi.mock("../../models/user.ts", async (importActual) => {
 	let actual = await importActual<typeof import("../../models/user.ts")>()
#!/bin/bash
set -euo pipefail

# Confirms each vi.mock target path matches its importActual type-import path.
python - <<'PY'
import re, pathlib
p = pathlib.Path("app/controllers/home/controller.test.ts")
t = p.read_text()
pattern = re.compile(r'vi\.mock\("([^"]+)"[\s\S]*?importActual<[^>]*import\("([^"]+)"\)', re.M)
pairs = pattern.findall(t)
if not pairs:
    print("No vi.mock/importActual pairs found.")
else:
    for idx, (mock_path, actual_path) in enumerate(pairs, 1):
        print(f"{idx}. mock={mock_path} actual={actual_path} status={'OK' if mock_path == actual_path else 'MISMATCH'}")
PY

Also applies to: 38-39

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

In `@app/controllers/home/controller.test.ts` around lines 10 - 11, The vi.mock
target strings do not match the typed importActual paths, causing mocks to not
apply; update each vi.mock call so its first argument exactly matches the path
used inside importActual's type-import (change "./models/..." to
"../../models/..." to mirror importActual<...import("../../models/xxx")>),
ensuring every vi.mock("...") string equals the corresponding
importActual<...import("...")> path.
app/controllers/auth/forgot-password/controller.tsx (1)

57-71: ⚠️ Potential issue | 🔴 Critical

Do not render live password reset tokens in the response HTML.

Line 57-71 exposes a usable reset URL to the requester when a token exists, which leaks sensitive reset credentials.

🔒 Proposed fix
-						{token ? (
-							<div style="margin-top: 1rem; padding: 1rem; background: `#f8f9fa`; border-radius: 4px;">
-								<p style="font-size: 0.9rem;">
-									<strong>Demo Mode:</strong> Click the link below to reset your password
-								</p>
-								<p style="margin-top: 0.5rem;">
-									<a
-										href={routes.auth.resetPassword.index.href({ token })}
-										class="btn btn-secondary"
-									>
-										Reset Password
-									</a>
-								</p>
-							</div>
-						) : null}
+						{/* Never render reset tokens to clients. Deliver reset URL out-of-band (e.g., email). */}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/auth/forgot-password/controller.tsx` around lines 57 - 71,
The current JSX renders a live password reset link when the local variable token
is present (token and routes.auth.resetPassword.index.href are used), which
leaks credentials; remove the block that outputs the anchor with the actual href
and instead replace it with a non-sensitive message (e.g., “If an account
exists, a password reset link has been sent to your email”) or display a
masked/token-not-shown notice; ensure no code elsewhere in forgot-password
controller.tsx returns or injects the raw token into the HTML response.
app/controllers/auth/register/controller.tsx (3)

118-118: ⚠️ Potential issue | 🔴 Critical

Session data structure mismatch breaks authentication after registration.

The session stores { auth: user.id } but parseAuthSession in app/utils/auth-session.ts:17-28 expects { userId: string }. This causes newly registered users to fail authentication immediately—the schema validation will return null since userId is missing.

The login controller at app/controllers/auth/login/controller.tsx:30 correctly uses { userId: user.id }.

-		session.set("auth", { auth: user.id })
+		session.set("auth", { userId: user.id })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/auth/register/controller.tsx` at line 118, The session is
being set with the wrong shape in session.set("auth", { auth: user.id }) which
does not match parseAuthSession (expected { userId: string }); change the
session write to use the same property name as login/controller.tsx — call
session.set("auth", { userId: user.id }) so parseAuthSession will validate and
return the newly registered user; ensure any other places that read or write the
"auth" session use the { userId } shape for consistency with parseAuthSession.

15-15: ⚠️ Potential issue | 🟡 Minor

Page title displays "Login" but this is the registration page.

The title should reflect the registration flow.

-				<Document url={url} head={<title>Login - Remix Wordle</title>}>
+				<Document url={url} head={<title>Register - Remix Wordle</title>}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/auth/register/controller.tsx` at line 15, The Document
component on the registration page is using an incorrect title; update the head
prop in the Document usage (the line rendering Document with url and head) to
use an appropriate registration title such as "Register - Remix Wordle" or "Sign
Up - Remix Wordle" so the page title matches the registration flow.

91-91: ⚠️ Potential issue | 🟡 Minor

Same title issue in the error rendering path.

-				<Document url={url} head={<title>Login - Remix Wordle</title>}>
+				<Document url={url} head={<title>Register - Remix Wordle</title>}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/auth/register/controller.tsx` at line 91, The error rendering
path in the register controller uses the wrong page title ("Login - Remix
Wordle"); update the Document head title in the error branch from "Login - Remix
Wordle" to the correct "Register - Remix Wordle" (look for the Document
component instance with Document url={url} head={<title>Login - Remix
Wordle</title>}), and verify any other titles in this file or the register error
rendering path are similarly corrected to "Register - Remix Wordle".
app/controllers/auth/reset-password/controller.tsx (3)

87-87: ⚠️ Potential issue | 🟡 Minor

Same title issue on the success page.

-				<Document url={url} head={<title>Login - Remix Wordle</title>}>
+				<Document url={url} head={<title>Reset Password - Remix Wordle</title>}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/auth/reset-password/controller.tsx` at line 87, The success
page currently uses the same Document head title ("Login - Remix Wordle") as the
login page; update the Document component used in the reset-password success
render so its head prop contains an appropriate title like "Password Reset
Success - Remix Wordle" (or "Reset Password - Remix Wordle") instead of "Login -
Remix Wordle"—look for the Document component and its head/title usage in the
reset-password controller (the line with Document url={url}
head={<title>...</title>}) and change the title string accordingly.

20-20: ⚠️ Potential issue | 🟡 Minor

Page title displays "Login" but this is the reset password page.

-				<Document url={url} head={<title>Login - Remix Wordle</title>}>
+				<Document url={url} head={<title>Reset Password - Remix Wordle</title>}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/auth/reset-password/controller.tsx` at line 20, The page
title in the Document component within reset-password controller.tsx is
incorrectly set to "Login"; update the head <title> used in Document (the JSX
element rendering title inside Document url={url}) to a correct label such as
"Reset Password - Remix Wordle" (or similar) so the browser tab and
accessibility metadata reflect this is the reset password page.

79-84: ⚠️ Potential issue | 🔴 Critical

Dead code: resetPassword always returns true, making the error branch unreachable.

Per app/models/user.ts:69-82, resetPassword has all validation logic commented out and unconditionally returns true. The error handling at lines 81-84 will never execute—invalid or expired tokens will appear to succeed silently.

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

In `@app/controllers/auth/reset-password/controller.tsx` around lines 79 - 84, The
controller's error branch is dead because model.resetPassword currently always
returns true; update the logic so invalid/expired tokens are detected and
resetPassword returns a meaningful boolean or throws on failure, or perform
explicit token validation before calling resetPassword. Specifically, restore or
implement the validation logic in the resetPassword function in
app/models/user.ts (ensure it checks token existence, expiry, and matches user),
or change the controller to call a new validateResetToken(token) helper and only
call resetPassword when validation passes; keep the existing
session.flash("error", ...) and redirect(routes.auth.resetPassword.index.href({
token: params.token })) behavior when validation fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/controllers/auth/login/controller.tsx`:
- Around line 22-33: The redirect uses the user-controlled variable returnTo
directly, which allows open redirects; validate returnTo before using it by
ensuring it is an internal path (e.g., a relative path starting with "/" and not
containing a scheme or hostname) or matches your app's allowed origins, and only
use it when safe; update the login handler around the returnTo variable (the
code that sets let returnTo = url.searchParams.get("returnTo") and later calls
redirect(returnTo ?? routes.home.index.href())) to sanitize/validate returnTo
and fall back to routes.home.index.href() when validation fails.

In `@oxlint-plugins/prefer-import-alias-plugin.ts`:
- Around line 10-12: The current newSource computation only removes a single
leading "../" so nested parent imports like "../../utils/x" become
"#../utils/x"; update the logic that builds newSource (the source.replace calls
used before calling fixer.replaceText on node.source) to strip all leading "../"
occurrences (and a single "./") and then prefix the remainder with "#" — e.g.
replace the two replace(...) calls with a single expression that replaces all
leading "../" groups (use a regex like /^(\.\.\/)+/) and also handle a leading
"./" so the final newSource is "#<path-without-parent-or-dot-parts>" before
calling fixer.replaceText.

In `@server.ts`:
- Line 19: The current PORT parsing (const port = process.env.PORT ?
parseInt(process.env.PORT, 10) : 44100) can yield NaN and crash server.listen;
update the logic around process.env.PORT/parseInt to validate the parsed value
(e.g., ensure Number.isInteger(port) and port > 0 and <= 65535) and fall back to
the default 44100 (or log an error and exit) when invalid; locate and change the
code that defines the port variable (the const port line) and any server.listen
usage to use the validated port.

---

Outside diff comments:
In `@app/models/user.ts`:
- Around line 55-67: The createPasswordResetToken function calls the async
getUserByEmail(email) without awaiting it, so change createPasswordResetToken to
an async function, await getUserByEmail(email), and update its return type to
Promise<string | undefined>; inside the async function use "const user = await
getUserByEmail(email)" and keep the existing null check and token generation
logic (ensure callers of createPasswordResetToken are updated to handle a
Promise).

---

Duplicate comments:
In `@app/controllers/auth/controller.test.ts`:
- Around line 6-7: The vi.mock specifier string doesn't match the importActual
specifier causing the mock to not apply; update the vi.mock call so its first
argument string equals the string used inside importActual<typeof
import("...")>(), e.g. change vi.mock("./models/user.ts", ...) to use the exact
same module specifier used in importActual (or vice versa) so the mock pair
(vi.mock and importActual) reference the identical module name; ensure this
consistency for the vi.mock(...) and importActual<typeof import("...")>() pair
in the test file.

In `@app/controllers/auth/forgot-password/controller.tsx`:
- Around line 57-71: The current JSX renders a live password reset link when the
local variable token is present (token and routes.auth.resetPassword.index.href
are used), which leaks credentials; remove the block that outputs the anchor
with the actual href and instead replace it with a non-sensitive message (e.g.,
“If an account exists, a password reset link has been sent to your email”) or
display a masked/token-not-shown notice; ensure no code elsewhere in
forgot-password controller.tsx returns or injects the raw token into the HTML
response.

In `@app/controllers/auth/register/controller.tsx`:
- Line 118: The session is being set with the wrong shape in session.set("auth",
{ auth: user.id }) which does not match parseAuthSession (expected { userId:
string }); change the session write to use the same property name as
login/controller.tsx — call session.set("auth", { userId: user.id }) so
parseAuthSession will validate and return the newly registered user; ensure any
other places that read or write the "auth" session use the { userId } shape for
consistency with parseAuthSession.
- Line 15: The Document component on the registration page is using an incorrect
title; update the head prop in the Document usage (the line rendering Document
with url and head) to use an appropriate registration title such as "Register -
Remix Wordle" or "Sign Up - Remix Wordle" so the page title matches the
registration flow.
- Line 91: The error rendering path in the register controller uses the wrong
page title ("Login - Remix Wordle"); update the Document head title in the error
branch from "Login - Remix Wordle" to the correct "Register - Remix Wordle"
(look for the Document component instance with Document url={url}
head={<title>Login - Remix Wordle</title>}), and verify any other titles in this
file or the register error rendering path are similarly corrected to "Register -
Remix Wordle".

In `@app/controllers/auth/reset-password/controller.tsx`:
- Line 87: The success page currently uses the same Document head title ("Login
- Remix Wordle") as the login page; update the Document component used in the
reset-password success render so its head prop contains an appropriate title
like "Password Reset Success - Remix Wordle" (or "Reset Password - Remix
Wordle") instead of "Login - Remix Wordle"—look for the Document component and
its head/title usage in the reset-password controller (the line with Document
url={url} head={<title>...</title>}) and change the title string accordingly.
- Line 20: The page title in the Document component within reset-password
controller.tsx is incorrectly set to "Login"; update the head <title> used in
Document (the JSX element rendering title inside Document url={url}) to a
correct label such as "Reset Password - Remix Wordle" (or similar) so the
browser tab and accessibility metadata reflect this is the reset password page.
- Around line 79-84: The controller's error branch is dead because
model.resetPassword currently always returns true; update the logic so
invalid/expired tokens are detected and resetPassword returns a meaningful
boolean or throws on failure, or perform explicit token validation before
calling resetPassword. Specifically, restore or implement the validation logic
in the resetPassword function in app/models/user.ts (ensure it checks token
existence, expiry, and matches user), or change the controller to call a new
validateResetToken(token) helper and only call resetPassword when validation
passes; keep the existing session.flash("error", ...) and
redirect(routes.auth.resetPassword.index.href({ token: params.token })) behavior
when validation fails.

In `@app/controllers/home/controller.test.ts`:
- Around line 10-11: The vi.mock target strings do not match the typed
importActual paths, causing mocks to not apply; update each vi.mock call so its
first argument exactly matches the path used inside importActual's type-import
(change "./models/..." to "../../models/..." to mirror
importActual<...import("../../models/xxx")>), ensuring every vi.mock("...")
string equals the corresponding importActual<...import("...")> path.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a4510ec0-677c-46e2-ac7c-d2827dfbc4cf

📥 Commits

Reviewing files that changed from the base of the PR and between 35db8cc and 1870b0c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (43)
  • .vscode/settings.json
  • app/components/document.tsx
  • app/components/form.tsx
  • app/components/game-over-modal.tsx
  • app/components/keyboard.tsx
  • app/constants.ts
  • app/controllers/auth/controller.test.ts
  • app/controllers/auth/controller.tsx
  • app/controllers/auth/forgot-password/controller.tsx
  • app/controllers/auth/login/controller.tsx
  • app/controllers/auth/register/controller.tsx
  • app/controllers/auth/reset-password/controller.tsx
  • app/controllers/health.tsx
  • app/controllers/history/controller.tsx
  • app/controllers/history/game.tsx
  • app/controllers/history/history-page.tsx
  • app/controllers/history/not-found-page.tsx
  • app/controllers/home/controller.test.ts
  • app/controllers/home/controller.tsx
  • app/controllers/home/page.tsx
  • app/entry.browser.ts
  • app/middleware/auth.ts
  • app/models/game.ts
  • app/models/user.ts
  • app/router.ts
  • app/routes.ts
  • app/utils/auth-session.ts
  • app/utils/board-to-emoji.test.ts
  • app/utils/db.ts
  • app/utils/game.test.ts
  • app/utils/game.ts
  • app/utils/queue.ts
  • app/utils/redirect.ts
  • app/utils/render.ts
  • app/utils/session.ts
  • mocks/handlers.ts
  • mocks/test.ts
  • oxlint-plugins/prefer-import-alias-plugin.ts
  • oxlint-plugins/prefer-let-locals-plugin.ts
  • package.json
  • pnpm-workspace.yaml
  • server.ts
  • vite.config.ts

Comment thread app/controllers/auth/login/controller.tsx Outdated
Comment thread oxlint-plugins/prefer-import-alias-plugin.ts
Comment thread server.ts
…model functions

Signed-off-by: Logan McAnsh <logan@mcan.sh>
@mcansh mcansh merged commit bbed2ff into main Apr 4, 2026
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