Skip to content

[codex] Modernize frontend toolchain and strict linting#143

Closed
rchalamala wants to merge 7 commits into
mainfrom
codex/modernize-dependencies-and-chain
Closed

[codex] Modernize frontend toolchain and strict linting#143
rchalamala wants to merge 7 commits into
mainfrom
codex/modernize-dependencies-and-chain

Conversation

@rchalamala
Copy link
Copy Markdown
Owner

@rchalamala rchalamala commented May 12, 2026

Summary

Modernizes the Caltech course scheduler frontend toolchain and tightens the project rules around typing and linting.

What changed

  • Migrated the app from the old Create React App-era setup to Vite/Bun-based tooling.
  • Upgraded the frontend dependency chain, including React, MUI, Vite, TypeScript, Tailwind, and Wrangler.
  • Replaced react-beautiful-dnd with @hello-pangea/dnd.
  • Moved generated course JSON files into public/data so Vite serves them as static assets.
  • Consolidated styling into Tailwind classes and removed obsolete custom CSS files.
  • Added strict TypeScript project references and a shared strict compiler policy.
  • Added Oxc type-aware linting through .oxlintrc.jsonc.
  • Removed unnecessary Stylelint/Autoprefixer/Tailwind config machinery after styling was inlined into Tailwind utilities.
  • Added CI coverage for install, typecheck, lint, and build.

Why

The repo was still carrying years-old CRA assumptions and a loose toolchain. This makes the build chain current, trims dead configuration, and makes type/lint failures more explicit so future changes have sharper guardrails.

Validation

  • bun install --frozen-lockfile
  • bun run check
  • Browser smoke test on http://127.0.0.1:4173/fa2023: page loaded, calendar/search/workspace rendered, Default Schedule populated courses, Remove All reset the workspace, and the console had zero errors.

Note: Vite now reports the default large chunk warning during production build because the old custom chunk warning suppression was removed intentionally.


View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 12, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
caltech-dev ff34d6e Commit Preview URL

Branch Preview URL
May 12 2026, 10:01 AM

@rchalamala rchalamala marked this pull request as ready for review May 12, 2026 09:58
@rchalamala rchalamala marked this pull request as draft May 12, 2026 09:59
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 4 potential issues.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment thread src/Workspace.tsx
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Boolean false rendered as literal "false" CSS class name

In the WorkspaceEntry component, the template literal ${course.locked && "bg-neutral-100"} evaluates to the boolean false when course.locked is false. In a JavaScript template literal, ${false} becomes the string "false", so the class attribute will contain the literal text false as a class name. The correct pattern is a ternary: course.locked ? "bg-neutral-100" : "".

(Refers to lines 270-272)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/App.tsx
Comment on lines 604 to +608
if (result) {
// course was already in workspace
newCourses = courses;
newCourses = courses.map((course) =>
course.courseData.id === newCourse.courseData.id ? newCourse : course,
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 addCourse overwrites existing course state when re-adding from search

When addCourse is called for a course that already exists in the workspace, the new code replaces the existing course object with newCourse (src/App.tsx:606-608). The old code preserved existing state with newCourses = courses. This is a behavioral regression: when WorkspaceSearch.handleSelect (src/Workspace.tsx:397-403) calls addCourse with sectionId: null, enabled: true, locked: false, re-selecting an already-added course from the search bar silently resets the user's section selection, lock state, and enable state.

Old vs new behavior when course already exists

Old code: newCourses = courses — no change to existing course.

New code:

newCourses = courses.map((course) =>
  course.courseData.id === newCourse.courseData.id ? newCourse : course,
);

Replaces existing course with newCourse, which from search has sectionId: null, locked: false.

Suggested change
if (result) {
// course was already in workspace
newCourses = courses;
newCourses = courses.map((course) =>
course.courseData.id === newCourse.courseData.id ? newCourse : course,
);
if (result) {
// course was already in workspace — keep existing state
newCourses = courses;
} else {
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/App.tsx
Comment on lines 558 to 566
// 5 blank workspaces by default bc I'm too lazy to implement dynamic tabs and stuff
const localWorkspaces = localStorage.getItem("workspaces" + realPath);
const [workspaces, setWorkspaces] = useState<Workspace[]>(
localWorkspaces
? JSON.parse(localWorkspaces)
: [
emptyWorkspace(),
emptyWorkspace(),
emptyWorkspace(),
emptyWorkspace(),
emptyWorkspace(),
],
const [workspaces, setWorkspaces] = useState<Workspace[]>(() =>
parseStoredWorkspaces(localWorkspaces),
);
const localWorkspaceIdx = localStorage.getItem("workspaceIdx" + realPath);
const [workspaceIdx, setWorkspaceIdx] = useState<number>(
localWorkspaceIdx ? JSON.parse(localWorkspaceIdx) : 0,
const [workspaceIdx, setWorkspaceIdx] = useState<number>(() =>
parseStoredWorkspaceIdx(localWorkspaceIdx, workspaces.length),
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 Workspace state not re-initialized when navigating between terms

When realPath changes (e.g., user navigates from /sp2026 to /fa2025), localStorage.getItem("workspaces" + realPath) at src/App.tsx:559 is re-evaluated but the useState initializer at line 560-562 only runs once. The save effect at lines 578-584 then fires because realPath changed, writing the OLD term's workspace data to the NEW term's localStorage key — corrupting saved state. This is a pre-existing bug (the old code had the same pattern) so it's not introduced by this PR, but the async data loading amplifies it: setIndexedCourses(null) at line 531 triggers a loading screen, during which the save effect runs with stale data. A fix would involve keying the component on realPath or using a useEffect to re-load workspace state when the path changes.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/Workspace.tsx
Comment on lines +259 to 265
let className =
"mb-8 flex flex-col items-center rounded border border-neutral-300 p-2 transition-colors shadow-lg";
className += course.enabled ? " bg-white" : " bg-neutral-300";
className += course.locked ? " bg-neutral-100" : "";

const [infoModalOpen, setInfoModalOpen] = useState(false);
return (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 Duplicate and conflicting Tailwind classes in WorkspaceEntry

The className variable at lines 259-262 builds up conditional background colors (bg-white/bg-neutral-300 for enabled/disabled, bg-neutral-100 for locked), but the div template at line 270 unconditionally appends bg-white shadow-lg border-0. This means: (1) bg-white always appears, making the bg-neutral-300 disabled state from line 261 unreliable — in Tailwind v4, conflicting utilities of the same property have undefined winner order based on stylesheet generation; (2) border-0 conflicts with border border-neutral-300 from line 260; (3) shadow-lg is duplicated. The net effect is the disabled course visual state (bg-neutral-300) likely never renders correctly. This appears to be a refactoring oversight — the old CSS classes had explicit specificity order that Tailwind utilities don't replicate.

(Refers to lines 259-272)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0149c5983c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/App.tsx
Comment on lines +606 to +608
newCourses = courses.map((course) =>
course.courseData.id === newCourse.courseData.id ? newCourse : course,
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve existing course state on duplicate add

When addCourse sees an existing course ID, it now replaces that entry with newCourse instead of leaving it unchanged. WorkspaceSearch passes sectionId: null and locked: false on selection, so selecting a course that's already in the workspace will silently reset its chosen section/lock state and can reshuffle arrangements unexpectedly. The previous behavior was effectively idempotent for duplicate adds, so this is a user-visible regression.

Useful? React with 👍 / 👎.

@rchalamala rchalamala closed this May 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant