You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2026/09/18 14:33:09 UTC

(juneau) branch master updated: Give Juneau ownership of the gray .jc-main well, white content cards, and transparent table-chrome so apps stop forking those paints.

This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 961d1d9c3d Give Juneau ownership of the gray .jc-main well, white content cards, and transparent table-chrome so apps stop forking those paints.
961d1d9c3d is described below

commit 961d1d9c3d72e2eab904e7407e39a213ee281b7f
Author: James Bognar <ja...@apache.org>
AuthorDate: Fri Sep 18 10:33:02 2026 -0400

    Give Juneau ownership of the gray .jc-main well, white content cards, and transparent table-chrome so apps stop forking those paints.
---
 .../apache/juneau/rest/server/console/Theme.java   | 16 +++--
 .../resources/org/apache/juneau/console/chrome.css | 44 +++++++------
 .../server/console/ChromeCss_FullBleed_Test.java   | 10 +--
 .../server/console/ChromeCss_MainInner_Test.java   | 73 ++++++++++++++++++++++
 .../server/console/ConsoleChromeMixin_Test.java    | 18 ++----
 .../rest/server/console/Theme_Gray_Test.java       |  7 ++-
 .../rest/server/console/Theme_LightBrown_Test.java |  7 ++-
 .../rest/server/console/Theme_LightRed_Test.java   |  7 ++-
 .../juneau/rest/server/console/Theme_Red_Test.java |  4 +-
 .../org/apache/juneau/views/juneau-views.css       | 20 +++---
 .../rest/server/views/ViewsMixin_Serving_Test.java | 29 +++++----
 11 files changed, 164 insertions(+), 71 deletions(-)

diff --git a/juneau-rest/juneau-rest-server-console-ui/src/main/java/org/apache/juneau/rest/server/console/Theme.java b/juneau-rest/juneau-rest-server-console-ui/src/main/java/org/apache/juneau/rest/server/console/Theme.java
index a6f6314818..f9be75a3b3 100644
--- a/juneau-rest/juneau-rest-server-console-ui/src/main/java/org/apache/juneau/rest/server/console/Theme.java
+++ b/juneau-rest/juneau-rest-server-console-ui/src/main/java/org/apache/juneau/rest/server/console/Theme.java
@@ -106,7 +106,7 @@ public final class Theme {
 		.token("--jc-text-muted", "#706e6b")
 		.token("--jc-border", "#dddbda")
 		.token("--jc-border-2", "#ced4da")
-		.token("--jc-card-bg", "#f5f6f9")
+		.token("--jc-card-bg", "#ffffff")
 		.token("--jc-chrome-bg", "#f3f2f2")
 		.token("--jc-white", "#ffffff")
 		.token("--jc-btn-primary", "#1a5297")
@@ -159,6 +159,7 @@ public final class Theme {
 		// this token; it must be overridden on its own.  A permanent widening of the theming surface.
 		.token("--jc-accent-selected", "#1589EE")
 		.token("--jc-table-stripe-bg", "#fafaf9")
+		.token("--jc-main-bg", "#f5f6f9")  // inner-main well; not a retarget of old card tints
 		.build();
 
 	/**
@@ -169,7 +170,7 @@ public final class Theme {
 	 * every one of {@link #OPEN}'s semantic status/tag tokens (info blue, success green, danger red, warning amber,
 	 * neutral gray) verbatim &mdash; a brown "danger" pill would stop reading as danger, so the status palette is
 	 * deliberately untouched. Authored as {@link #deriveFrom(String, Theme) deriveFrom}({@link #OPEN}), overriding
-	 * only the 16 browned tokens below; the other 36 &mdash; including all five tag triads and every structural
+	 * only the 15 browned tokens below; the other 38 &mdash; including all five tag triads and every structural
 	 * token &mdash; are inherited from {@link #OPEN} unchanged, so this theme's token <i>key</i> set is provably
 	 * identical to {@link #OPEN}'s.
 	 *
@@ -189,7 +190,6 @@ public final class Theme {
 		.token("--jc-text-muted", "#7a6a4f")
 		.token("--jc-border", "#d8cbb0")
 		.token("--jc-border-2", "#c9b896")
-		.token("--jc-card-bg", "#fbf8f1")
 		.token("--jc-chrome-bg", "#efe6d4")
 		.token("--jc-btn-primary", "#8a6d3b")
 		.token("--jc-btn-primary-hover", "#74592f")
@@ -206,7 +206,7 @@ public final class Theme {
 	 * every one of {@link #OPEN}'s semantic status/tag tokens (info blue, success green, danger red, warning amber,
 	 * neutral gray) verbatim &mdash; a red "danger" pill that matched the brand would stop reading as danger, so
 	 * the status palette is deliberately untouched. Authored as {@link #deriveFrom(String, Theme) deriveFrom}({@link #OPEN}),
-	 * overriding only the 16 recolored tokens below; the other 36 &mdash; including all five tag triads and every
+	 * overriding only the 15 recolored tokens below; the other 38 &mdash; including all five tag triads and every
 	 * structural token &mdash; are inherited from {@link #OPEN} unchanged, so this theme's token <i>key</i> set is
 	 * provably identical to {@link #OPEN}'s.
 	 *
@@ -230,7 +230,6 @@ public final class Theme {
 		.token("--jc-text-muted", "#6e585c")
 		.token("--jc-border", "#e4d4d7")
 		.token("--jc-border-2", "#d4c2c6")
-		.token("--jc-card-bg", "#fbf8f9")
 		.token("--jc-chrome-bg", "#f3ecee")
 		.token("--jc-btn-primary", "#6B1D2A")
 		.token("--jc-btn-primary-hover", "#541620")
@@ -291,9 +290,9 @@ public final class Theme {
 	 * amber, neutral gray) <b>and</b> {@link #OPEN}'s blue interactive affordances (accent, link, primary button,
 	 * focus ring, avatar gradient) verbatim &mdash; a pure-grayscale accent would make links/buttons stop reading
 	 * as clickable, so the restrained blue affordance set is deliberately left untouched (this is the
-	 * "8 grayed / 44 kept" derivation; see the design item for the vetoed all-monochrome alternative). Authored
-	 * as {@link #deriveFrom(String, Theme) deriveFrom}({@link #OPEN}), overriding only the 8 grayed tokens below;
-	 * the other 44 &mdash; including all five tag triads, every structural token, and the blue affordance set
+	 * "7 grayed / 46 kept" derivation; see the design item for the vetoed all-monochrome alternative). Authored
+	 * as {@link #deriveFrom(String, Theme) deriveFrom}({@link #OPEN}), overriding only the 7 grayed tokens below;
+	 * the other 46 &mdash; including all five tag triads, every structural token, and the blue affordance set
 	 * &mdash; are inherited from {@link #OPEN} unchanged, so this theme's token <i>key</i> set is provably
 	 * identical to {@link #OPEN}'s.
 	 *
@@ -309,7 +308,6 @@ public final class Theme {
 		.token("--jc-text-muted", "#737373")
 		.token("--jc-border", "#e0e0e0")
 		.token("--jc-border-2", "#d0d0d0")
-		.token("--jc-card-bg", "#f7f7f7")
 		.token("--jc-chrome-bg", "#f4f4f4")
 		.build();
 
diff --git a/juneau-rest/juneau-rest-server-console-ui/src/main/resources/org/apache/juneau/console/chrome.css b/juneau-rest/juneau-rest-server-console-ui/src/main/resources/org/apache/juneau/console/chrome.css
index 320e01829d..c71ab12778 100644
--- a/juneau-rest/juneau-rest-server-console-ui/src/main/resources/org/apache/juneau/console/chrome.css
+++ b/juneau-rest/juneau-rest-server-console-ui/src/main/resources/org/apache/juneau/console/chrome.css
@@ -447,13 +447,15 @@ a:hover { text-decoration: underline; }
   flex: 1 1 auto;
   padding: 20px 24px 40px;
   min-width: 0;
+  background-color: var(--jc-main-bg);
+  margin: var(--jc-space-3);
+  border-radius: var(--jc-radius);
 }
 
 .jc-page-header {
   display: flex;
   align-items: baseline;
   justify-content: space-between;
-  max-width: 1180px;
   margin: 0 auto 14px;
   flex-wrap: wrap;
   gap: 10px;
@@ -486,7 +488,6 @@ a:hover { text-decoration: underline; }
    ========================================================================== */
 
 .jc-card {
-  max-width: 1180px;
   margin: 0 auto;
   background-color: var(--jc-card-bg);
   border: none;
@@ -512,29 +513,36 @@ a:hover { text-decoration: underline; }
 }
 
 /* ==========================================================================
-   TODO-445n full horizontal real estate (Goal 1)
+   Table-chrome detector (data-juneau-layout="wide")
    ==========================================================================
-   ViewTable stamps `data-juneau-layout="wide"` on the ONE stamp node it emits (its wrapper <div>).  This is a
-   first-class public data-juneau-* convention (allowed value: "wide"; never stamped on .jc-card / .jc-main — the
-   toolkit emits neither class).  The wide wrapper widens itself, and the enclosing card/main expand off it via
-   :has(), so a wide table page uses the full content-area width instead of the default centered 1180px card.
-
-   Position-dependent wide-stamp matrix:
-     - the wrapper itself           -> always widened
-     - .jc-card                     -> widened ONLY when the wide wrapper is a DIRECT child (typical after the RM
-                                       .rm-table-scroll unwrap)
-     - .jc-main                     -> widened when ANY descendant wrapper is wide
-   `.jc-main` keeps its padding; `max-width:none` means edge-of-content-area, not edge-of-viewport.
-   `min-width:0` (declared on .jc-main's single rule up in "Page scaffolding") lets the main region shrink below
-   the table's intrinsic width so the table-region scroll box (juneau-views.css), not the page, absorbs overflow
-   (INV-3). */
+   ViewTable stamps `data-juneau-layout="wide"` on the ONE stamp node it emits (its wrapper <div>).
+   This is a first-class public data-juneau-* convention (allowed value: "wide"; never stamped on
+   .jc-card / .jc-main — the toolkit emits neither class).  After cards fill the main well, the
+   stamp is the table-chrome detector, not a width punch-through: the outer .jc-card that contains
+   the stamp goes transparent and unpadded so Table Toolbar + Summary View sit on the gray well.
+   Nested .jc-card descendants stay content cards (`:not(.jc-card .jc-card)`).
+
+   Position-dependent matrix:
+     - the wrapper itself           -> still widened (self-match)
+     - outer .jc-card containing the stamp (HTML-slot: stamp may sit inside a slot <div>)
+                                    -> table-chrome (transparent, padding 0); descendant :has
+     - nested .jc-card              -> never table-chrome
+     - .jc-main                     -> max-width:none when ANY descendant wrapper is wide (harmless;
+                                       .jc-main has no max-width of its own)
+   `.jc-main` keeps its padding; `min-width:0` lets the main region shrink below the table's
+   intrinsic width so the table-region scroll box (juneau-views.css), not the page, absorbs overflow.
+*/
 
 [data-juneau-layout="wide"] {
   max-width: none;
   width: 100%;
 }
 
-.jc-card:has(> [data-juneau-layout="wide"]),
+.jc-card:has([data-juneau-layout="wide"]):not(.jc-card .jc-card) {
+  background-color: transparent;
+  padding: 0;
+}
+
 .jc-main:has([data-juneau-layout="wide"]) {
   max-width: none;
 }
diff --git a/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/ChromeCss_FullBleed_Test.java b/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/ChromeCss_FullBleed_Test.java
index dd8c0bdd0c..e636d17ce4 100644
--- a/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/ChromeCss_FullBleed_Test.java
+++ b/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/ChromeCss_FullBleed_Test.java
@@ -74,11 +74,13 @@ class ChromeCss_FullBleed_Test extends TestBase {
 		return css.substring(braceIdx + 1, endIdx < 0 ? css.length() : endIdx).contains(needle);
 	}
 
-	/** The ancestor card widens only when the wide wrapper is a DIRECT child (position-dependent matrix). */
-	@Test void a02_cardExpandsViaHasDirectChild() throws Exception {
+	/** Outer table-chrome is descendant :has plus :not nested .jc-card (HTML-slot stamp is not a direct child). */
+	@Test void a02_tableChrome_isDescendantHas_notDirectChildOnly() throws Exception {
 		var c = flat();
-		assertTrue(c.contains(".jc-card:has(> [data-juneau-layout=\"wide\"])"),
-			"the card must expand via :has(> [data-juneau-layout=\"wide\"]) - direct-child only (wide-stamp matrix)");
+		assertTrue(c.contains(".jc-card:has([data-juneau-layout=\"wide\"]):not(.jc-card .jc-card)"),
+			"outer table-chrome must be descendant :has + :not nested .jc-card");
+		assertFalse(c.contains(".jc-card:has(> [data-juneau-layout=\"wide\"])"),
+			"direct-child-only :has(>) misses HTML-slot and is not the detector");
 	}
 
 	/** The main region widens when any descendant wrapper is wide. */
diff --git a/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/ChromeCss_MainInner_Test.java b/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/ChromeCss_MainInner_Test.java
new file mode 100644
index 0000000000..21f33ac76e
--- /dev/null
+++ b/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/ChromeCss_MainInner_Test.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.server.console;
+
+import static java.nio.charset.StandardCharsets.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.*;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Pins the inner-main well, card fill-width, and outer table-chrome contracts in {@code chrome.css}.
+ */
+class ChromeCss_MainInner_Test extends TestBase {
+
+	private static String readChromeCss() throws IOException {
+		try (var in = ChromeCss_MainInner_Test.class.getResourceAsStream("/org/apache/juneau/console/chrome.css")) {
+			assertNotNull(in, "missing classpath resource: /org/apache/juneau/console/chrome.css");
+			return new String(in.readAllBytes(), UTF_8);
+		}
+	}
+
+	private static String flat() throws IOException {
+		return readChromeCss().replaceAll("\\s+", " ");
+	}
+
+	@Test void a01_jcMain_paintsMainBg_gutter_radius_noBorder() throws Exception {
+		var c = flat();
+		assertTrue(c.contains(".jc-main {"), ".jc-main rule missing");
+		var start = c.indexOf(".jc-main {");
+		var end = c.indexOf('}', start);
+		var body = c.substring(start, end);
+		assertTrue(body.contains("background-color: var(--jc-main-bg)"), body);
+		assertTrue(body.contains("margin: var(--jc-space-3)"), body);
+		assertTrue(body.contains("border-radius: var(--jc-radius)"), body);
+		assertFalse(body.contains("max-width:"), "Q4: no max-width on .jc-main: " + body);
+		assertFalse(body.contains("border:"), "Q4: no extra border on .jc-main: " + body);
+		assertTrue(body.contains("padding: 20px 24px 40px"), "keep today's inner padding: " + body);
+	}
+
+	@Test void a02_cardAndPageHeader_haveNo1180MaxWidth() throws Exception {
+		var css = readChromeCss();
+		assertFalse(css.contains("max-width: 1180px"), "Q5: drop max-width 1180px on .jc-card / .jc-page-header");
+	}
+
+	@Test void a03_tableChrome_transparentUnpadded_outerOnly() throws Exception {
+		var c = flat();
+		var sel = ".jc-card:has([data-juneau-layout=\"wide\"]):not(.jc-card .jc-card)";
+		assertTrue(c.contains(sel), "missing table-chrome selector");
+		var start = c.indexOf(sel);
+		var brace = c.indexOf('{', start);
+		var end = c.indexOf('}', brace);
+		var body = c.substring(brace, end);
+		assertTrue(body.contains("background-color: transparent"), body);
+		assertTrue(body.contains("padding: 0"), body);
+	}
+}
diff --git a/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/ConsoleChromeMixin_Test.java b/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/ConsoleChromeMixin_Test.java
index b03d06c3f2..08e59a454d 100644
--- a/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/ConsoleChromeMixin_Test.java
+++ b/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/ConsoleChromeMixin_Test.java
@@ -561,23 +561,17 @@ class ConsoleChromeMixin_Test extends TestBase {
 	}
 
 	@Test void j03_themeOpenTokenCount_pinned_unaffectedByAssetsFeature() {
-		// A0 must not add a --jc-logo or --jc-page-bg-image token - the logo/page-bg mechanism is deliberately
-		// NOT part of the Theme token model (finding 4 of the design doc). If this count ever changes, it must be
-		// a DIFFERENT, deliberate change to Theme.OPEN - not a side effect of the asset feature.
-		//
-		// 52 = the original 32, plus the three-token red tag triad, plus the fourteen additive
-		// token gaps (--jc-header-height, --jc-nav-indicator-width, --jc-page-nav-hairline,
-		// --jc-page-nav-section-font-size, --jc-page-nav-child-font-size, --jc-card-shadow,
-		// --jc-danger-wash, --jc-success-wash, and the six-step --jc-space-1..6 scale), plus the --jc-focus
-		// focus-ring colour, plus the --jc-accent-selected ribbon-format selected-state face (WCAG 1.4.11
-		// non-text contrast remedy), plus --jc-table-stripe-bg (#fafaf9; not aliased to --jc-card-bg).
-		// Bumping this number is only ever correct alongside a reviewed edit to Theme.OPEN itself.
-		assertEquals(52, Theme.OPEN.getTokens().size());
+		// 53 = previous 52 plus --jc-main-bg (inner-main well). --jc-card-bg is now
+		// content-card white; do not retarget old card tints onto --jc-main-bg.
+		assertEquals(53, Theme.OPEN.getTokens().size());
+		assertEquals("#f5f6f9", Theme.OPEN.getTokens().get("--jc-main-bg"));
+		assertEquals("#ffffff", Theme.OPEN.getTokens().get("--jc-card-bg"));
 		assertEquals("2px", Theme.OPEN.getTokens().get("--jc-page-nav-hairline"));
 		assertEquals("13px", Theme.OPEN.getTokens().get("--jc-page-nav-section-font-size"));
 		assertEquals("12px", Theme.OPEN.getTokens().get("--jc-page-nav-child-font-size"));
 		assertFalse(Theme.OPEN.getTokens().containsKey("--jc-logo"));
 		assertFalse(Theme.OPEN.getTokens().containsKey("--jc-page-bg-image"));
+		assertFalse(Theme.OPEN.getTokens().containsKey("--jc-card-surface"));
 	}
 
 	@Test void j04_accentSelectedFace_meetsWcag1411NonTextContrast_andBothSelectorsConsumeToken() throws Exception {
diff --git a/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/Theme_Gray_Test.java b/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/Theme_Gray_Test.java
index a1548a0440..55eed80f87 100644
--- a/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/Theme_Gray_Test.java
+++ b/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/Theme_Gray_Test.java
@@ -36,8 +36,8 @@ class Theme_Gray_Test extends TestBase {
 		assertEquals(Theme.OPEN.getTokens().keySet(), Theme.GRAY.getTokens().keySet());
 	}
 
-	@Test void a02_tokenCount_is52_mirroringOpensPin() {
-		assertEquals(52, Theme.GRAY.getTokens().size());
+	@Test void a02_tokenCount_is53_mirroringOpensPin() {
+		assertEquals(53, Theme.GRAY.getTokens().size());
 	}
 
 	@Test void a03_name_isGray() {
@@ -63,6 +63,9 @@ class Theme_Gray_Test extends TestBase {
 		assertEquals("#1589EE", tokens.get("--jc-accent"));
 		assertEquals(Theme.OPEN.getTokens().get("--jc-link"), tokens.get("--jc-link"));
 		assertEquals(Theme.OPEN.getTokens().get("--jc-btn-primary"), tokens.get("--jc-btn-primary"));
+		assertEquals(Theme.OPEN.getTokens().get("--jc-card-bg"), tokens.get("--jc-card-bg"));
+		assertEquals("#ffffff", tokens.get("--jc-card-bg"));
+		assertEquals(Theme.OPEN.getTokens().get("--jc-main-bg"), tokens.get("--jc-main-bg"));
 	}
 
 	@Test void b03_noVarReferencesLeak_everyValueIsAResolvedLiteral() {
diff --git a/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/Theme_LightBrown_Test.java b/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/Theme_LightBrown_Test.java
index ae6b3e3f7a..8382fe5531 100644
--- a/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/Theme_LightBrown_Test.java
+++ b/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/Theme_LightBrown_Test.java
@@ -36,8 +36,8 @@ class Theme_LightBrown_Test extends TestBase {
 		assertEquals(Theme.OPEN.getTokens().keySet(), Theme.LIGHT_BROWN.getTokens().keySet());
 	}
 
-	@Test void a02_tokenCount_is52_mirroringOpensPin() {
-		assertEquals(52, Theme.LIGHT_BROWN.getTokens().size());
+	@Test void a02_tokenCount_is53_mirroringOpensPin() {
+		assertEquals(53, Theme.LIGHT_BROWN.getTokens().size());
 	}
 
 	@Test void a03_name_isLightBrown() {
@@ -59,6 +59,9 @@ class Theme_LightBrown_Test extends TestBase {
 		assertEquals(Theme.OPEN.getTokens().get("--jc-success"), tokens.get("--jc-success"));
 		assertEquals(Theme.OPEN.getTokens().get("--jc-tag-green-bg"), tokens.get("--jc-tag-green-bg"));
 		assertEquals(Theme.OPEN.getTokens().get("--jc-tag-red-text"), tokens.get("--jc-tag-red-text"));
+		assertEquals(Theme.OPEN.getTokens().get("--jc-card-bg"), tokens.get("--jc-card-bg"));
+		assertEquals("#ffffff", tokens.get("--jc-card-bg"));
+		assertEquals(Theme.OPEN.getTokens().get("--jc-main-bg"), tokens.get("--jc-main-bg"));
 	}
 
 	@Test void b03_noVarReferencesLeak_everyValueIsAResolvedLiteral() {
diff --git a/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/Theme_LightRed_Test.java b/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/Theme_LightRed_Test.java
index 258a3fdc59..0a0d3b5f7f 100644
--- a/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/Theme_LightRed_Test.java
+++ b/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/Theme_LightRed_Test.java
@@ -36,8 +36,8 @@ class Theme_LightRed_Test extends TestBase {
 		assertEquals(Theme.OPEN.getTokens().keySet(), Theme.LIGHT_RED.getTokens().keySet());
 	}
 
-	@Test void a02_tokenCount_is52_mirroringOpensPin() {
-		assertEquals(52, Theme.LIGHT_RED.getTokens().size());
+	@Test void a02_tokenCount_is53_mirroringOpensPin() {
+		assertEquals(53, Theme.LIGHT_RED.getTokens().size());
 	}
 
 	@Test void a03_name_isLightRed() {
@@ -51,6 +51,9 @@ class Theme_LightRed_Test extends TestBase {
 		assertEquals("#6B1D2A", tokens.get("--jc-btn-primary"));
 		assertEquals("#f3ecee", tokens.get("--jc-chrome-bg"));
 		assertTrue(tokens.get("--jc-page-bg").contains("#e8cfd3"), () -> "expected the light-red gradient, got: " + tokens.get("--jc-page-bg"));
+		assertEquals(Theme.OPEN.getTokens().get("--jc-card-bg"), tokens.get("--jc-card-bg"));
+		assertEquals("#ffffff", tokens.get("--jc-card-bg"));
+		assertEquals(Theme.OPEN.getTokens().get("--jc-main-bg"), tokens.get("--jc-main-bg"));
 	}
 
 	@Test void b02_keptFromOpen_statusAndTagValues_areUnchanged() {
diff --git a/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/Theme_Red_Test.java b/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/Theme_Red_Test.java
index bfbd3f7fe6..47422a8842 100644
--- a/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/Theme_Red_Test.java
+++ b/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/Theme_Red_Test.java
@@ -36,8 +36,8 @@ class Theme_Red_Test extends TestBase {
 		assertEquals(Theme.OPEN.getTokens().keySet(), Theme.RED.getTokens().keySet());
 	}
 
-	@Test void a02_tokenCount_is52_mirroringOpensPin() {
-		assertEquals(52, Theme.RED.getTokens().size());
+	@Test void a02_tokenCount_is53_mirroringOpensPin() {
+		assertEquals(53, Theme.RED.getTokens().size());
 	}
 
 	@Test void a03_name_isRed() {
diff --git a/juneau-rest/juneau-rest-server-views/src/main/resources/org/apache/juneau/views/juneau-views.css b/juneau-rest/juneau-rest-server-views/src/main/resources/org/apache/juneau/views/juneau-views.css
index 2f8ce3b950..f037a31c1e 100644
--- a/juneau-rest/juneau-rest-server-views/src/main/resources/org/apache/juneau/views/juneau-views.css
+++ b/juneau-rest/juneau-rest-server-views/src/main/resources/org/apache/juneau/views/juneau-views.css
@@ -1571,19 +1571,17 @@ div.dataTables_wrapper div.dataTables_filter input:focus-visible {
  * The DataTables-native child row created by `row.child(panel).show()` (juneau-views.js) - class "child", per
  * DataTables' own row.child() API, not one of this file's own "juneau-view-detail-*" names: Juneau never gets a
  * handle on this <tr>/<td> to stamp its own class onto it, so it has to be reached by DataTables' own default
- * shape instead.  Explicit opaque white (WORK-J0518 DF-12.1): without a background of its own the row/cell is
- * transparent, so ANY ancestor row styling - a zebra stripe, an "open" indicator, a hover wash on the row that
- * owns the expander - paints straight through it, including while that same row's hover state is still live.
- * The same "opaque floating/panel surface" exception this file already grants `.juneau-view-dialog` /
- * `.jc-cell-popover` / `.juneau-ts-popup`, extended to the one row DataTables itself creates rather than one
- * this file's own emit owns.
+ * shape instead.  Opaque content-card fill (`--jc-card-bg`) plus `padding: 0` so ancestor row colouring (a zebra
+ * stripe, an "open" indicator, a hover wash) cannot show through, and the gray main well cannot halo the
+ * expanded panel.
  */
 table[data-juneau-view] > tbody > tr.child,
 table[data-juneau-view] > tbody > tr.child > td,
 table.dataTable > tbody > tr.child,
 table.dataTable > tbody > tr.child > td {
-	background: #fff;
+	background-color: var(--jc-card-bg);
 	border: none;
+	padding: 0;
 }
 
 /* The expanded detail body itself - DataTables' native child row (juneau-views.js `row.child(...).show()`).
@@ -1591,7 +1589,13 @@ table.dataTable > tbody > tr.child > td {
 .juneau-view-detail-panel {
 	margin: 0;
 	padding: var(--jc-space-2) var(--jc-space-3);
-	background: #fff;
+	background-color: var(--jc-card-bg);
+}
+
+.juneau-view-detail-panel .jc-card,
+table[data-juneau-view] > tbody > tr.child .jc-card,
+table.dataTable > tbody > tr.child .jc-card {
+	margin: 0;
 }
 
 .juneau-view-detail-section {
diff --git a/juneau-rest/juneau-rest-server-views/src/test/java/org/apache/juneau/rest/server/views/ViewsMixin_Serving_Test.java b/juneau-rest/juneau-rest-server-views/src/test/java/org/apache/juneau/rest/server/views/ViewsMixin_Serving_Test.java
index 81e7afea1e..ab79634850 100644
--- a/juneau-rest/juneau-rest-server-views/src/test/java/org/apache/juneau/rest/server/views/ViewsMixin_Serving_Test.java
+++ b/juneau-rest/juneau-rest-server-views/src/test/java/org/apache/juneau/rest/server/views/ViewsMixin_Serving_Test.java
@@ -1189,13 +1189,10 @@ class ViewsMixin_Serving_Test extends TestBase {
 	}
 
 	/**
-	 * DF-12.1: the DataTables-native child row (row.child(...).show()) and this module's own detail panel are
-	 * explicit opaque white, so no ancestor row colouring (a zebra stripe, an "open" indicator, a parent-row
-	 * hover wash) can show through the expanded surface - the same "opaque floating/panel surface" exception
-	 * this file already grants the dialog/popover/timestamp-popup families, extended to the row DataTables
-	 * itself creates.
+	 * The expanded-row Detail View is a flush content card: {@code --jc-card-bg} fill, inner padding,
+	 * {@code margin: 0}, and child-row cells with {@code padding: 0} so the gray well cannot halo the panel.
 	 */
-	@Test void p02_viewsCss_detailExpanderSurfaceIsOpaqueWhite() throws Exception {
+	@Test void p02_viewsCss_detailExpanderSurfaceIsOpaqueContentCard() throws Exception {
 		var body = cWithMixin.get(ViewsMixin.VIEWS_CSS_PATH).run().assertStatus(200).getContent().asString();
 		assertTrue(body.contains("table[data-juneau-view] > tbody > tr.child,\n"
 			+ "table[data-juneau-view] > tbody > tr.child > td,\n"
@@ -1203,16 +1200,24 @@ class ViewsMixin_Serving_Test extends TestBase {
 			+ "table.dataTable > tbody > tr.child > td {"), body);
 		var start = body.indexOf("table[data-juneau-view] > tbody > tr.child,");
 		var end = body.indexOf("}", start);
-		assertTrue(body.substring(start, end).contains("background: #fff"), body);
-		assertTrue(body.substring(start, end).contains("border: none"),
-			() -> "expander child cells must not carry the summary-view grid: " + body.substring(start, end));
+		var child = body.substring(start, end);
+		assertTrue(child.contains("background-color: var(--jc-card-bg)"), child);
+		assertTrue(child.contains("padding: 0"), child);
+		assertTrue(child.contains("border: none"), child);
 
-		// ".juneau-view-detail-panel {" appears TWICE (the container-query host near the top of the file, and
-		// the expanded-body rule this test targets) - anchor on the body rule's own first declaration.
 		assertTrue(body.contains(".juneau-view-detail-panel {\n\tmargin: 0;"), body);
 		var panelStart = body.indexOf(".juneau-view-detail-panel {\n\tmargin: 0;");
 		var panelEnd = body.indexOf("}", panelStart);
-		assertTrue(body.substring(panelStart, panelEnd).contains("background: #fff"), body);
+		var panel = body.substring(panelStart, panelEnd);
+		assertTrue(panel.contains("background-color: var(--jc-card-bg)"), panel);
+		assertFalse(panel.contains("background: #fff"), panel);
+
+		assertTrue(body.contains(".juneau-view-detail-panel .jc-card"),
+			"nested .jc-card inside the detail panel must be pinned");
+		var nestedStart = body.indexOf(".juneau-view-detail-panel .jc-card");
+		var nestedEnd = body.indexOf("}", nestedStart);
+		assertTrue(body.substring(nestedStart, nestedEnd).contains("margin: 0"),
+			body.substring(nestedStart, nestedEnd));
 	}
 
 	//------------------------------------------------------------------------------------------------------------------