diff --git a/app/rules/coverage/page.tsx b/app/rules/coverage/page.tsx
index f41bffea..d46d71a7 100644
--- a/app/rules/coverage/page.tsx
+++ b/app/rules/coverage/page.tsx
@@ -1,8 +1,7 @@
-import Link from 'next/link';
export default function Rules() {
return (
- ← Return to the table
+ ← Return to the table
Rules coverage ledger
This project targets classic Gale Force Nine Dune, the advanced game,
diff --git a/components/bribes.tsx b/components/bribes.tsx
index 1b692972..e1399396 100644
--- a/components/bribes.tsx
+++ b/components/bribes.tsx
@@ -1,6 +1,5 @@
'use client';
import { useId, useState } from 'react';
-import Link from 'next/link';
import type { Action, GameView } from '@/game/engine';
import { bribeAction } from '@/game/bribe-options';
import { faction } from '@/game/catalog';
@@ -47,7 +46,7 @@ export function Bribes({ game: g, act, busy }: {
{recipient ? `You may pay up to ${recipient.maximum} spice to this recipient.` : `Uncommitted spice: ${options.available}. Choose a recipient to see the payment limit.`}
Eligible Bureaucrats may divert two spice to the Bank. AI opponents can receive spice, but do not negotiate deals yet. Bribe rules
+
Eligible Bureaucrats may divert two spice to the Bank. AI opponents can receive spice, but do not negotiate deals yet. Bribe rules
);
}
diff --git a/components/draw-piles.tsx b/components/draw-piles.tsx
index c4772941..dcd876e9 100644
--- a/components/draw-piles.tsx
+++ b/components/draw-piles.tsx
@@ -1,4 +1,3 @@
-import Link from 'next/link';
/** Counts only: this component never receives deck contents or private cards. */
export function DrawPiles({ piles }: {
@@ -19,7 +18,7 @@ export function DrawPiles({ piles }: {
Draw piles exclude hands, auction cards and discards.
{(piles.treachery === 0 || piles.spice === 0) && ' An empty pile may be replenished from eligible discards.'}
- {' '}About these piles
+ {' '}About these piles
);
diff --git a/components/ecaz-loyalty.tsx b/components/ecaz-loyalty.tsx
index 7c2dc655..a698d9bf 100644
--- a/components/ecaz-loyalty.tsx
+++ b/components/ecaz-loyalty.tsx
@@ -1,4 +1,3 @@
-import Link from 'next/link';
import { leaders } from '@/game/cards';
import type { GameView } from '@/game/engine';
import { LeaderInspector } from './leader-inspector';
@@ -16,7 +15,7 @@ export function EcazLoyaltyCard({ loyalty }: { loyalty: GameView['ecazLoyalty']
This Ecaz Traitor Card was randomly set aside face up before dealing. It stays outside the deck for this game and cannot be drawn as a Traitor or Face Dancer. Its leader remains in play under the normal leader rules.
;
diff --git a/components/faction-inspector.tsx b/components/faction-inspector.tsx
index 7242bdbd..5018e9f3 100644
--- a/components/faction-inspector.tsx
+++ b/components/faction-inspector.tsx
@@ -2,7 +2,6 @@
import { useState } from 'react';
import Image from 'next/image';
-import Link from 'next/link';
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog';
import { Eye, X } from 'lucide-react';
import { FACTIONS, faction, type FactionId } from '@/game/catalog';
@@ -34,8 +33,8 @@ export function FactionSheet({ factionId, advanced }: { factionId: FactionId; ad
}
;
}
@@ -64,7 +63,7 @@ export function FactionInspector({ factionId, advanced }: { factionId: FactionId
{house.name} faction sheetRead public faction powers and alliance guidance without changing the game.
- Faction sheet help
+ Faction sheet help
diff --git a/components/force-inspector.tsx b/components/force-inspector.tsx
index 2da624c4..7c9e4581 100644
--- a/components/force-inspector.tsx
+++ b/components/force-inspector.tsx
@@ -1,7 +1,6 @@
'use client';
import { useState, type CSSProperties } from 'react';
-import Link from 'next/link';
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog';
import { Eye, Star, X } from 'lucide-react';
import { FACTIONS, faction, type FactionId } from '@/game/catalog';
@@ -64,7 +63,7 @@ export function ForceInventoryDetails({ player, worlds = null }: { player: Publi
Concealed No-Field
{inventory.marker.name}
Counts as one force for board presence until revealed. It is a separate marker, not an extra physical force counter. Its hidden force value is not shown here. Revelation draws from the reserves available then.
}
Inspecting counters does not move them or commit a Battle Plan. Spice support, temporary powers and leaders may change battle strength without adding counters.
- Force counter guide
+ Force counter guide
;
}
diff --git a/components/game-table.tsx b/components/game-table.tsx
index 9e3f8410..b3a29ada 100644
--- a/components/game-table.tsx
+++ b/components/game-table.tsx
@@ -137,7 +137,6 @@ import {
import { validBattleSlotPair } from '@/game/battle-card-slots';
import { stoneBurnerPlanBlock } from '@/game/stone-burner';
import { TECH_TOKENS, ownedTech } from '@/game/tech-tokens';
-import Link from 'next/link';
import {
canUseAsKaramaRole,
canUseAsTruthtranceRole,
@@ -2627,9 +2626,9 @@ export function GameTable({
comparison. Other weapon effects and traitor precedence
still apply.
-
+
Read Stone Burner rules
-
+
{actionButton('Kill both leaders', {
type: 'decision',
event: g.decision.event,
@@ -3286,13 +3285,13 @@ export function GameTable({
What happens in this phase?
{PHASE_HELP[g.phase]}
-
Open phase guide ↗
-
+
@@ -5529,9 +5528,9 @@ export function GameTable({
several special cards, optional faction reactions, ally support,
auction deadlines, and a printed-board connectivity audit.
-
+
Open the rules coverage ledger
-
+
)}
diff --git a/components/hand-browser.tsx b/components/hand-browser.tsx
index 07c95acd..4274d505 100644
--- a/components/hand-browser.tsx
+++ b/components/hand-browser.tsx
@@ -1,6 +1,5 @@
'use client';
import { Fragment, useId, useState, type ReactNode } from 'react';
-import Link from 'next/link';
import type { Card } from '@/game/cards';
import { browseHand, type HandCategory, type HandSort } from '@/game/hand-browsing';
import { Button } from './ui/button';
@@ -48,7 +47,7 @@ export function HandBrowser({ cards, empty, children }: {
diff --git a/components/introduction.tsx b/components/introduction.tsx
index d052fe15..09066ef4 100644
--- a/components/introduction.tsx
+++ b/components/introduction.tsx
@@ -1,6 +1,5 @@
'use client';
-import Link from 'next/link';
import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
import { BattleWheel } from './battle-wheel';
import { CardInspector, CardRules } from './card-inspector';
@@ -25,7 +24,7 @@ export function Introduction() {
const hydrated = useSyncExternalStore(subscribeHydration, clientSnapshot, serverSnapshot);
return
- ← Return to Arrakis
+ ← Return to Arrakis
LEARN TO PLAY · BASIC GAME
Your first steps on Arrakis
Practice the Basic phases, from the storm and spice blow to alliances, resources, bidding, movement, battle and collection, then take a seat at a real table. These are separate examples; they do not change any saved game.
@@ -164,11 +163,11 @@ function IntroductionLessons() {
Use Protect your saved seat and keep its recovery kit private. An invitation identifies the room; the kit restores your seat after cookie loss.
Refresh restores a live game through its saved seat. If a request has an uncertain result, use the offered retry rather than repeating the game action.
-
Create or join a game →Explore the full reference →
diff --git a/components/truthtrance.tsx b/components/truthtrance.tsx
index e767150a..fd5266b4 100644
--- a/components/truthtrance.tsx
+++ b/components/truthtrance.tsx
@@ -7,7 +7,6 @@ import { ForceCountFields, forceCountInputError } from './truthtrance-force-coun
import { isKnowledgeFact, knowledgeFactInputError } from '@/game/truthtrance-knowledge';
import type { PlanClaim } from '@/game/battle-promises';
import { useId, useState } from 'react';
-import Link from 'next/link';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { CHEAP_HERO_TRAITOR } from '@/game/traitors';
@@ -130,7 +129,7 @@ export function Truthtrance({
Truthtrance
- Question rules · The table
+ Question rules · The table
resumes its previous decision after these questions.
{w.stage === 'priority' ? (
diff --git a/docs/TRUENAS.md b/docs/TRUENAS.md
index 015675c2..655e103b 100644
--- a/docs/TRUENAS.md
+++ b/docs/TRUENAS.md
@@ -52,6 +52,16 @@ public hosting remains separately gated. HTTP does not encrypt cookies or seat
recovery kits. Browser operation IDs now use cryptographic `getRandomValues`,
which also works at LAN HTTP origins where `randomUUID` is unavailable.
+## Production navigation
+
+Use native anchors for app page links. The pinned Vinext/Vite production build
+renames navigation exports while its Link runtime dynamically looks up their
+original names, breaking clicks and prefetch despite successful HTTP responses.
+Native navigation keeps query strings, rule anchors, browser history and saved
+seat cookies. The Next.js lint preference for client-side Link is disabled for
+this intentional choice. Verify page links in the production browser when
+changing framework versions; development navigation alone does not cover it.
+
## Update and preserve games
### Existing NGINX HTTPS termination