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

xiazcy pushed a commit to branch 3.7-dev
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git


The following commit(s) were added to refs/heads/3.7-dev by this push:
     new 76b06f71ba Add toy-graph dataset captions to rendered Gremlin examples 
CTR
76b06f71ba is described below

commit 76b06f71bad4be8bcadec908fa1da17ff18adee6
Author: Yang Xia <[email protected]>
AuthorDate: Wed Aug 12 16:51:39 2026 -0700

    Add toy-graph dataset captions to rendered Gremlin examples CTR
    
    Render an italic caption beneath each Gremlin example naming the toy graph 
it runs against, linking to that graph's section in the Sample Data book in the 
HTML output and emitting a machine-parseable `graph-dataset:` line in the 
Markdown mirror. Resolve the dataset from the AST walk rather than execution 
state.
    
    Introduce GraphCatalog as the single source of truth for toy-graph tokens, 
replacing the duplicated GRAPH_INIT and GRAPH_DOC maps so adding a dataset is a 
one-line change.
    
    Also simplify the console tab label from "console (groovy)" to "console" so 
it renders at the same size as the other single-word tabs.
    
    Assisted-by: Kiro:claude-opus-4.8
---
 docs/sass/tabs.scss                                | 53 +++++++++++-
 docs/stylesheets/tinkerpop.css                     |  6 +-
 .../apache/tinkerpop/tinkeradoc/GraphCatalog.java  | 94 ++++++++++++++++++++++
 .../tinkerpop/tinkeradoc/GremlinTreeprocessor.java | 50 ++++++------
 .../tinkerpop/tinkeradoc/HtmlTabRenderer.java      | 16 +++-
 .../tinkerpop/tinkeradoc/MarkdownTabRenderer.java  | 15 ++++
 .../apache/tinkerpop/tinkeradoc/NeutralTab.java    |  6 +-
 .../tinkerpop/tinkeradoc/TabbedHtmlBuilder.java    | 47 ++++++++++-
 .../tinkeradoc/DualBackendIntegrationTest.java     |  2 +-
 .../tinkerpop/tinkeradoc/GraphCatalogTest.java     | 65 +++++++++++++++
 .../tinkeradoc/GremlinTreeprocessorTest.java       | 66 ++++++++++++++-
 .../tinkerpop/tinkeradoc/HtmlTabRendererTest.java  |  4 +-
 .../tinkerpop/tinkeradoc/IntegrationTest.java      |  2 +-
 .../tinkeradoc/MarkdownTabRendererTest.java        | 18 ++++-
 .../tinkeradoc/TabbedHtmlBuilderTest.java          | 33 +++++++-
 15 files changed, 430 insertions(+), 47 deletions(-)

diff --git a/docs/sass/tabs.scss b/docs/sass/tabs.scss
index 1541fd19fc..13811e5bcf 100644
--- a/docs/sass/tabs.scss
+++ b/docs/sass/tabs.scss
@@ -114,6 +114,13 @@ $maxTabs: 7;
   }
 }
 
+// At the tightest tab counts the columns get narrow, so trim horizontal 
padding to keep a long
+// single-word label (e.g. "javascript") from overflowing its tab.
+.tabs-6 label,
+.tabs-7 label {
+  padding: 0 8px;
+}
+
 .tabs label:first-of-type {
   z-index: 4;
 }
@@ -173,6 +180,50 @@ $maxTabs: 7;
     height: auto;
     width: 100%;
     height: auto;
-    padding-top: 30px;
+    // Uniform gray frame: equal gutter on all four sides around the code 
panel. The base
+    // stylesheet's side (pre margins) and bottom (listingblock margin) 
gutters are zeroed under
+    // .tabs below so this padding is the sole gutter, and the caption is 
overlaid in it.
+    padding: 30px;
+  }
+}
+
+// Zero the base-stylesheet code-block gutters inside tabs so the uniform 30px 
panel padding is the
+// only gray frame (otherwise the sides would be 30px + 2em and the bottom 
30px + 1.25em).
+.tabs .tabcontent .listingblock {
+  margin-bottom: 0;
+}
+.tabs .tabcontent .listingblock > .content > pre {
+  margin-left: 0;
+  margin-right: 0;
+}
+
+// Graph-dataset caption for Gremlin examples: an italic line naming the toy 
graph the example runs
+// against, with a link to its Sample Data book section. Absolutely positioned 
in the bottom gray
+// gutter of the .tabcontent box: right-aligned to the code panel's right edge 
and vertically
+// centered in the 30px gutter. Being out of flow, it never adds to the box 
height, so the gray
+// frame stays uniform on all four sides. The 
position/top/left/width/height/opacity declarations
+// override the `.tabcontent > div` rule that collapses and hides inactive 
panels.
+.tabs .graph-dataset-note {
+  position: absolute;
+  top: auto;
+  left: auto;
+  right: 30px;
+  bottom: 0;
+  width: auto;
+  height: 30px;
+  line-height: 30px;
+  opacity: 1;
+  z-index: 101;
+  padding: 0;
+  font-size: 13px;
+  font-style: italic;
+  color: #555;
+  text-align: right;
+
+  a {
+    // Intentionally darker than the tab accent $active (#609060) for legible 
contrast on the
+    // light-gray caption. This is caption-specific; do not replace it with 
$active.
+    color: #4a704a;
+    text-decoration: underline;
   }
 }
diff --git a/docs/stylesheets/tinkerpop.css b/docs/stylesheets/tinkerpop.css
index 71cc47eeec..f7495b2477 100644
--- a/docs/stylesheets/tinkerpop.css
+++ b/docs/stylesheets/tinkerpop.css
@@ -692,4 +692,8 @@ table.tableblock.grid-all th.tableblock, 
table.tableblock.grid-all td.tableblock
 #footer { background-color: #465158; padding: 2em; }
 
 #footer-text { color: #eee; font-size: 0.8em; text-align: center; }
-.tabs{position:relative;margin:40px 
auto;width:1024px;max-width:100%;overflow:hidden;padding-top:10px;margin-bottom:60px}.tabs
 
input{position:absolute;z-index:1000;height:50px;left:0;top:0;opacity:0;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";filter:alpha(opacity=0);cursor:pointer;margin:0}.tabs
 input:hover+label{background:#e08f24}.tabs 
label{background:#e9ffe9;color:#1a1a1a;font-size:15px;line-height:50px;height:60px;position:relative;top:0;padding:0
 20px;float:left [...]
+.tabs{position:relative;margin:40px 
auto;width:1024px;max-width:100%;overflow:hidden;padding-top:10px;margin-bottom:60px}.tabs
 
input{position:absolute;z-index:1000;height:50px;left:0;top:0;opacity:0;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";filter:alpha(opacity=0);cursor:pointer;margin:0}.tabs
 input:hover+label{background:#e08f24}.tabs 
label{background:#e9ffe9;color:#1a1a1a;font-size:15px;line-height:50px;height:60px;position:relative;top:0;padding:0
 20px;float:left [...]
+
+.tabs 
.graph-dataset-note{position:absolute;top:auto;left:auto;right:30px;bottom:0;width:auto;height:30px;line-height:30px;opacity:1;z-index:101;padding:0;font-size:13px;font-style:italic;color:#555;text-align:right}.tabs
 .graph-dataset-note a{color:#4a704a;text-decoration:underline}.tabs 
.tabcontent .listingblock{margin-bottom:0}.tabs .tabcontent 
.listingblock>.content>pre{margin-left:0;margin-right:0}
+
+.tabs-6 label,.tabs-7 label{padding:0 8px}
diff --git 
a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/GraphCatalog.java
 
b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/GraphCatalog.java
new file mode 100644
index 0000000000..94db100ab3
--- /dev/null
+++ 
b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/GraphCatalog.java
@@ -0,0 +1,94 @@
+/*
+ * 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.tinkerpop.tinkeradoc;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Canonical registry of the toy graph datasets referenced by Gremlin 
documentation examples.
+ * <p>
+ * This is the single source of truth shared by two concerns that must never 
disagree about the set
+ * of datasets:
+ * <ul>
+ *   <li><b>execution</b> — the Gremlin statement run to build the graph 
before an example
+ *       ({@link GremlinTreeprocessor}); and</li>
+ *   <li><b>rendering</b> — the dataset caption shown beneath an example and 
its link to the
+ *       Sample Data book section ({@link TabbedHtmlBuilder}).</li>
+ * </ul>
+ * Adding a graph is therefore a one-line change here that feeds both, so a 
new dataset can never be
+ * executable-but-unlabeled (or vice versa).
+ */
+final class GraphCatalog {
+
+    /** A dataset entry: how to construct it, how to name it, and where its 
Sample Data book section lives. */
+    static final class Entry {
+        /** Gremlin statement that constructs the graph into the {@code graph} 
binding. */
+        final String initStatement;
+        /** Human-readable name used in the rendered dataset caption. */
+        final String displayName;
+        /** Sample Data book section anchor, or {@code null} if the graph has 
no dedicated section. */
+        final String docAnchor;
+
+        Entry(final String initStatement, final String displayName, final 
String docAnchor) {
+            this.initStatement = initStatement;
+            this.displayName = displayName;
+            this.docAnchor = docAnchor;
+        }
+    }
+
+    /** Init statement for a bare block or any unrecognized token: a fresh, 
empty graph. */
+    static final String DEFAULT_INIT = "graph = TinkerGraph.open()";
+
+    private static final Map<String, Entry> BY_TOKEN;
+
+    static {
+        final Map<String, Entry> m = new HashMap<>();
+        // "crew" and "theCrew" are aliases for the same dataset.
+        final Entry crew = new Entry("graph = TinkerFactory.createTheCrew()", 
"crew", "the-crew");
+        m.put("modern", new Entry("graph = TinkerFactory.createModern()", 
"modern", "modern"));
+        m.put("classic", new Entry("graph = TinkerFactory.createClassic()", 
"classic", null));
+        m.put("crew", crew);
+        m.put("theCrew", crew);
+        m.put("grateful", new Entry("graph = 
TinkerFactory.createGratefulDead()", "Grateful Dead", "grateful-dead"));
+        m.put("sink", new Entry("graph = TinkerFactory.createKitchenSink()", 
"kitchen sink", null));
+        BY_TOKEN = Collections.unmodifiableMap(m);
+    }
+
+    private GraphCatalog() {
+    }
+
+    /**
+     * Returns the catalog entry for a dataset token, or {@code null} if the 
token is not a known
+     * dataset (including {@code null}, a bare block, or a misspelling).
+     */
+    static Entry entry(final String token) {
+        return token == null ? null : BY_TOKEN.get(token);
+    }
+
+    /**
+     * Returns the Gremlin init statement for a token, falling back to {@link 
#DEFAULT_INIT} (a fresh
+     * empty graph) for {@code null} or unrecognized tokens.
+     */
+    static String initStatement(final String token) {
+        final Entry e = entry(token);
+        return e != null ? e.initStatement : DEFAULT_INIT;
+    }
+}
diff --git 
a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/GremlinTreeprocessor.java
 
b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/GremlinTreeprocessor.java
index 0106f33dba..a39d2f4104 100644
--- 
a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/GremlinTreeprocessor.java
+++ 
b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/GremlinTreeprocessor.java
@@ -55,19 +55,6 @@ public class GremlinTreeprocessor extends Treeprocessor {
     static final Set<String> SUPPORTED_LANGUAGES = Collections.unmodifiableSet(
             new HashSet<>(Arrays.asList("groovy", "java", "csharp", 
"javascript", "python", "go")));
 
-    static final Map<String, String> GRAPH_INIT;
-
-    static {
-        final Map<String, String> m = new HashMap<>();
-        m.put("modern", "graph = TinkerFactory.createModern()");
-        m.put("classic", "graph = TinkerFactory.createClassic()");
-        m.put("crew", "graph = TinkerFactory.createTheCrew()");
-        m.put("theCrew", "graph = TinkerFactory.createTheCrew()");
-        m.put("grateful", "graph = TinkerFactory.createGratefulDead()");
-        m.put("sink", "graph = TinkerFactory.createKitchenSink()");
-        GRAPH_INIT = Collections.unmodifiableMap(m);
-    }
-
     private int gremlinBlockCount;
     private final StatementExecutor executor;
     private final TabbedHtmlBuilder tabBuilder;
@@ -78,6 +65,11 @@ public class GremlinTreeprocessor extends Treeprocessor {
     private final ConsoleRestartHandler restartHandler;
     private ConsoleRestartHandler activeRestartHandler;
     private String currentGraph;
+    // Tracks the most recently declared graph dataset from the AST walk 
(independent of execution),
+    // used only to resolve the label for "existing" blocks. Unlike 
currentGraph, this is maintained
+    // on every backend pass — including passes that reuse the execution cache 
and skip graph init —
+    // so the rendered dataset label is identical across the HTML and Markdown 
backends.
+    private String labelGraphLineage;
     private String documentId;
     private List<String> currentExcludedPlugins;
 
@@ -152,6 +144,7 @@ public class GremlinTreeprocessor extends Treeprocessor {
     public Document process(final Document document) {
         gremlinBlockCount = 0;
         currentGraph = null;
+        labelGraphLineage = null;
         documentId = resolveDocumentId(document);
         markdownMode = isMarkdownBackend(document);
         final Object dryRunAttr = document.getAttribute("gremlin-docs-dryrun");
@@ -317,6 +310,20 @@ public class GremlinTreeprocessor extends Treeprocessor {
         final List<StructuralNode> blocks = parent.getBlocks();
         final Block gremlinBlock = (Block) blocks.get(startIndex);
 
+        // Resolve the graph dataset name for the label from the AST walk (not 
execution state), so
+        // it is identical across backend passes even when a later pass reuses 
the execution cache and
+        // skips graph initialization. An "existing" block carries forward the 
last declared dataset;
+        // any other block (including a bare block with no dataset) sets the 
lineage, mirroring how
+        // initGraphIfNeeded() re-initializes the graph for every 
non-"existing" block.
+        final String rawGraphName = extractGraphName(gremlinBlock);
+        final String resolvedGraphName;
+        if (EXISTING.equals(rawGraphName)) {
+            resolvedGraphName = labelGraphLineage;
+        } else {
+            resolvedGraphName = rawGraphName;
+            labelGraphLineage = rawGraphName;
+        }
+
         // Walk consecutive [source,<lang>] sibling blocks first (FR-5). This 
is pure AST work with
         // no execution: it fixes the block range to replace and captures each 
sibling's language
         // and source, which are needed whether the console output comes fresh 
or from the cache.
@@ -352,7 +359,7 @@ public class GremlinTreeprocessor extends Treeprocessor {
             executionCache.put(cacheKey, tabs);
         }
 
-        emitNeutralTabGroup(parent, startIndex, lastIndex, tabs);
+        emitNeutralTabGroup(parent, startIndex, lastIndex, tabs, 
resolvedGraphName);
         return startIndex;
     }
 
@@ -377,7 +384,7 @@ public class GremlinTreeprocessor extends Treeprocessor {
             }
         }
 
-        emitNeutralTabGroup(parent, startIndex, lastIndex, tabs);
+        emitNeutralTabGroup(parent, startIndex, lastIndex, tabs, null);
         return startIndex;
     }
 
@@ -389,12 +396,12 @@ public class GremlinTreeprocessor extends Treeprocessor {
      * into a neutral custom block for a separate render pass to consume.
      */
     private void emitNeutralTabGroup(final StructuralNode parent, final int 
startIndex,
-                                     final int endIndex, final 
List<NeutralTab> tabs) {
+                                     final int endIndex, final 
List<NeutralTab> tabs, final String graphName) {
         final String json = NeutralTabCodec.serialize(tabs);
         final List<NeutralTab> resolvedTabs = NeutralTabCodec.parse(json);
         final String rendered = markdownMode
-                ? markdownTabRenderer.render(resolvedTabs)
-                : htmlTabRenderer.render(parent, resolvedTabs);
+                ? markdownTabRenderer.render(resolvedTabs, graphName)
+                : htmlTabRenderer.render(parent, resolvedTabs, graphName);
         replaceWithPassBlock(parent, startIndex, endIndex, rendered);
     }
 
@@ -684,12 +691,7 @@ public class GremlinTreeprocessor extends Treeprocessor {
         executeSafely("['/tmp/neo4j', '/tmp/tinkergraph.kryo'].each { p -> "
                 + "def f = new File(p); if (f.exists()) f.deleteDir() }");
 
-        final String initStatement;
-        if (graphName == null) {
-            initStatement = "graph = TinkerGraph.open()";
-        } else {
-            initStatement = GRAPH_INIT.getOrDefault(graphName, "graph = 
TinkerGraph.open()");
-        }
+        final String initStatement = GraphCatalog.initStatement(graphName);
 
         executeSafely(initStatement);
         executeSafely("g = graph.traversal()");
diff --git 
a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/HtmlTabRenderer.java
 
b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/HtmlTabRenderer.java
index 3f9f667d69..1373b870b1 100644
--- 
a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/HtmlTabRenderer.java
+++ 
b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/HtmlTabRenderer.java
@@ -45,11 +45,12 @@ class HtmlTabRenderer {
     /**
      * Highlights and renders the given neutral tabs into a complete tab-group 
HTML string.
      *
-     * @param parent the AST node used to locate the JRuby runtime for 
highlighting
-     * @param tabs   the neutral tabs to render
+     * @param parent    the AST node used to locate the JRuby runtime for 
highlighting
+     * @param tabs      the neutral tabs to render
+     * @param graphName the resolved graph dataset name (e.g. "modern"), or 
null if not applicable
      * @return the tab-group HTML
      */
-    String render(final StructuralNode parent, final List<NeutralTab> tabs) {
+    String render(final StructuralNode parent, final List<NeutralTab> tabs, 
final String graphName) {
         final List<TabbedHtmlBuilder.Tab> htmlTabs = new 
ArrayList<>(tabs.size());
         for (final NeutralTab tab : tabs) {
             // The existing pipeline highlighted every tab (console and 
source) as groovy-flavored
@@ -62,6 +63,13 @@ class HtmlTabRenderer {
                 
htmlTabs.add(TabbedHtmlBuilder.codeTabHighlighted(tab.getLanguage(), 
highlighted));
             }
         }
-        return tabBuilder.build(htmlTabs);
+        return tabBuilder.build(htmlTabs, graphName);
+    }
+
+    /**
+     * Highlights and renders the given neutral tabs into a complete tab-group 
HTML string (no graph label).
+     */
+    String render(final StructuralNode parent, final List<NeutralTab> tabs) {
+        return render(parent, tabs, null);
     }
 }
diff --git 
a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/MarkdownTabRenderer.java
 
b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/MarkdownTabRenderer.java
index bb1685b956..cf583525a8 100644
--- 
a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/MarkdownTabRenderer.java
+++ 
b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/MarkdownTabRenderer.java
@@ -45,7 +45,22 @@ class MarkdownTabRenderer {
      * @return the Markdown for the tab group (no trailing newline)
      */
     String render(final List<NeutralTab> tabs) {
+        return render(tabs, null);
+    }
+
+    /**
+     * Renders the given neutral tabs as a sequence of labeled fenced code 
blocks with an optional
+     * graph-dataset label.
+     *
+     * @param tabs      the neutral tabs to render
+     * @param graphName the resolved graph dataset name (e.g. "modern"), or 
null to omit
+     * @return the Markdown for the tab group (no trailing newline)
+     */
+    String render(final List<NeutralTab> tabs, final String graphName) {
         final StringBuilder sb = new StringBuilder();
+        if (graphName != null && !graphName.isEmpty()) {
+            sb.append("*graph-dataset: ").append(graphName).append("*\n\n");
+        }
         for (int i = 0; i < tabs.size(); i++) {
             if (i > 0) sb.append("\n\n");
             final NeutralTab tab = tabs.get(i);
diff --git 
a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/NeutralTab.java
 
b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/NeutralTab.java
index 2aa66b82f5..f97e6cf344 100644
--- 
a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/NeutralTab.java
+++ 
b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/NeutralTab.java
@@ -51,11 +51,11 @@ class NeutralTab {
     }
 
     /**
-     * Creates a console-transcript tab. Its label matches the existing HTML 
output, i.e.
-     * {@code console (<lang>)}.
+     * Creates a console-transcript tab. Its label is {@code console} (the 
transcript is always the
+     * Groovy Gremlin Console, so the language is not repeated in the label).
      */
     static NeutralTab console(final String language, final String content) {
-        return new NeutralTab("console (" + language + ")", language, 
Kind.CONSOLE, content);
+        return new NeutralTab("console", language, Kind.CONSOLE, content);
     }
 
     /**
diff --git 
a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/TabbedHtmlBuilder.java
 
b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/TabbedHtmlBuilder.java
index 5098ecf5b0..925c583b9a 100644
--- 
a/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/TabbedHtmlBuilder.java
+++ 
b/docs/tinkeradoc-extension/src/main/java/org/apache/tinkerpop/tinkeradoc/TabbedHtmlBuilder.java
@@ -45,6 +45,28 @@ public class TabbedHtmlBuilder {
 
     private int groupCounter = 0;
 
+    /**
+     * Builds the italic caption line naming the toy graph an example runs 
against, linking the graph
+     * name to its section in the Sample Data book when one exists. Unknown 
tokens render as plain
+     * (unlinked) text. The display name and anchor come from the shared 
{@link GraphCatalog}. The link
+     * uses the {@code x.y.z} version placeholder, which {@code 
GremlinPostprocessor} replaces with the
+     * real version in the final HTML (the same mechanism used by every other 
cross-reference in the docs).
+     *
+     * @param token the resolved graph token (e.g. "modern", "theCrew")
+     * @return the caption HTML (a {@code <div class="graph-dataset-note">} 
element)
+     */
+    static String graphNoteHtml(final String token) {
+        final GraphCatalog.Entry entry = GraphCatalog.entry(token);
+        final String display = (entry != null) ? entry.displayName : token;
+        final String anchor = (entry != null) ? entry.docAnchor : null;
+        final String name = (anchor != null)
+                ? "<a href=\"https://tinkerpop.apache.org/docs/x.y.z/data/#"; + 
anchor + "\">"
+                        + escapeHtml(display) + "</a>"
+                : escapeHtml(display);
+        return "<div class=\"graph-dataset-note\"><em>This example runs 
against the "
+                + name + " graph.</em></div>";
+    }
+
     /**
      * A single tab entry with a label, language, and source code content.
      */
@@ -103,6 +125,17 @@ public class TabbedHtmlBuilder {
      * @return the complete HTML string for the tab group
      */
     String build(final List<Tab> tabs) {
+        return build(tabs, null);
+    }
+
+    /**
+     * Builds tabbed HTML for a list of tabs with an optional graph-dataset 
caption.
+     *
+     * @param tabs      the tabs to render
+     * @param graphName the resolved graph dataset name (e.g. "modern"), or 
null to omit the caption
+     * @return the complete HTML string for the tab group
+     */
+    String build(final List<Tab> tabs, final String graphName) {
         if (tabs == null || tabs.isEmpty()) {
             return "";
         }
@@ -144,9 +177,17 @@ public class TabbedHtmlBuilder {
             html.append("    </div>\n");
         }
 
+        // Graph-dataset caption: an italic line naming the toy graph the 
example runs against,
+        // rendered inside the gray .tabcontent box at the bottom-right. 
Emitted after the floated
+        // tab panels and cleared below them, so it sits within the gray frame 
rather than beneath
+        // it. It links to the graph's Sample Data book section.
+        if (graphName != null && !graphName.isEmpty()) {
+            html.append("    ").append(graphNoteHtml(graphName)).append("\n");
+        }
+
         html.append("  </div>\n");
-        html.append("</section>");
 
+        html.append("</section>");
         return html.toString();
     }
 
@@ -158,14 +199,14 @@ public class TabbedHtmlBuilder {
      * @return a Tab instance for the console output
      */
     static Tab consoleTab(final String lang, final String consoleOutput) {
-        return new Tab("console (" + lang + ")", lang, consoleOutput);
+        return new Tab("console", lang, consoleOutput);
     }
 
     /**
      * Creates a console tab with pre-highlighted HTML content.
      */
     static Tab consoleTabHighlighted(final String lang, final String 
highlightedHtml) {
-        return new Tab("console (" + lang + ")", lang, highlightedHtml, true);
+        return new Tab("console", lang, highlightedHtml, true);
     }
 
     /**
diff --git 
a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/DualBackendIntegrationTest.java
 
b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/DualBackendIntegrationTest.java
index 48e634970a..dccddd3d7f 100644
--- 
a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/DualBackendIntegrationTest.java
+++ 
b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/DualBackendIntegrationTest.java
@@ -91,7 +91,7 @@ public class DualBackendIntegrationTest {
         // preserved verbatim (no highlighting).
         assertThat(markdown, containsString("## Basics"));
         assertThat(markdown, containsString("Some intro text."));
-        assertThat(markdown, containsString("**console (groovy)**"));
+        assertThat(markdown, containsString("**console**"));
         assertThat(markdown, containsString("```text"));
         assertThat(markdown, containsString("gremlin> g.V(1)"));
         assertThat(markdown, containsString("==>v[1]"));
diff --git 
a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/GraphCatalogTest.java
 
b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/GraphCatalogTest.java
new file mode 100644
index 0000000000..db3c5c3b5b
--- /dev/null
+++ 
b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/GraphCatalogTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.tinkerpop.tinkeradoc;
+
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+
+public class GraphCatalogTest {
+
+    @Test
+    public void shouldExposeInitStatementDisplayAndAnchorForKnownGraph() {
+        final GraphCatalog.Entry modern = GraphCatalog.entry("modern");
+        assertThat(modern.initStatement, is("graph = 
TinkerFactory.createModern()"));
+        assertThat(modern.displayName, is("modern"));
+        assertThat(modern.docAnchor, is("modern"));
+    }
+
+    @Test
+    public void shouldTreatCrewAndTheCrewAsTheSameDataset() {
+        final GraphCatalog.Entry crew = GraphCatalog.entry("crew");
+        final GraphCatalog.Entry theCrew = GraphCatalog.entry("theCrew");
+        assertThat(crew.initStatement, is("graph = 
TinkerFactory.createTheCrew()"));
+        assertThat(theCrew.initStatement, is(crew.initStatement));
+        assertThat(theCrew.displayName, is(crew.displayName));
+        assertThat(theCrew.docAnchor, is(crew.docAnchor));
+    }
+
+    @Test
+    public void shouldReportNullAnchorForGraphWithoutReferenceSection() {
+        assertThat(GraphCatalog.entry("classic").docAnchor, nullValue());
+        assertThat(GraphCatalog.entry("sink").docAnchor, nullValue());
+    }
+
+    @Test
+    public void shouldReturnNoEntryForNullOrUnknownToken() {
+        assertThat(GraphCatalog.entry(null), nullValue());
+        assertThat(GraphCatalog.entry("bogus"), nullValue());
+    }
+
+    @Test
+    public void shouldFallBackToEmptyGraphInitForNullOrUnknownToken() {
+        assertThat(GraphCatalog.initStatement(null), 
is(GraphCatalog.DEFAULT_INIT));
+        assertThat(GraphCatalog.initStatement("bogus"), 
is(GraphCatalog.DEFAULT_INIT));
+        assertThat(GraphCatalog.DEFAULT_INIT, is("graph = 
TinkerGraph.open()"));
+    }
+}
diff --git 
a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/GremlinTreeprocessorTest.java
 
b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/GremlinTreeprocessorTest.java
index 209e73f66e..c1a2069993 100644
--- 
a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/GremlinTreeprocessorTest.java
+++ 
b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/GremlinTreeprocessorTest.java
@@ -87,6 +87,68 @@ public class GremlinTreeprocessorTest {
         }
     }
 
+    @Test
+    public void shouldRenderGraphBadgeForNamedGraph() {
+        final RecordingExecutor executor = new RecordingExecutor("==>v[1]");
+        final GremlinTreeprocessor processor = new 
GremlinTreeprocessor(executor);
+        try (final Asciidoctor asciidoctor = Asciidoctor.Factory.create()) {
+            asciidoctor.unregisterAllExtensions();
+            asciidoctor.javaExtensionRegistry().treeprocessor(processor);
+            final String input = "= 
Test\n\n[gremlin-groovy,modern]\n----\ng.V(1)\n----\n";
+            final String result = asciidoctor.convert(input, 
Options.builder().build());
+            assertThat(result, containsString("class=\"graph-dataset-note\""));
+            assertThat(result, containsString("/data/#modern\">modern</a> 
graph."));
+        }
+    }
+
+    @Test
+    public void shouldCarryForwardGraphLabelForExisting() {
+        final RecordingExecutor executor = new RecordingExecutor("==>result");
+        final GremlinTreeprocessor processor = new 
GremlinTreeprocessor(executor);
+        try (final Asciidoctor asciidoctor = Asciidoctor.Factory.create()) {
+            asciidoctor.unregisterAllExtensions();
+            asciidoctor.javaExtensionRegistry().treeprocessor(processor);
+            final String input = "= Test\n\n" +
+                    "[gremlin-groovy,modern]\n----\ng.V(1)\n----\n\n" +
+                    "[gremlin-groovy,existing]\n----\ng.E()\n----\n";
+            final String result = asciidoctor.convert(input, 
Options.builder().build());
+            // Both blocks show the "modern" note; the existing block carries 
it forward.
+            final int badgeCount = result.split("/data/#modern\">modern</a>", 
-1).length - 1;
+            assertThat(badgeCount, is(2));
+        }
+    }
+
+    @Test
+    public void shouldLabelExistingConsistentlyAcrossCachedSecondPass() {
+        // Regression: the "existing" label must be resolved from the AST 
walk, not from
+        // execution-set state. A second backend pass reuses the shared 
execution cache and skips
+        // graph initialization, so a label derived from execution state would 
go missing on that
+        // pass. Both passes must produce the same "modern" label for the 
existing block.
+        final RecordingExecutor executor = new RecordingExecutor("==>result");
+        final GremlinExecutionCache cache = new GremlinExecutionCache();
+        final String input = "= Test\n\n" +
+                "[gremlin-groovy,modern]\n----\ng.V(1)\n----\n\n" +
+                "[gremlin-groovy,existing]\n----\ng.E()\n----\n";
+        try (final Asciidoctor asciidoctor = Asciidoctor.Factory.create()) {
+            
asciidoctor.javaConverterRegistry().register(MarkdownConverter.class);
+
+            asciidoctor.unregisterAllExtensions();
+            asciidoctor.javaExtensionRegistry().treeprocessor(new 
GremlinTreeprocessor(executor, null, cache));
+            final String html = asciidoctor.convert(input, 
Options.builder().backend("html5").build());
+
+            asciidoctor.unregisterAllExtensions();
+            asciidoctor.javaExtensionRegistry().treeprocessor(new 
GremlinTreeprocessor(executor, null, cache));
+            final String markdown = asciidoctor.convert(input, 
Options.builder().backend("tpmarkdown").build());
+
+            // HTML pass: two "modern" notes (named + existing carried 
forward).
+            final int htmlBadges = html.split("/data/#modern\">modern</a>", 
-1).length - 1;
+            assertThat(htmlBadges, is(2));
+            // Markdown pass (cache reused, no execution): two "modern" 
dataset labels.
+            final int mdLabels = markdown.split("\\*graph-dataset: modern\\*", 
-1).length - 1;
+            assertThat(mdLabels, is(2));
+        }
+    }
+
     @Test
     public void shouldReuseGraphStateForExisting() {
         final RecordingExecutor executor = new RecordingExecutor("==>result");
@@ -356,7 +418,7 @@ public class GremlinTreeprocessorTest {
             final String input = "= 
Test\n\n[gremlin-groovy,modern]\n----\ng.V(1)\n----\n";
             final String result = asciidoctor.convert(input, 
Options.builder().build());
             assertThat(result, containsString("section class=\"tabs 
tabs-2\""));
-            assertThat(result, containsString("console (groovy)"));
+            assertThat(result, containsString("console"));
             assertThat(result, containsString("tab-group-1"));
         }
     }
@@ -375,7 +437,7 @@ public class GremlinTreeprocessorTest {
                     "[source,python]\n----\ng.V(1)\n----\n";
             final String result = asciidoctor.convert(input, 
Options.builder().build());
             assertThat(result, containsString("tabs tabs-4"));
-            assertThat(result, containsString("console (groovy)"));
+            assertThat(result, containsString("console"));
             assertThat(result, containsString("tab-label-3\">java"));
             assertThat(result, containsString("tab-label-4\">python"));
         }
diff --git 
a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/HtmlTabRendererTest.java
 
b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/HtmlTabRendererTest.java
index d75da871ad..299327425e 100644
--- 
a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/HtmlTabRendererTest.java
+++ 
b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/HtmlTabRendererTest.java
@@ -48,7 +48,7 @@ public class HtmlTabRendererTest {
     @Test
     public void consoleFactorySetsLabelLanguageAndKind() {
         final NeutralTab tab = NeutralTab.console("groovy", "gremlin> g.V()");
-        assertThat(tab.getLabel(), is("console (groovy)"));
+        assertThat(tab.getLabel(), is("console"));
         assertThat(tab.getLanguage(), is("groovy"));
         assertThat(tab.getKind(), is(NeutralTab.Kind.CONSOLE));
         assertThat(tab.getContent(), is("gremlin> g.V()"));
@@ -69,7 +69,7 @@ public class HtmlTabRendererTest {
                 NeutralTab.source("groovy", "g.V(1)"));
         final String html = renderer.render(null, tabs);
         assertThat(html, containsString("<section class=\"tabs tabs-2\">"));
-        assertThat(html, containsString("class=\"tab-label-1\">console 
(groovy)</label>"));
+        assertThat(html, 
containsString("class=\"tab-label-1\">console</label>"));
         assertThat(html, 
containsString("class=\"tab-label-2\">groovy</label>"));
     }
 
diff --git 
a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/IntegrationTest.java
 
b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/IntegrationTest.java
index 3f767534ca..cda2bd4582 100644
--- 
a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/IntegrationTest.java
+++ 
b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/IntegrationTest.java
@@ -75,7 +75,7 @@ public class IntegrationTest {
 
     @Test
     public void shouldContainConsoleTabLabel() {
-        assertThat(html, containsString("console (groovy)"));
+        assertThat(html, containsString("console"));
     }
 
     @Test
diff --git 
a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/MarkdownTabRendererTest.java
 
b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/MarkdownTabRendererTest.java
index 34d8e61dd7..63eb611883 100644
--- 
a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/MarkdownTabRendererTest.java
+++ 
b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/MarkdownTabRendererTest.java
@@ -46,7 +46,7 @@ public class MarkdownTabRendererTest {
     public void consoleTabRendersAsTextFence() {
         final String md = renderer.render(Collections.singletonList(
                 NeutralTab.console("groovy", "gremlin> g.V()\n==>v[1]")));
-        assertThat(md, containsString("**console (groovy)**"));
+        assertThat(md, containsString("**console**"));
         assertThat(md, containsString("```text\ngremlin> 
g.V()\n==>v[1]\n```"));
     }
 
@@ -65,7 +65,7 @@ public class MarkdownTabRendererTest {
                 NeutralTab.source("groovy", "g.V(1)"),
                 NeutralTab.source("python", "g.V(1)"));
         final String md = renderer.render(tabs);
-        final int console = md.indexOf("**console (groovy)**");
+        final int console = md.indexOf("**console**");
         final int groovy = md.indexOf("**groovy**");
         final int python = md.indexOf("**python**");
         assertThat(console >= 0 && groovy > console && python > groovy, 
is(true));
@@ -110,4 +110,18 @@ public class MarkdownTabRendererTest {
                 NeutralTab.source("groovy", "a() <1>\nb()\nc() <2>")));
         assertThat(md, containsString("a() // (1)\nb()\nc() // (2)"));
     }
+
+    @Test
+    public void shouldIncludeGraphLabelWhenProvided() {
+        final String md = renderer.render(Collections.singletonList(
+                NeutralTab.source("groovy", "g.V()")), "modern");
+        assertThat(md, containsString("*graph-dataset: modern*"));
+    }
+
+    @Test
+    public void shouldOmitGraphLabelWhenNull() {
+        final String md = renderer.render(Collections.singletonList(
+                NeutralTab.source("groovy", "g.V()")), null);
+        assertThat(md, not(containsString("graph-dataset")));
+    }
 }
diff --git 
a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/TabbedHtmlBuilderTest.java
 
b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/TabbedHtmlBuilderTest.java
index 998c4104e2..b8cd8930ba 100644
--- 
a/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/TabbedHtmlBuilderTest.java
+++ 
b/docs/tinkeradoc-extension/src/test/java/org/apache/tinkerpop/tinkeradoc/TabbedHtmlBuilderTest.java
@@ -101,7 +101,7 @@ public class TabbedHtmlBuilderTest {
                 TabbedHtmlBuilder.consoleTab("groovy", "output"),
                 TabbedHtmlBuilder.codeTab("java", "code"));
         final String html = builder.build(tabs);
-        assertThat(html, containsString("<label for=\"tab-1-1\" 
class=\"tab-label-1\">console (groovy)</label>"));
+        assertThat(html, containsString("<label for=\"tab-1-1\" 
class=\"tab-label-1\">console</label>"));
         assertThat(html, containsString("<label for=\"tab-1-2\" 
class=\"tab-label-2\">java</label>"));
     }
 
@@ -176,7 +176,7 @@ public class TabbedHtmlBuilderTest {
     @Test
     public void shouldCreateConsoleTabWithCorrectLabel() {
         final TabbedHtmlBuilder.Tab tab = 
TabbedHtmlBuilder.consoleTab("groovy", "gremlin> g.V()");
-        assertThat(tab.getLabel(), is("console (groovy)"));
+        assertThat(tab.getLabel(), is("console"));
         assertThat(tab.getLanguage(), is("groovy"));
         assertThat(tab.getContent(), is("gremlin> g.V()"));
     }
@@ -258,7 +258,7 @@ public class TabbedHtmlBuilderTest {
         assertThat(html, containsString("id=\"tab-1-4\" 
class=\"tab-selector-4\""));
 
         // Verify all 4 labels
-        assertThat(html, containsString("class=\"tab-label-1\">console 
(groovy)</label>"));
+        assertThat(html, 
containsString("class=\"tab-label-1\">console</label>"));
         assertThat(html, containsString("class=\"tab-label-2\">java</label>"));
         assertThat(html, 
containsString("class=\"tab-label-3\">python</label>"));
         assertThat(html, 
containsString("class=\"tab-label-4\">csharp</label>"));
@@ -283,4 +283,31 @@ public class TabbedHtmlBuilderTest {
         assertThat(html2, containsString("id=\"tab-2-1\""));
         assertThat(html3, containsString("id=\"tab-3-1\""));
     }
+
+    @Test
+    public void shouldIncludeGraphBadgeWhenGraphNameProvided() {
+        final List<TabbedHtmlBuilder.Tab> tabs = Collections.singletonList(
+                TabbedHtmlBuilder.codeTab("groovy", "g.V()"));
+        final String html = builder.build(tabs, "modern");
+        assertThat(html, containsString("class=\"graph-dataset-note\""));
+        assertThat(html, containsString("This example runs against the"));
+        assertThat(html, containsString("/data/#modern\">modern</a> graph."));
+    }
+
+    @Test
+    public void shouldOmitGraphBadgeWhenGraphNameIsNull() {
+        final List<TabbedHtmlBuilder.Tab> tabs = Collections.singletonList(
+                TabbedHtmlBuilder.codeTab("groovy", "g.V()"));
+        final String html = builder.build(tabs, null);
+        assertThat(html, not(containsString("graph-dataset-note")));
+    }
+
+    @Test
+    public void shouldEscapeHtmlInGraphBadge() {
+        final List<TabbedHtmlBuilder.Tab> tabs = Collections.singletonList(
+                TabbedHtmlBuilder.codeTab("groovy", "g.V()"));
+        final String html = builder.build(tabs, "<script>alert(1)</script>");
+        assertThat(html, 
containsString("&lt;script&gt;alert(1)&lt;/script&gt;"));
+        assertThat(html, not(containsString("<script>")));
+    }
 }

Reply via email to