web: fix OIDC navigation intercepted by the SPA router; tidy table layout

- Login/Logout anchors now carry rel="external": sycamore-router
  intercepts same-origin anchor clicks otherwise, so the OIDC login
  round-trip never left the SPA (found with a real browser test)
- Opponents' hidden hands render as a tidy 5x2 grid of card backs, seats
  have a fixed height and shrink-wrap so the table no longer jumps as
  cards are played
- Dockerfile: cache the Rust target dir so frontend rebuilds are fast
- ignore .playwright-mcp/ tooling output
This commit is contained in:
2026-09-16 13:20:06 +08:00
parent 96a95d74b6
commit 8414831849
4 changed files with 30 additions and 3 deletions
+3
View File
@@ -10,3 +10,6 @@ build/
# Rust build output
target/
# Playwright MCP tooling output
.playwright-mcp/
+1
View File
@@ -34,6 +34,7 @@ COPY web/assets ./assets
COPY web/src ./src
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/web/target \
trunk build --release
# --- Python builder ----------------------------------------------------------
+5 -2
View File
@@ -54,7 +54,10 @@ pub fn LobbyPage() -> View {
Some(None) => view! {
div(class="panel login-panel") {
p { "Log in with your account to play." }
a(class="button primary", href="/auth/login") { "Log in" }
// rel="external" opts out of the SPA router's click
// interception: this must be a full page navigation
// to the backend's OIDC login endpoint.
a(class="button primary", href="/auth/login", rel="external") { "Log in" }
}
},
Some(Some(me)) => view! {
@@ -63,7 +66,7 @@ pub fn LobbyPage() -> View {
span(class="whoami") { "Signed in as " strong { (me.name.clone()) } }
a(href="/history") { "My matches" }
a(href="/leaderboard") { "Leaderboard" }
a(href="/auth/logout") { "Log out" }
a(href="/auth/logout", rel="external") { "Log out" }
}
div(class="panel") {
h2 { "New match" }
+21 -1
View File
@@ -252,6 +252,13 @@ table.matches td.lost {
border-radius: 10px;
padding: 0.5rem 0.75rem;
text-align: center;
/* Fixed height keeps the table from jumping as cards are played. */
display: flex;
flex-direction: column;
justify-content: flex-start;
min-height: 116px;
align-self: center;
width: 100%;
}
.seat.active {
@@ -275,9 +282,19 @@ table.matches td.lost {
color: var(--muted);
}
/* Opponents' hidden hands: a tidy grid so the count reads at a glance. */
.seat-cards {
display: grid;
grid-template-columns: repeat(5, auto);
gap: 2px;
justify-content: center;
margin: 0.35rem 0;
}
.seat-stats {
font-size: 0.8rem;
color: var(--muted);
margin-top: auto;
}
.center {
@@ -287,6 +304,7 @@ table.matches td.lost {
align-items: center;
justify-content: center;
gap: 0.75rem;
min-height: 230px;
}
.table-cards {
@@ -313,7 +331,7 @@ table.matches td.lost {
}
.card-img.mini {
height: 46px;
height: 34px;
}
.card-img.move-card {
@@ -334,6 +352,8 @@ table.matches td.lost {
flex-wrap: wrap;
gap: 0.5rem;
justify-content: center;
align-items: flex-end;
min-height: 132px;
}
.hand-slot {