Turn document

Fix Forgejo CI terminal test mock alias

The remaining Forgejo-only failure was a Next.js module-shape mismatch in the terminal client component. I switched the terminal screen to a namespace import for next/navigation so Forgejo no longer trips over Bun's named-export resolution for usePathname.

Updated: 2026-05-30 01:57 EDT Beads: islandflow-3l6 Validation: targeted terminal test + full Bun suite passed

New Changes as of 2026-05-30 01:57 EDT

This update follows the earlier Bun PATH and test-harness fixes. Forgejo was still failing inside the terminal component itself, where Bun 1.3.14 treated the direct usePathname import as a named-export mismatch. The component now reads the hook from the namespace import instead.

Summary of changes

Why this change was made

The previous test-level mocks were enough for local Bun, but Forgejo's Bun 1.3.14 runtime still errored on the named export lookup inside the client component. Changing the import shape removes that check instead of asking the test harness to paper over it.

Code diff

import * as nextNavigation from "next/navigation";
        

Related issues or PRs

islandflow-3l6

Summary

The remaining Forgejo failure was inside the terminal client component, not the install or typecheck stages. Using a namespace import keeps Bun from tripping over the usePathname named-export lookup in the runner.

Changes Made

  • Updated apps/web/app/terminal.tsx to read usePathname through the nextNavigation namespace.
  • Kept the earlier test-harness aliases intact, since they still cover the old runner behavior and make the tests resilient.
  • Left the earlier Bun PATH and redirect-mock fixes intact, since they were already solving the other CI failure modes.

Context

The repository already had the Bun executable path fix and the routes mock alias fix in place. The remaining failure surfaced only in the full CI-shaped test run, where Bun 1.3.14 was stricter about the terminal client component's direct named import from next/navigation.

Important Implementation Details

  • The terminal screen now reaches the pathname hook through the module namespace, which avoids Bun's stricter named-export check in CI.
  • This stays narrowly scoped to the client component and does not change the route semantics or the visible UI behavior.
  • The existing test mocks remain useful as guardrails, but the component import no longer depends on them to satisfy Bun's module loader.

Relevant Diff Snippets

Rendered with @pierre/diffs/ssr from the current working tree. It shows the terminal client component switching to a namespace import for next/navigation and updating the three pathname reads accordingly.

apps/web/app/terminal.tsx
-4+4
1
2
3
4
5
6
7
5369 unmodified lines
5377
5378
5379
5380
5381
5382
5383
1844 unmodified lines
7228
7229
7230
7231
7232
7233
7234
1863 unmodified lines
9098
9099
9100
9101
9102
9103
9104
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import {
createContext,
memo,
5369 unmodified lines
};
const useTerminalState = () => {
const pathname = usePathname();
const routeFeatures = useMemo(() => getRouteFeatures(pathname), [pathname]);
const [mode, setMode] = useState<TapeMode>("live");
const [replaySource, setReplaySource] = useState<string | null>(null);
1844 unmodified lines
};
export const FlowFilterPopover = ({ filters, onChange }: FlowFilterPopoverProps) => {
const pathname = usePathname();
const [open, setOpen] = useState(false);
const rootRef = useRef<HTMLDivElement | null>(null);
const activeCount = countActiveFlowFilterGroups(filters);
1863 unmodified lines
export function TerminalAppShell({ children }: { children: ReactNode }) {
const state = useTerminalState();
const pathname = usePathname();
const [drawerOpen, setDrawerOpen] = useState(false);
const tickerFieldId = useId();
const tickerHintId = useId();
1
2
3
4
5
6
7
5369 unmodified lines
5377
5378
5379
5380
5381
5382
5383
1844 unmodified lines
7228
7229
7230
7231
7232
7233
7234
1863 unmodified lines
9098
9099
9100
9101
9102
9103
9104
"use client";
import Link from "next/link";
import * as nextNavigation from "next/navigation";
import {
createContext,
memo,
5369 unmodified lines
};
const useTerminalState = () => {
const pathname = nextNavigation.usePathname();
const routeFeatures = useMemo(() => getRouteFeatures(pathname), [pathname]);
const [mode, setMode] = useState<TapeMode>("live");
const [replaySource, setReplaySource] = useState<string | null>(null);
1844 unmodified lines
};
export const FlowFilterPopover = ({ filters, onChange }: FlowFilterPopoverProps) => {
const pathname = nextNavigation.usePathname();
const [open, setOpen] = useState(false);
const rootRef = useRef<HTMLDivElement | null>(null);
const activeCount = countActiveFlowFilterGroups(filters);
1863 unmodified lines
export function TerminalAppShell({ children }: { children: ReactNode }) {
const state = useTerminalState();
const pathname = nextNavigation.usePathname();
const [drawerOpen, setDrawerOpen] = useState(false);
const tickerFieldId = useId();
const tickerHintId = useId();

Expected Impact for End-Users

Forgejo should stop failing on the terminal screen's CI-only module resolution mismatch, which reduces false negative pipeline runs and makes it easier to trust the branch when the suite passes.

Validation

  • env PATH="$HOME/.bun/bin:/usr/bin:/bin" bun test apps/web/app/terminal.test.ts passed: 74 tests, 0 failures.
  • env PATH="$HOME/.bun/bin:/usr/bin:/bin" bun test passed: 250 tests, 0 failures.

Issues, Limitations, and Mitigations

This fix is intentionally narrow. If another CI-only Next.js import path shows up later, the same namespace-import pattern should be applied to the affected component or test file instead of broadening the mock surface globally. That keeps the failure signal honest and the test harness easy to reason about.

Follow-up Work

  • Watch the next Forgejo run on this branch to confirm the namespace import clears the last failure.
  • If another module-shape mismatch appears, fold the shared mock setup into a tiny helper rather than repeating the alias logic by hand.