Skip to content

Commit c550a39

Browse files
committed
feat(docs[_ext]): Add 4 toolicon placement variants for A/B testing
why: Need to test all icon-badge positions to find the best visual fit. what: - {tooliconl}: icon left, outside code — [🔍] `capture_pane` - {tooliconr}: icon right, outside code — `capture_pane` [🔍] - {tooliconil}: icon inline-left, inside code — `[🔍]capture_pane` - {tooliconir}: icon inline-right, inside code — `capture_pane[🔍]` - {toolicon} remains as alias for {tooliconl} - Factory function _make_toolicon_role creates role variants - CSS: .icon-only-inline for inside-code variants (tighter margins) - Demo page shows all 4 variants side by side
1 parent e156487 commit c550a39

3 files changed

Lines changed: 100 additions & 33 deletions

File tree

docs/_ext/fastmcp_autodoc.py

Lines changed: 67 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -881,11 +881,14 @@ def _resolve_tool_refs(
881881
doctree: nodes.document,
882882
fromdocname: str,
883883
) -> None:
884-
"""Resolve ``{tool}``, ``{toolref}``, and ``{toolicon}`` placeholders.
884+
"""Resolve ``{tool}``, ``{toolref}``, and ``{toolicon*}`` placeholders.
885885
886886
``{tool}`` renders as ``code`` + safety badge (text + icon).
887887
``{toolref}`` renders as ``code`` only (no badge).
888-
``{toolicon}`` renders as ``code`` + icon-only badge (square, no text).
888+
``{toolicon}``/``{tooliconl}`` — icon-only badge left of code.
889+
``{tooliconr}`` — icon-only badge right of code.
890+
``{tooliconil}`` — icon-only badge inside code, left of text.
891+
``{tooliconir}`` — icon-only badge inside code, right of text.
889892
890893
Runs at ``doctree-resolved`` — after all labels are registered and
891894
standard ``{ref}`` resolution is done.
@@ -897,7 +900,7 @@ def _resolve_tool_refs(
897900
for node in list(doctree.findall(_tool_ref_placeholder)):
898901
target = node.get("reftarget", "")
899902
show_badge = node.get("show_badge", True)
900-
icon_only = node.get("icon_only", False)
903+
icon_pos = node.get("icon_pos", "")
901904
label_info = domain.labels.get(target)
902905
if label_info is None:
903906
node.replace_self(nodes.literal("", target.replace("-", "_")))
@@ -916,24 +919,44 @@ def _resolve_tool_refs(
916919
newnode["classes"].append("reference")
917920
newnode["classes"].append("internal")
918921

919-
if icon_only:
920-
# Icon badge goes BEFORE the <code> element, outside it
922+
if icon_pos:
921923
tool_info = tool_data.get(tool_name)
924+
badge = None
922925
if tool_info:
923926
badge = _safety_badge(tool_info.safety)
924927
badge["classes"].append("icon-only")
928+
if icon_pos.startswith("inline"):
929+
badge["classes"].append("icon-only-inline")
925930
badge.children.clear()
926931
badge += nodes.Text("")
927-
newnode += badge
928-
newnode += nodes.literal("", tool_name)
932+
933+
if icon_pos == "left":
934+
if badge:
935+
newnode += badge
936+
newnode += nodes.literal("", tool_name)
937+
elif icon_pos == "right":
938+
newnode += nodes.literal("", tool_name)
939+
if badge:
940+
newnode += badge
941+
elif icon_pos == "inline-left":
942+
code_node = nodes.literal("", "")
943+
if badge:
944+
code_node += badge
945+
code_node += nodes.Text(tool_name)
946+
newnode += code_node
947+
elif icon_pos == "inline-right":
948+
code_node = nodes.literal("", "")
949+
code_node += nodes.Text(tool_name)
950+
if badge:
951+
code_node += badge
952+
newnode += code_node
929953
else:
930954
newnode += nodes.literal("", tool_name)
931-
932-
if not icon_only and show_badge:
933-
tool_info = tool_data.get(tool_name)
934-
if tool_info:
935-
newnode += nodes.Text(" ")
936-
newnode += _safety_badge(tool_info.safety)
955+
if show_badge:
956+
tool_info = tool_data.get(tool_name)
957+
if tool_info:
958+
newnode += nodes.Text(" ")
959+
newnode += _safety_badge(tool_info.safety)
937960

938961
node.replace_self(newnode)
939962

@@ -975,25 +998,34 @@ def _toolref_role(
975998
return [node], []
976999

9771000

978-
def _toolicon_role(
979-
name: str,
980-
rawtext: str,
981-
text: str,
982-
lineno: int,
983-
inliner: object,
984-
options: dict[str, object] | None = None,
985-
content: list[str] | None = None,
986-
) -> tuple[list[nodes.Node], list[nodes.system_message]]:
987-
"""Inline role ``:toolicon:`capture-pane``` → code + square icon badge.
1001+
def _make_toolicon_role(
1002+
icon_pos: str,
1003+
) -> t.Callable[..., tuple[list[nodes.Node], list[nodes.system_message]]]:
1004+
"""Create an icon-only tool reference role for a given position."""
1005+
1006+
def role_fn(
1007+
name: str,
1008+
rawtext: str,
1009+
text: str,
1010+
lineno: int,
1011+
inliner: object,
1012+
options: dict[str, object] | None = None,
1013+
content: list[str] | None = None,
1014+
) -> tuple[list[nodes.Node], list[nodes.system_message]]:
1015+
target = text.strip().replace("_", "-")
1016+
node = _tool_ref_placeholder(
1017+
rawtext, reftarget=target, show_badge=False, icon_pos=icon_pos,
1018+
)
1019+
return [node], []
9881020

989-
Like ``{tool}`` but the badge is icon-only (no text label), square,
990-
and compact — designed for inline prose where the full badge is too wide.
991-
"""
992-
target = text.strip().replace("_", "-")
993-
node = _tool_ref_placeholder(
994-
rawtext, reftarget=target, show_badge=False, icon_only=True,
995-
)
996-
return [node], []
1021+
return role_fn
1022+
1023+
1024+
_toolicon_role = _make_toolicon_role("left")
1025+
_tooliconl_role = _make_toolicon_role("left")
1026+
_tooliconr_role = _make_toolicon_role("right")
1027+
_tooliconil_role = _make_toolicon_role("inline-left")
1028+
_tooliconir_role = _make_toolicon_role("inline-right")
9971029

9981030

9991031
def _badge_role(
@@ -1022,6 +1054,10 @@ def setup(app: Sphinx) -> ExtensionMetadata:
10221054
app.add_role("tool", _tool_role)
10231055
app.add_role("toolref", _toolref_role)
10241056
app.add_role("toolicon", _toolicon_role)
1057+
app.add_role("tooliconl", _tooliconl_role)
1058+
app.add_role("tooliconr", _tooliconr_role)
1059+
app.add_role("tooliconil", _tooliconil_role)
1060+
app.add_role("tooliconir", _tooliconir_role)
10251061
app.add_role("badge", _badge_role)
10261062
app.add_directive("fastmcp-tool", FastMCPToolDirective)
10271063
app.add_directive("fastmcp-tool-input", FastMCPToolInputDirective)

docs/_static/css/custom.css

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,12 +331,31 @@ h4:has(> .sd-badge) {
331331
margin-left: 0;
332332
}
333333

334+
/* Right-of-code variant: flip margins */
335+
code.docutils + .sd-badge.icon-only {
336+
margin-left: 0.3em;
337+
margin-right: 0;
338+
}
339+
334340
.sd-badge.icon-only::before {
335341
font-size: 10px;
336342
line-height: 1;
337343
margin: 0;
338344
}
339345

346+
/* Inline variants (inside <code>): tighter, inherit vertical alignment */
347+
.sd-badge.icon-only-inline {
348+
vertical-align: baseline;
349+
margin-right: 0.2em;
350+
margin-left: 0;
351+
}
352+
353+
/* Inline-right: flip margins */
354+
code.docutils .sd-badge.icon-only-inline:last-child {
355+
margin-left: 0.2em;
356+
margin-right: 0;
357+
}
358+
340359
/* ── Context-aware badge sizing ─────────────────────────── */
341360
h2 .sd-badge,
342361
h3 .sd-badge {

docs/demo.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,21 @@ Standalone badges via `{badge}`:
2424

2525
{toolref}`capture-pane` · {toolref}`send-keys` · {toolref}`search-panes` · {toolref}`wait-for-text` · {toolref}`kill-pane` · {toolref}`create-session` · {toolref}`split-window`
2626

27-
### `{toolicon}`code-linked with icon-only square badge
27+
### `{tooliconl}` — icon left, outside code
2828

29-
{toolicon}`capture-pane` · {toolicon}`send-keys` · {toolicon}`search-panes` · {toolicon}`wait-for-text` · {toolicon}`kill-pane` · {toolicon}`create-session` · {toolicon}`split-window`
29+
{tooliconl}`capture-pane` · {tooliconl}`send-keys` · {tooliconl}`search-panes` · {tooliconl}`wait-for-text` · {tooliconl}`kill-pane` · {tooliconl}`create-session` · {tooliconl}`split-window`
30+
31+
### `{tooliconr}` — icon right, outside code
32+
33+
{tooliconr}`capture-pane` · {tooliconr}`send-keys` · {tooliconr}`search-panes` · {tooliconr}`wait-for-text` · {tooliconr}`kill-pane` · {tooliconr}`create-session` · {tooliconr}`split-window`
34+
35+
### `{tooliconil}` — icon inline-left, inside code
36+
37+
{tooliconil}`capture-pane` · {tooliconil}`send-keys` · {tooliconil}`search-panes` · {tooliconil}`wait-for-text` · {tooliconil}`kill-pane` · {tooliconil}`create-session` · {tooliconil}`split-window`
38+
39+
### `{tooliconir}` — icon inline-right, inside code
40+
41+
{tooliconir}`capture-pane` · {tooliconir}`send-keys` · {tooliconir}`search-panes` · {tooliconir}`wait-for-text` · {tooliconir}`kill-pane` · {tooliconir}`create-session` · {tooliconir}`split-window`
3042

3143
### `{ref}` — plain text link
3244

0 commit comments

Comments
 (0)