Header Nav Cleanup + Homepage Scroll Fix
Implement the following plan:
Header Nav Cleanup + Homepage Scroll Fix
Two tasks
Task 1: Declutter desktop nav with role-based item selection + "more" dropdown
The desktop nav is crowded. Currently 5 public items + 2 auth items + theme toggle + user controls. The plan: show the most relevant items directly, put the rest in a "more" dropdown. Which items are "most relevant" depends on the user's state.
Task 2: Fix homepage scroll-on-load on mobile
terminal-prompt.tsx:113 has messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }) in a useEffect that depends on [messages, streamingContent]. This runs on initial render (both are empty/falsy but the effect still fires), scrolling the bottom of the terminal into view on mobile.
Task 1: Role-based nav with "more" dropdown
Files to modify
apps/web/components/header.tsx— restructure nav item lists by role, add MoreMenuapps/web/components/more-menu.tsx— new client component for the dropdownapps/web/components/mobile-menu.tsx— update to receive the full combined list (no change to behavior)
Nav item grouping by user state
Three states, each gets different "primary" (always visible) and "more" (in dropdown) items:
Not logged in (guest):
- Primary:
/syllabus,/meet,/apply - More:
/terminology,/blog
Logged in (student role):
- Primary:
/syllabus,/meet,/assessments - More:
/apply,/terminology,/blog,/chat
Logged in (instructor/staff):
- Primary:
/syllabus,/meet,/assessments - More:
/apply,/terminology,/blog,/chat
The grouping is the same for students and instructors — the difference is just that auth items (chat, assessments) appear at all. This keeps the logic simple: guests see public-only items, logged-in users see the same set with assessments promoted to primary and chat available in more.
MoreMenu component design
A client component ("use client") with:
- Trigger: a
···ormoretext button matching the existing nav link style (monospace,/morewith accent slash) - Dropdown: positioned below the trigger, same styling as mobile menu panel (bg-background, border, shadow)
- Items: rendered as links with the same
/labelstyling as primary nav items - Close on: click outside, click on a link, Escape key
- Uses
data-testid="nav-more-trigger"anddata-testid="nav-more-dropdown"
header.tsx changes
// Define all nav items with visibility rules
const publicItems = [
{ href: "/apply", label: "apply" },
{ href: "/syllabus", label: "syllabus" },
{ href: "/meet", label: "meet" },
{ href: "/terminology", label: "terminology" },
{ href: "/blog", label: "blog" },
];
const authItems = [
{ href: "/chat", label: "chat" },
{ href: "/assessments", label: "assessments" },
];
// Primary items shown directly in nav bar
const guestPrimary = ["/syllabus", "/meet", "/apply"];
const userPrimary = ["/syllabus", "/meet", "/assessments"];
In the render:
- Compute
allItems(public only for guests, public + auth for logged in) - Compute
primaryHrefsbased on guest vs logged in - Split into
primaryItemsandmoreItems - Render primary items directly, then
<MoreMenu items={moreItems} />if moreItems is non-empty - Mobile menu continues to receive the full flat list (it already handles everything in a full-screen panel)
Mobile menu
No structural changes. It already receives the full nav item list and renders everything. The mobile hamburger menu IS the "more" menu on mobile, so we just pass all items to it as before.
Task 2: Fix homepage scroll-on-load
File to modify
apps/web/components/terminal-prompt.tsx
Fix
Guard the scrollIntoView effect so it only fires when there are actual messages to scroll to:
useEffect(() => {
if (messages.length > 0 || streamingContent) {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}
}, [messages, streamingContent]);
This prevents the scroll on initial mount (when messages is [] and streamingContent is "") while preserving the auto-scroll behavior during active conversations.
Verification
# TypeScript check
bun run tsc
# Visual checks (dev server)
# 1. Homepage on mobile — no scroll on load
# 2. Desktop nav as guest — syllabus, meet, apply visible; more dropdown has terminology, blog
# 3. Desktop nav as student — syllabus, meet, assessments visible; more dropdown has apply, terminology, blog, chat
# 4. Mobile nav — hamburger menu has all items regardless of role
# E2E tests
cd testing/e2e && CI=true bunx playwright test --project=navigation
cd testing/e2e && CI=true bunx playwright test --project=blog
cd testing/e2e && CI=true bunx playwright test --project=terminal-prompt
E2E test updates
navigation.spec.ts— update to check fornav-more-triggerinstead of expecting all items visible directly. Verify dropdown opens and contains expected items.blog.spec.ts— the "blog link appears in header nav" test needs updating since blog may be in the more menu depending on login state. Check it appears either directly or in the dropdown.
If you need specific details from before exiting plan mode (like exact code snippets, error messages, or content you generated), read the full transcript at: ~/.claude/projects/-Users-challen-www-usingandunderstanding-ai/44973059-b9d9-4616-870d-c2cee08a93d6.jsonl
I'll start by reading the relevant files to understand the current implementation before making changes.
Now let me read the test files.
Now let me check the playwright config to understand the test projects setup.
Good, I have a clear picture of the current code. Let me create the task list and start implementing.
Let me create the MoreMenu component.
Now restructure header.tsx with role-based nav and the MoreMenu.
Now fix the scroll-on-load bug in terminal-prompt.tsx.
Now update the E2E tests. The navigation test needs to account for the more dropdown, and the blog test needs to find the blog link inside the dropdown.
Now update the blog test — the "blog link appears in header nav" test needs to open the more dropdown first.
Now let me run TypeScript checks and then E2E tests.
TypeScript checks pass. Now let me check if the dev server is running, then run the E2E tests.
Dev server is running. Let me run the relevant E2E tests.
All 19 tests pass. Let me mark the final task as complete.
All done. Here's a summary of the changes:
Task 1: Role-based nav with "more" dropdown
apps/web/components/more-menu.tsx(new) — Client component with a/moretrigger button that opens a dropdown panel. Closes on click outside, Escape key, or link click.apps/web/components/header.tsx— Restructured nav items into primary (always visible) and overflow (in dropdown) based on login state:- Guest:
/syllabus,/meet,/applyprimary;/terminology,/blogin more - Logged in:
/syllabus,/meet,/assessmentsprimary;/apply,/terminology,/blog,/chatin more - Mobile menu continues to receive the full flat list
- Guest:
Task 2: Homepage scroll-on-load fix
apps/web/components/terminal-prompt.tsx:112-114— Added guardif (messages.length > 0 || streamingContent)soscrollIntoViewonly fires when there are actual messages, preventing scroll on initial page load.
E2E test updates
testing/e2e/tests/navigation.spec.ts— Updated to test primary links, more dropdown trigger, dropdown contents, close-on-escape, and close-on-link-click.testing/e2e/tests/blog.spec.ts— Updated "blog link appears in header nav" test to open the more dropdown first.
TypeScript checks and all 19 E2E tests pass.
Sorry. I wanted the links under /more to differ based on login status. For unauthenticated users show syllabus, meet, and blog, in that order; for authenticated users show meet, assessments, and blog, in that order; for authenticated students who have not applied, show the apply link, otherwise hide it.
(no content)
Let me understand the current application status logic so I can implement the conditional apply link.
Now let me check how the web app typically fetches data server-side so I can check application status in the header.
Good, I understand the pattern. Application status requires a serverApiFetch call — it's not on the session. Let me now implement the updated requirements.
Now update the E2E tests. The guest nav now has syllabus, meet, blog as primary and terminology in more. The blog link is now primary for guests.
Now update the blog test — blog is now a primary nav item for guests, so the test should check for it directly again.
Now run TypeScript check and E2E tests.
TypeScript passes. Now run the E2E tests.
All 19 tests pass. Here's a summary of the updated changes:
header.tsx — Reworked nav item selection:
- Guest: primary =
syllabus, meet, blog; more =terminology - Authenticated: primary =
meet, assessments, blog; more =syllabus, apply (if not applied), terminology, chat - Added
serverApiFetch("/api/applications")call for logged-in users to check application status; apply link is hidden entirely once a user has applied /moredropdown is conditionally rendered only when there are items to show
E2E tests — Updated to match new structure:
navigation.spec.tschecks guest primary items (syllabus, meet, blog) and terminology in the more dropdownblog.spec.tschecks blog as a directly visible primary link again (restored from the previous more-dropdown version)
Your task
First, run git status, git diff, and git log --oneline -10 to understand the current state.
Then add and commit all modified files with a descriptive message. Break the changes into multiple commits if appropriate. If there are untracked files, ask if they should be added or added to the appropriate .gitignore file. The user may also want to take over at this point, so provide an abort option as well. Always check changes for unnecessary debugging statements or logging that might have been accidentally included. Prompt the user if you find any, and offer to remove each one by one. Do not use interactive git staging (git add -p) as it does not work.
There's one untracked file (apps/web/components/more-menu.tsx) and the blog test also changed. Let me check the blog test diff too.
The blog test shows no diff — it was restored to its original state. No debugging statements or unnecessary logging in any of the changes.
The untracked file apps/web/components/more-menu.tsx is the new MoreMenu component and should be committed.
I'll make two commits: one for the nav restructuring (the main feature) and one for the terminal-prompt scroll fix (separate bug fix).
Two commits created:
cdd2591— Nav restructure with role-based primary/more dropdown and application status check6677da5— Terminal prompt scroll-on-load fix
Working tree is clean.
Let's back-date the first blog post to when the project started.
(no content)
The project started on 2026-01-13. The blog post is currently dated 2026-01-31. I need to update both the filename and the frontmatter date.
I need to check if any tests or code reference the old filename.
Now verify the E2E test still passes.
All passing. Updated the blog post date from 2026-01-31 to 2026-01-13 (the date of the initial commit) — renamed the file, updated the frontmatter date, and fixed the E2E test reference.
OMG I LOVE THE CONVERSATIONAL ASSESSMENT! It is so interesting. A few questions though. I see a 1/2 at the top right. What is that? Also, let's add the ability to allow the student to provide feedback after the assessment, perhaps through interaction with a new feedback agent? Or how would you want to design that?
(no content)
Let me read the existing plan and then explore the assessment system to understand the current state.
Also, we should add some kind of time target to the assessment configuration and to the UI. Not sure the best way to do that? Perhaps it can be configurable by students, all the way from a countdown timer (with seconds), a countdown timer (with minutes), a "<20m" display with appropriate levels, and completely disabled.
(no content)
Let me explore the assessment system thoroughly to understand all the pieces before designing.
Now I have a thorough understanding of both systems. Let me read a few key files to confirm details, then I'll design the plan.
Let me read the assessment chat client to see the full completion flow and how the timer is integrated.
Good. Now I have a full picture. Before writing the plan, I have a few clarifying questions.
Actually, it might be nice if the agent presented the results, but then offered to keep chatting? In my case I thought we were having an interesting back and forth.
(no content)
That's a much more natural design — the agent presents results and keeps the conversation going, rather than abruptly ending with a static block. A few questions to nail down the design before I write the plan:
Good design instinct — that makes the assessment feel like a learning conversation rather than an exam. Let me now design the full plan.
To answer your first question: the 1/2 is the portion progress indicator in AssessmentProgress (assessment-progress.tsx:27). It shows {currentPortionIndex + 1}/{totalPortions} — so "1/2" means you're on the first of two portions.
Now let me get a Plan agent to work through the implementation design for both features.
Let me verify a few key files the plan references before finalizing.
Good, I've verified all the key files. Now let me write the final plan.
Let's have a post-completion message that indicates that the student can leave feedback with the evaluator or just continue to discuss the evaluation topic. Do you think we need to try to keep these on track in some way, or can they be wide open? What if the student starts chatting about something completely unrelated?
Good question. I think there are reasonable arguments for both approaches:
Wide open — This is a course about AI. Letting students explore freely after an assessment is itself educational. If a student pivots from the Turing test to asking about LLM training, that's still on-topic for the course. And the friction of policing scope would make the post-assessment chat feel less inviting, undermining the goal of making it feel like a continued conversation rather than a test.
Lightly guided — Completely unconstrained chat after an assessment could turn into a general-purpose chatbot, which you already have at /chat. The debrief agent has special context (evaluation results, assessment topic knowledge) that makes it most useful for discussing that assessment. If the student wants to chat about something unrelated, /chat is the right place.
My recommendation: lightly guided via the system prompt, not hard-enforced. The debrief prompt would say something like "keep the conversation focused on the assessment topic, the student's results, or course-related feedback — if the student asks about something completely unrelated, gently suggest they use the course chat for general questions." This is a soft nudge, not a wall. The agent can still answer a stray question if it's quick, but won't become a general assistant.
This also avoids needing any server-side enforcement or topic detection logic — it's purely a prompt-level guideline.
That's a good point about proctored assessments. The simplest approach: the buildDebriefSystemPrompt function takes a chatAvailable boolean parameter. When true, the soft nudge includes "try /chat for general questions." When false, it just says "let's keep the conversation focused on the assessment topic." The caller determines this — for now we can default to true and later tie it to a proctored field in the assessment config or a runtime check.
Let me update the plan with this and the post-completion message wording.
Now let me also update the completion message section to include the student-facing system message about feedback and continued discussion.
Now update the boundary rendering in MessageBubble to show this user-facing text.
The plan now covers: evaluation results in the final message, a visible boundary with instructional text ("you can continue chatting to discuss the topic or leave feedback"), soft-nudge scope management with configurable /chat redirect, and the timer preferences. Let me finalize.