From 8e2a55329868f86bd7dde716d165d9e45c4f082a Mon Sep 17 00:00:00 2001 From: ACE Engineering Date: Sat, 15 Aug 2026 12:54:35 -0700 Subject: [PATCH] fix(dashboard): introduce ACE Fleet in the About section, and unbreak the rail anchors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The About banner described the sidecar to a reader who has no way of telling it apart from the company: someone arriving through the open-source repo saw "ACE Sidecar — local developer observability" and nothing about the platform it is a slice of. It now names ACE Fleet, says what the proxy does, and places the sidecar as one open-sourced vertical of it. The pills followed the headline: LOCAL-FIRST and ZERO-CLOUD OVERHEAD are true of this page but read as claims about Fleet in that position, which inverts what Fleet is, so they are now DROP-IN PROXY and CUT AI BILLS. The outbound link opens in a new tab rather than dropping the reader out of their dashboard. Separately, the rail's "Common questions" pointed at #s11 and landed on Live Stream. The link was a symptom: the questions section was numbered 10, the same as Session Time, so the page carried two id='s10' — invalid, and a fragment jump resolves to whichever comes first. The tail also ran 11, 13, 10, 12 in document order. Renumbered to follow the order the sections are emitted in — Prometheus 13 to 12, Common questions 10 to 13, About 12 to 14 — with the rail, the About CTA and three docstrings that had drifted to other numbers updated to match. Every rail target now resolves to the section it names. Button styling came along with the banner, since the CTA sits inside it: spacing between glyph and label moves from a margin on the arrow to gap on the flex container, which is what lets the arrow slide on hover; word-break:break-all is dropped so "acefleet.dev" cannot split mid-word; and the links gain a :focus-visible ring, having previously been unreachable to indicate by keyboard. Co-Authored-By: Claude Opus 5 --- src/ace/sidecar/dashboard_render.py | 83 +++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/src/ace/sidecar/dashboard_render.py b/src/ace/sidecar/dashboard_render.py index cb13f8f..8af0d38 100644 --- a/src/ace/sidecar/dashboard_render.py +++ b/src/ace/sidecar/dashboard_render.py @@ -271,12 +271,32 @@ text-transform:uppercase;margin-bottom:10px} .card h3{margin:4px 0 8px;font-size:14.5px;color:var(--ink);font-weight:600;letter-spacing:-.01em} .card p{margin:0 0 16px;color:var(--ink-3);font-size:13px;line-height:1.6;flex:1} -.lk{align-self:flex-start;display:inline-flex;align-items:center;font-family:var(--mono);font-size:12px; -color:var(--mint);background:rgba(0,230,153,0.06);border:1px solid rgba(0,230,153,0.25); -border-radius:6px;padding:6px 14px;text-decoration:none;transition:all .2s ease;word-break:break-all} +/* Spacing between glyph and label is `gap`, not a margin on the arrow: the arrow slides on + hover, and a transformed element with a margin drags the gap along with it. break-all is + deliberately not used — it would split "acefleet.dev" mid-word — but the mail buttons still + have to survive a long address in a 270px card, so wrapping is allowed anywhere only once a + word genuinely cannot fit. */ +.lk{align-self:flex-start;display:inline-flex;align-items:center;gap:8px;font-family:var(--mono); +font-size:12px;line-height:1.2;color:var(--mint);background:rgba(0,230,153,0.06); +border:1px solid rgba(0,230,153,0.25);border-radius:7px;padding:9px 14px;text-decoration:none; +overflow-wrap:anywhere; +transition:background .2s ease,border-color .2s ease,color .2s ease,box-shadow .2s ease,transform .2s ease} .lk:hover{background:rgba(0,230,153,0.16);border-color:var(--mint);color:#ffffff; box-shadow:0 0 12px rgba(0,230,153,0.25);transform:translateY(-1px)} -.site-lk{background:rgba(0,230,153,0.1);border-color:rgba(0,230,153,0.4);font-weight:600} +.lk:focus-visible{outline:2px solid var(--mint);outline-offset:2px} +.lk:active{transform:translateY(0)} +.lk .arw{transition:transform .2s ease} +.lk:hover .arw{transform:translateX(3px)} +/* The one outbound link on the page, so it carries more weight than the mail buttons. */ +.site-lk{background:linear-gradient(135deg,rgba(0,230,153,0.22) 0%,rgba(0,230,153,0.08) 100%); +border-color:rgba(0,230,153,0.55);color:#EAFFF6;font-weight:600;font-size:12.5px;padding:11px 18px; +box-shadow:0 2px 10px -2px rgba(0,230,153,0.22)} +.site-lk:hover{background:linear-gradient(135deg,rgba(0,230,153,0.34) 0%,rgba(0,230,153,0.16) 100%); +box-shadow:0 6px 18px -4px rgba(0,230,153,0.35)} +@media (prefers-reduced-motion:reduce){ +.lk,.lk .arw{transition:none} +.lk:hover{transform:none} +.lk:hover .arw{transform:none}} .foot{color:var(--ink-4);font-size:11.5px;margin-top:36px;padding-top:14px; border-top:1px solid var(--line);line-height:1.65} /* ---- modal drawer ---- */ @@ -1009,7 +1029,7 @@ def _qa_item(q: str, body: str, open_first: bool = False) -> str: def _qa(f: Optional[Dict[str, Any]], rc: Dict[str, Any]) -> str: - """§ 09 — the questions this dashboard reliably provokes, answered with the reader's own + """§ 13 — the questions this dashboard reliably provokes, answered with the reader's own numbers. Every figure is recomputed from the scope above rather than written into the prose. The @@ -1017,7 +1037,7 @@ def _qa(f: Optional[Dict[str, Any]], rc: Dict[str, Any]) -> str: measure per-request latency. """ head = _sec( - "10", + "13", "COMMON QUESTIONS", "Why the token counts look the way they do.", "Mechanism, priced against your scope.", @@ -1367,12 +1387,17 @@ def _mailto(subject: str) -> str: def _about() -> str: - """§ 10 — what ACE is, and how to reach the people who build it. + """§ 14 — what ACE is, and how to reach the people who build it. Answers the two questions the measurements above cannot: what the measuring thing *is*, and who to contact about it. One address for questions, feature requests, collaborations, business and team rollout, with ``?subject=`` prefill as the routing — it degrades to a plain mailto if the client ignores it. + + The banner names ACE Fleet because this page is the only thing most readers will ever see + of it: a reader who arrives through the open-source sidecar has no way to tell that it is + one vertical of a broader cost-control platform rather than the whole company, and that is + exactly the impression the section exists to correct. """ cards = ( ( @@ -1405,19 +1430,30 @@ def _about() -> str: body = "".join( f"
{escape(k)}
" f"

{escape(h)}

" - f"

{escape(p)}

✉ {escape(label)}
" + f"

{escape(p)}

✉ {escape(label)}" for k, h, p, href, label in cards ) return ( - _sec("12", "ACE SIDECAR", "Who makes this.", "And how to reach them.", "ABOUT") + _sec("14", "ACE SIDECAR", "Who makes this.", "And how to reach them.", "ABOUT") + "
" "
" - "
ACE Sidecar — local developer observability for AI coding agents.
" - "
" - "✓ LOCAL-FIRST" - "⚡ ZERO-CLOUD OVERHEAD" + "
ACE Sidecar is built by ACE Fleet.
" + # The pills sit beside a headline about ACE Fleet, so they describe the proxy, not the + # page they are printed on: what it costs to adopt, and what it does to the bill. + # LOCAL-FIRST and ZERO-CLOUD OVERHEAD were true of the sidecar but read as claims + # about Fleet in this position, which inverts what it is. + "
" + "⚡ DROP-IN PROXY" + "📉 CUT AI BILLS" "
" - f"
🌐 Visit acefleet.dev
" + "

ACE Fleet is a cost-saving proxy for companies scaling AI applications — it sits in " + "front of the model providers and cuts what an organisation spends on inference as that " + "spend grows, across every workload rather than any single one.

" + "

This sidecar is the coding-agent slice of that work, open-sourced on its own: the " + "same accounting turned on one developer's machine, where the spend is small enough to " + "check by hand and the levers are easy to see. It is a showcase of the approach — the " + "platform behind it is considerably wider in scope.

" + f"
🌐 Visit acefleet.dev
" "
" f"
{body}
" ) @@ -1425,9 +1461,9 @@ def _about() -> str: def _prometheus_section(d: Dict[str, Any]) -> str: - """§ 13 — Prometheus metrics exposition & offline telemetry ingestion guide.""" + """§ 12 — Prometheus metrics exposition & offline telemetry ingestion guide.""" head = _sec( - "13", + "12", "PROMETHEUS METRICS", "Machine-readable telemetry exporter.", "Prometheus, Grafana Alloy & OTel scraping.", @@ -1475,7 +1511,7 @@ def _prometheus_section(d: Dict[str, Any]) -> str: "
" "" "" - "🌐 Open Raw Stream " + "🌐 Open Raw Stream" "
" "
" "
" @@ -1512,10 +1548,15 @@ def _prometheus_section(d: Dict[str, Any]) -> str: ("✓", "Installed Skills", "08"), ("◷", "Sessions", "09"), ("⧗", "Time", "10"), - ("📊", "Prometheus Metrics", "13"), - ("?", "Common questions", "11"), - ("◈", "About ACE", "12"), + ("📊", "Prometheus Metrics", "12"), + ("?", "Common questions", "13"), + ("◈", "About ACE", "14"), ) +# The numbers are the anchors — `#s` — so they have to match the section each rail item +# means, and each has to be unique across the page. Both failed here: Common questions pointed +# at #s11 and landed on Live Stream, because the questions section was itself numbered 10 and +# collided with Session Time, leaving two id='s10' on one document. Numbers now follow the +# order the sections are emitted in; keep it that way when adding one. @@ -1656,7 +1697,7 @@ def _rail(d: Dict[str, Any]) -> str:

Local developer observability sidecar for AI coding agents. Intercepts local Claude Code & Antigravity traffic, mines session transcripts, proposes workflow skills, and exposes standard Prometheus metrics for time-series scraping.

- About & contact + About & contact
"""