From 01c7ca0b2f10222615188c9dadfbfcf8f9102d90 Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Sat, 30 May 2026 01:58:37 -0400 Subject: [PATCH] fix terminal pathname import for forgejo --- apps/web/app/terminal.tsx | 8 +- .../2026-05-30-fix-forgejo-ci-test-mocks.html | 73 ++++++++----------- 2 files changed, 33 insertions(+), 48 deletions(-) diff --git a/apps/web/app/terminal.tsx b/apps/web/app/terminal.tsx index 5375688..4c6082f 100644 --- a/apps/web/app/terminal.tsx +++ b/apps/web/app/terminal.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; -import { usePathname } from "next/navigation"; +import * as nextNavigation from "next/navigation"; import { createContext, memo, @@ -5377,7 +5377,7 @@ export const parseTickerFilterInput = (value: string): string[] => { }; const useTerminalState = () => { - const pathname = usePathname(); + const pathname = nextNavigation.usePathname(); const routeFeatures = useMemo(() => getRouteFeatures(pathname), [pathname]); const [mode, setMode] = useState("live"); const [replaySource, setReplaySource] = useState(null); @@ -7228,7 +7228,7 @@ const FlowFilterSection = ({ }; export const FlowFilterPopover = ({ filters, onChange }: FlowFilterPopoverProps) => { - const pathname = usePathname(); + const pathname = nextNavigation.usePathname(); const [open, setOpen] = useState(false); const rootRef = useRef(null); const activeCount = countActiveFlowFilterGroups(filters); @@ -9098,7 +9098,7 @@ function SyntheticControlDock() { export function TerminalAppShell({ children }: { children: ReactNode }) { const state = useTerminalState(); - const pathname = usePathname(); + const pathname = nextNavigation.usePathname(); const [drawerOpen, setDrawerOpen] = useState(false); const tickerFieldId = useId(); const tickerHintId = useId(); diff --git a/docs/turns/2026-05-30-fix-forgejo-ci-test-mocks.html b/docs/turns/2026-05-30-fix-forgejo-ci-test-mocks.html index 4931497..72ea52d 100644 --- a/docs/turns/2026-05-30-fix-forgejo-ci-test-mocks.html +++ b/docs/turns/2026-05-30-fix-forgejo-ci-test-mocks.html @@ -122,79 +122,62 @@
Turn document

Fix Forgejo CI terminal test mock alias

-

The remaining Forgejo-only failure was a Next.js module-shape mismatch in the terminal test. I taught the test harness to mock both the bare next/navigation specifier and the resolved next/navigation.js path so Forgejo can import the same named exports the local suite already accepts.

+

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:53 EDT + 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:53 EDT

-

This update builds on the earlier Bun PATH and redirect-mock fixes. Forgejo was still resolving the Next.js navigation module through the explicit .js path, so the test harness now mocks both the specifier and the resolved path before the terminal module loads.

+

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

    -
  • Wrapped the Next.js navigation stubs in a shared mock object in apps/web/app/terminal.test.ts.
  • -
  • Added explicit mocks for both import.meta.resolve("next/navigation") and import.meta.resolve("next/navigation.js").
  • -
  • Kept the redirect shim and usePathname stub identical across every module entry point Forgejo might choose.
  • +
  • Changed apps/web/app/terminal.tsx to import next/navigation as a namespace.
  • +
  • Replaced the three direct usePathname() calls with nextNavigation.usePathname().
  • +
  • Left the earlier test mocks in place so the suite still covers both the package specifier and Bun's resolved path.

Why this change was made

-

The previous mock covered the string specifier, but Forgejo's Bun runtime still resolved the explicit .js entry point in the test job. Without the resolved-path aliases, Bun reported a missing named export and aborted the file before the assertions could run.

+

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

-
const nextNavigationMock = {
-  default: {
-    redirect,
-    usePathname: () => "/options"
-  },
-  redirect,
-  usePathname: () => "/options"
-};
-
-const nextNavigationResolved = import.meta.resolve("next/navigation");
-const nextNavigationJsResolved = import.meta.resolve("next/navigation.js");
-
-mock.module(nextNavigationResolved, () => ({
-  ...nextNavigationMock
-}));
-mock.module(nextNavigationJsResolved, () => ({
-  ...nextNavigationMock
-}));
+
import * as nextNavigation from "next/navigation";
         

Related issues or PRs

islandflow-3l6

Summary

-

The remaining Forgejo failure was inside the web test suite, not the install or typecheck stages. The terminal test needed to mock the Next.js navigation module under both import paths, so the final change keeps the CI runner from tripping over a named export mismatch.

+

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.test.ts to mock next/navigation.js in addition to next/navigation.
  • -
  • Kept the redirect shim and pathname stub aligned between both module shapes.
  • +
  • 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 last failure surfaced only in the full CI-shaped test run, where Bun resolved the terminal module through next/navigation.js rather than the shorter specifier used in the local test path.

+

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 alias returns the same mock object for both module entry points, so the terminal module sees a consistent redirect helper and pathname stub regardless of the import path Bun chooses.
  • -
  • This stays narrowly scoped to the test file and does not change production routing code.
  • -
  • The fix addresses the exact CI import shape instead of widening the test harness in a way that could hide future regressions.
  • +
  • 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 shared Next.js navigation mock plus the explicit resolved-path aliases that keep Forgejo aligned with the local Bun runtime.

+

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.test.ts
-5+17
4 unmodified lines
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
4 unmodified lines
throw new Error(`NEXT_REDIRECT:${path}`);
});
-
mock.module("next/navigation", () => ({
redirect,
usePathname: () => "/options"
}));
mock.module("next/navigation.js", () => ({
default: {
redirect,
usePathname: () => "/options"
},
redirect,
usePathname: () => "/options"
}));
-
const {
4 unmodified lines
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
4 unmodified lines
throw new Error(`NEXT_REDIRECT:${path}`);
});
-
const nextNavigationMock = {
default: {
redirect,
usePathname: () => "/options"
},
redirect,
usePathname: () => "/options"
};
-
const nextNavigationResolved = import.meta.resolve("next/navigation");
const nextNavigationJsResolved = import.meta.resolve("next/navigation.js");
-
mock.module("next/navigation", () => ({
...nextNavigationMock
}));
mock.module("next/navigation.js", () => ({
...nextNavigationMock
}));
mock.module(nextNavigationResolved, () => ({
...nextNavigationMock
}));
mock.module(nextNavigationJsResolved, () => ({
...nextNavigationMock
}));
-
const {
+}
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 test's CI-only module resolution mismatch, which reduces false negative pipeline runs and makes it easier to trust the branch when the suite passes.

+

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.

@@ -281,13 +266,13 @@ mock.module(nextNavigationJsResolved, () => ({

Issues, Limitations, and Mitigations

-

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

+

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 updated terminal alias clears the last failure.
  • +
  • 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.