diff --git a/graphify/exporters/html.py b/graphify/exporters/html.py
index 8d600c4f..372dd397 100644
--- a/graphify/exporters/html.py
+++ b/graphify/exporters/html.py
@@ -327,10 +327,10 @@ def _html_document_title(output_path: str) -> str:
"""Return a portable label for the graph.html
.
Tracked artifacts must not embed the generator host absolute path
- (regression of #433; reported again as #2598 on Windows). Prefer a
- path relative to the process cwd; otherwise keep from the configured
- output-dir bare name (``graphify-out`` / ``GRAPHIFY_OUT`` basename)
- onward; finally fall back to the filename only.
+ (regression of #433; reported again as #2598 on Windows). Keep from the
+ configured output-dir bare name (``graphify-out`` / ``GRAPHIFY_OUT``
+ basename) onward — portable in every case; otherwise fall back to a
+ cwd-relative label, and finally the filename only.
"""
from graphify.paths import GRAPHIFY_OUT_NAME
@@ -340,6 +340,20 @@ def _html_document_title(output_path: str) -> str:
raw = raw[2:] # "/Users/..." style after drive strip
p = Path(raw)
+ parts = list(Path(raw).parts)
+ # Path("C:/Users/..") on POSIX may keep "C:" as first part — strip it.
+ if parts and len(parts[0]) == 2 and parts[0][1] == ":" and parts[0][0].isalpha():
+ parts = parts[1:]
+ # Prefer keeping from the output-dir marker onward: portable in every
+ # case, whereas a cwd-relative path still leaks host/user segments when
+ # the graph is built from a directory ABOVE the project (#2598 follow-up).
+ marker = GRAPHIFY_OUT_NAME
+ for i, part in enumerate(parts):
+ if part == marker or part.startswith("graphify-out"):
+ return "/".join(parts[i:])
+
+ # No standard out-dir marker (fully custom output path): fall back to a
+ # cwd-relative label when the target is under cwd, else the bare filename.
try:
resolved = p if p.is_absolute() else (Path.cwd() / p)
rel = resolved.resolve().relative_to(Path.cwd().resolve())
@@ -349,14 +363,6 @@ def _html_document_title(output_path: str) -> str:
except (ValueError, OSError, RuntimeError):
pass
- parts = list(Path(raw).parts)
- # Path("C:/Users/..") on POSIX may keep "C:" as first part — strip it.
- if parts and len(parts[0]) == 2 and parts[0][1] == ":" and parts[0][0].isalpha():
- parts = parts[1:]
- marker = GRAPHIFY_OUT_NAME
- for i, part in enumerate(parts):
- if part == marker or part.startswith("graphify-out"):
- return "/".join(parts[i:])
name = p.name
return name if name else "graph.html"