Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .forge/features/blade-responsive-navigation/evidence/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Blade navigation review media

Screenshots and videos are hosted as attachments on [PR #537](https://github.com/KnightHacks/forge/pull/537), not stored in the repository.

Captured from a local development server in Chromium. The full behavioral suite passed in headed mode; the final navigation videos use headless mode to capture the complete viewport. Navigation recordings use an isolated local database containing only the synthetic fixture from `apps/blade/src/tests/e2e/responsive-navigation.spec.ts`.

The tests deliberately hold route responses to verify feedback before completion. These clips demonstrate behavior, not production response times. Recording uses Playwright's 100 ms action pacing to make the steps readable; videos preserve the recorded timing.

| Evidence | What to look for |
| ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| [Desktop navigation screenshot](https://github.com/user-attachments/assets/8e3d60f2-78b5-4c99-9012-4fc7a96c9b5b) | The Members destination highlights and the top progress bar appears while the current Forms page remains usable. |
| [Desktop navigation video](https://github.com/user-attachments/assets/a84c2db5-d4fd-4c5d-82da-5d2b8971b468) | Immediate feedback, completed navigation, browser back, and a repeated navigation without stuck loading. |
| [Mobile navigation screenshot](https://github.com/user-attachments/assets/680d6b59-6730-4a79-a93b-a274df958362) | The 320px drawer closes immediately, with a static progress indicator in reduced-motion mode. |
| [Mobile navigation video](https://github.com/user-attachments/assets/2c5fe6c4-1715-4531-82c6-c952653b516c) | Mobile menu selection, immediate dismissal, and the completed destination. The clip begins after the test's desktop sign-in setup. |
| [Desktop skeleton](https://github.com/user-attachments/assets/ff1f66c5-3079-4b7a-83c9-e257b12f2fa3) | Blade's root fallback at 1440px, using the existing logo, tokens, and inset rows. |
| [Mobile skeleton](https://github.com/user-attachments/assets/08498076-fa25-4b76-aaa7-c16bc5d67051) | The same fallback at 320px without horizontal overflow. |

The root skeleton was captured by rendering the real `apps/blade/src/app/loading.tsx` through a temporary preview route, with reduced motion enabled for a stable frame. That preview route is not included in the change. The Next.js development indicator is visible in the captures.

To repeat the behavioral checks against a local test database:

```bash
pnpm --filter=@forge/blade run e2e responsive-navigation.spec.ts --headed
```

The standard Playwright configuration retains video only on failure. For these passing-run recordings, a temporary configuration enabled video recording; no permanent test configuration changed.
21 changes: 21 additions & 0 deletions .forge/features/blade-responsive-navigation/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Blade responsive navigation

Status: Implemented and validated.

## Purpose and scope

Members, officers and judges should see a response as soon as they navigate or select a view. Reuse Blade's violet accents, raised panels and existing page skeletons. Cover Blade routes and button-driven navigation; other websites are excluded.

## Acceptance criteria

- Links start navigation immediately, without a decorative delay.
- Pending navigation shows a subtle progress indicator and the intended rail destination immediately.
- Layout and page waits have responsive skeletons; existing content stays usable during in-page updates.
- Tabs and selectors acknowledge the chosen view immediately where the data is already available or can be safely represented as pending.
- Feedback clears after completion, interruption or error. Rapid navigation remains usable.
- Keyboard, modified clicks, downloads, scroll/history options, unsaved-settings guards and reduced motion retain their behavior.
- Saves keep their existing pending, success and error semantics. Do not invent successful results before confirmation.

## Decisions and open questions

The user prioritized navigation across Blade and authorized implementation and browser testing. No blocking product questions: use existing design and access contracts. Database, authentication, permissions, payments, uploads, email delivery and other websites are outside this change.
21 changes: 21 additions & 0 deletions .forge/features/blade-responsive-navigation/srd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Blade responsive navigation SRD

## Implementation

Follow `docs/agentic-development/forge-engineering-principles.md` and `apps/blade/DESIGN_SYSTEM.md`. Keep all implementation within `apps/blade`.

Replace the existing timer/event-based route link with Next Link's `onNavigate` and a root React transition provider. Route imperative push/replace/refresh calls through the same Blade-local hook. React owns pending lifetime and optimistic destination rollback; avoid timers, event patching, new dependencies and duplicate routing state. Preserve Next prefetch, normal link behavior, navigation cancellation, history and scroll options.

Use a narrow violet indeterminate progress bar, a polite status announcement and optimistic rail highlighting. Keep existing route skeletons and add a root fallback for missing leaf fallbacks and asynchronous layouts. Stop pulse/progress animation for reduced motion.

GET search/filter forms use the same transition router and retain their native no-JavaScript fallback. Issue filter dialogs close when a search starts.

In-page selected views may update optimistically while navigation is pending. Preserve server data ownership, URL state, permissions and current mutations.

## Access and data compatibility

Public routes remain public, member routes retain session gates, officer routes retain permission gates and judging retains its existing access gates. No APIs, validation rules, schemas, persisted data, Discord operations or environment configuration change. No migration. Rollback removes Blade UI changes. No annual configuration is introduced.

## Verification

Targeted Vitest regressions, Blade typecheck/lint, React analysis, repository format/lint/typecheck, and delayed-navigation Playwright checks with desktop and 320px screenshots. Record exact failures and limitations in status.md.
42 changes: 42 additions & 0 deletions .forge/features/blade-responsive-navigation/status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Blade responsive navigation status

Current phase: Complete

## Decisions

- Scope: Blade navigation/loading and feature notes only. Other websites and shared source are unchanged.
- Branch: `blade/responsive-navigation`, following the repository's app/slug convention. Pulled `origin/main` initially at `ec5e26ec`, then updated to `bd97fccb` when PR #533 landed. Incorporated `20878e0c` (PR #539) during the navigation review follow-up.
- Resolved import conflicts in form builder and responses dashboard while preserving upstream callback delivery and action feedback.
- Removed the 80 ms link delay and the never-reset exit flag. React transitions now own navigation feedback and optimistic destination rollback.
- Reused page skeletons and added a root fallback for layout waits and uncovered routes. Tabs and form section choices update immediately.
- Search/filter forms navigate without a full document reload; applying or clearing issue filters closes their dialog immediately.
- Addressed CodeRabbit's two navigation findings: the settings back arrow uses `data-pending`, and both hackathon check-in selectors show optimistic values. Check-in actions wait for the selected destination to commit; changing hackathons clears the event and disables the previous hackathon's event options while pending.

## Validation

- Blade suite: 799 tests passed across 142 files.
- After the final search/filter adjustment: 220 targeted tests passed across 34 files.
- Seven headed Playwright tests passed together in a clean run: delayed navigation/repeat/back, 320px drawer/reduced motion, immediate section selection, interrupted navigation, search without reload, unsaved-settings cancellation/discard, and issue-filter dialog completion.
- Inspected desktop and 320px navigation screenshots and root skeleton screenshots. No document overflow. The temporary skeleton preview route was removed after inspection.
- `pnpm format`: passed.
- `pnpm lint`: passed with existing repository warnings.
- `pnpm typecheck`: passed.
- `pnpm analyze:react:changed`: passed.
- `pnpm --filter=@forge/blade build`: passed with temporary local-only values for `JUDGING_ACCESS_SECRET` and `NEXT_PUBLIC_BLADE_URL`. The default invocation compiled but stopped at environment validation because those values are missing locally. No `.env` or deployment settings changed.
- Refreshed generated validator declarations and Next route types after stale generated files initially blocked checks. No shared source fixes were needed.
- `git diff --check`: passed. Test database fixtures were cleaned up and the temporary E2E server stopped.

### CodeRabbit navigation follow-up

- Reproduced the delayed hackathon/event values with three failing regression tests before the fix. Verified immediate values, cancellation/history restoration, and that scans wait for the committed station.
- Reproduced the settings arrow remaining at its hover position while navigation was held. The browser regression now verifies its pending translation and loading completion.
- After incorporating PR #539, 32 focused tests passed across five files, including admin access; all eight headed navigation browser tests passed again. Inspected the settings pending screenshot, kept outside the repository.
- Root format, lint, typecheck, React analysis, and the Blade production build passed. Lint reports existing repository warnings. Browser checks used an isolated local database containing synthetic fixtures.

## Remaining scope and links

No open implementation tasks. No deployment or real-service mutations were tested.

- Issue: [#536 — Make Blade navigation respond immediately](https://github.com/KnightHacks/forge/issues/536).
- Review media: [screenshots and recordings](evidence/README.md), hosted as GitHub PR attachments. Removed the six media binaries from the branch; the PR retains embedded screenshots and video players.
- PR: [#537 — Add immediate loading feedback across Blade navigation](https://github.com/KnightHacks/forge/pull/537), authored by and assigned to `DGoel1602`.
24 changes: 24 additions & 0 deletions .forge/features/blade-responsive-navigation/test-cases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Blade responsive navigation test cases

## Navigation

1. Click a member or admin link with a delayed route response: the request starts immediately, progress is visible and the navigation target highlights before the response completes. Existing shell remains usable when Next can retain it; a page/layout skeleton covers streamed waits.
2. Complete the response, navigate again, then use back/forward: correct URL and current navigation return, with no stuck dimming or loading state.
3. Navigate rapidly to a second destination: newest destination wins and feedback clears when navigation settles.
4. Close a navigation drawer while navigating: progress survives the drawer unmount.
5. Cancel navigation using the unsaved-settings guard or a link callback: no request or progress. Modified clicks, downloads and same-page anchors retain native behavior.
6. Navigate via a button, URL filter or refresh: immediate shared feedback, preserved replace/push and scroll behavior.
7. A failed route resolves to its existing error UI and clears navigation feedback.

## Views and appearance

8. Change a URL-backed tab/selector: chosen value responds immediately, data remains correctly associated with its view, and back/forward restores URL state.
9. Open a route while its layout waits: Blade-branded skeleton fits desktop and 320px without horizontal overflow.
10. Submit a form search or issue filter: retain the application shell, show progress before the server responds, preserve search parameters and close the filter dialog immediately.
11. Reduced-motion mode keeps a visible static progress indicator and skeletons without pulsing. Status is accessible without relying on motion.
12. On hackathon check-in, changing either selector shows the chosen value before the URL commits. Changing hackathon clears and disables the old event selection while pending; scans cannot submit against the previous station. Cancellation and history restore URL-selected values.
13. The profile settings Dashboard link moves its back arrow immediately while the route response is held, then clears loading after arrival.

## Placement

Regression tests in Blade's loading test directory; high-value delayed route tests in Blade's existing Playwright suite. Existing mutation and unsaved-settings tests protect behavior outside navigation feedback.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use client";

import { startTransition } from "react";
import { useRouter } from "next/navigation";

import type { RouterOutputs } from "@forge/api";
import { toast } from "@forge/ui/toast";
Expand All @@ -10,6 +9,7 @@ import type {
AlumniBulletinWorkspacePost,
BulletinSaveHandler,
} from "./alumni-bulletin-workspace";
import { useNavigationRouter as useRouter } from "~/app/_components/shared/route-transition-link";
import { api } from "~/trpc/react";
import { AlumniBulletinWorkspace } from "./alumni-bulletin-workspace";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import type { ReactNode } from "react";
import { useCallback, useMemo, useState, useTransition } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
ArrowDownRight,
ArrowUpRight,
Expand Down Expand Up @@ -80,6 +78,10 @@ import {
AdminPageHeader,
adminPageLayoutClassName,
} from "~/app/_components/shared/admin-page";
import {
RouteTransitionLink as Link,
useNavigationRouter as useRouter,
} from "~/app/_components/shared/route-transition-link";
import { ADMIN_PAGE_EYEBROWS } from "~/consts/admin-page-eyebrows";
import { api } from "~/trpc/react";
import {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import type { ReactNode } from "react";
import { useMemo, useState, useTransition } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
Activity,
ArrowRight,
Expand Down Expand Up @@ -73,6 +71,10 @@ import {
AdminPageHeader,
adminPageLayoutClassName,
} from "~/app/_components/shared/admin-page";
import {
RouteTransitionLink as Link,
useNavigationRouter as useRouter,
} from "~/app/_components/shared/route-transition-link";
import { api } from "~/trpc/react";
import {
AnalyticsMetricCard as MetricCard,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"use client";

import { useMemo, useState, useTransition } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
CalendarCheck2,
Flame,
Expand Down Expand Up @@ -39,6 +37,10 @@ import {
AdminPageHeader,
adminPageLayoutClassName,
} from "~/app/_components/shared/admin-page";
import {
RouteTransitionLink as Link,
useNavigationRouter as useRouter,
} from "~/app/_components/shared/route-transition-link";
import { ADMIN_PAGE_EYEBROWS } from "~/consts/admin-page-eyebrows";
import { formatClubDateTime } from "~/lib/dates";
import {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use client";

import { useMemo, useState } from "react";
import Link from "next/link";
import { ArrowUpRight, Building2, CheckCircle2, Search } from "lucide-react";

import type { RouterOutputs } from "@forge/api";
Expand All @@ -16,6 +15,7 @@ import {
AdminPageHeader,
adminPageStackClassName,
} from "~/app/_components/shared/admin-page";
import { RouteTransitionLink as Link } from "~/app/_components/shared/route-transition-link";
import { ADMIN_PAGE_EYEBROWS } from "~/consts/admin-page-eyebrows";
import { CompanyAdminMark } from "./company-admin-mark";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"use client";

import { useMemo, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
ArrowLeft,
ArrowUpRight,
Expand Down Expand Up @@ -50,6 +48,10 @@ import {
} from "@forge/validators";

import { adminPageClassName } from "~/app/_components/shared/admin-page";
import {
RouteTransitionLink as Link,
useNavigationRouter as useRouter,
} from "~/app/_components/shared/route-transition-link";
import { ADMIN_PAGE_EYEBROWS } from "~/consts/admin-page-eyebrows";
import { formatUtcShortMonth } from "~/lib/dates";
import { getGuildCompanyUrl } from "~/lib/guild-urls";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use client";

import { startTransition, useCallback, useState } from "react";
import { useRouter } from "next/navigation";

import type { RouterOutputs } from "@forge/api";
import { toast } from "@forge/ui/toast";
Expand All @@ -12,6 +11,7 @@ import type {
EmailPortalTab,
TemplateEditorSeed,
} from "./email-portal-workspace";
import { useNavigationRouter as useRouter } from "~/app/_components/shared/route-transition-link";
import { api } from "~/trpc/react";
import { EmailPortalWorkspace } from "./email-portal-workspace";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import type { LucideIcon } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import dynamic from "next/dynamic";
import { useRouter } from "next/navigation";
import {
Archive,
CalendarClock,
Expand Down Expand Up @@ -61,6 +60,7 @@ import {
AdminPageHeader,
adminPageLayoutClassName,
} from "~/app/_components/shared/admin-page";
import { useNavigationRouter as useRouter } from "~/app/_components/shared/route-transition-link";
import { ADMIN_PAGE_EYEBROWS } from "~/consts/admin-page-eyebrows";
import { formatClubDateTime, formatClubTime } from "~/lib/dates";
import {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"use client";

import { useEffect, useRef, useState, useTransition } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
ArrowDownAZ,
ArrowDownUp,
Expand Down Expand Up @@ -36,6 +34,10 @@ import {
AdminPageHeader,
adminPageLayoutClassName,
} from "~/app/_components/shared/admin-page";
import {
RouteTransitionLink as Link,
useNavigationRouter as useRouter,
} from "~/app/_components/shared/route-transition-link";
import { ADMIN_PAGE_EYEBROWS } from "~/consts/admin-page-eyebrows";
import {
clubDateTimeInput,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use client";

import { useEffect, useState } from "react";
import Link from "next/link";
import {
ArrowLeft,
LockKeyhole,
Expand All @@ -21,6 +20,7 @@ import {
AdminPageHeader,
adminPageLayoutClassName,
} from "~/app/_components/shared/admin-page";
import { RouteTransitionLink as Link } from "~/app/_components/shared/route-transition-link";
import { ADMIN_PAGE_EYEBROWS } from "~/consts/admin-page-eyebrows";
import { api } from "~/trpc/react";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { LucideIcon } from "lucide-react";
import Link from "next/link";

import { cn } from "@forge/ui";

import { RouteTransitionLink as Link } from "~/app/_components/shared/route-transition-link";

export interface EventWorkspaceSection<T extends string> {
href: string;
icon: LucideIcon;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
"use client";

import { useEffect, useReducer, useState, useTransition } from "react";
import Link from "next/link";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { usePathname, useSearchParams } from "next/navigation";
import {
KeyboardSensor,
PointerSensor,
Expand Down Expand Up @@ -36,6 +35,10 @@ import {
AdminPageHeader,
adminPageLayoutClassName,
} from "~/app/_components/shared/admin-page";
import {
RouteTransitionLink as Link,
useNavigationRouter as useRouter,
} from "~/app/_components/shared/route-transition-link";
import { ADMIN_PAGE_EYEBROWS } from "~/consts/admin-page-eyebrows";
import { api } from "~/trpc/react";
import { FormAvailabilityDialog } from "./form-availability-dialog";
Expand Down
Loading
Loading