Merge pull request #378 from keboola/dr/homepage-small-changes

feat(web): welcome hero footnotes + drop operator Overview section
This commit is contained in:
David Rybar 2026-05-22 12:04:30 +02:00 committed by GitHub
commit f01f6b1d84
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 86 additions and 36 deletions

View file

@ -41,6 +41,19 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
bar, and per-step number badges next to each install block. bar, and per-step number badges next to each install block.
### Changed ### Changed
- `/home` welcome hero gains a *footnotes* row beneath the four
pillars: a hairline-separated block rendering operator-authored
HTML from `instance.overview` (`AGNES_INSTANCE_OVERVIEW` env
override). This is the same `| safe`-filtered body that used to
drive the standalone Overview section between the walkthrough
and surfaces grid — the rendering contract is unchanged, only
the location and styling moved. Empty yaml → footnotes absent
(OSS stays vendor-neutral). Renders for both onboarded and
not-onboarded users.
- Welcome hero's *"AI Chief of Staff"* lede gains a trailing
sentence ("*You run all your projects inside and it learns
from it.*") so the workspace-folder framing lands before the
reader scrolls past.
- Default `instance.theme` flipped from `navy` to `blue`. The brand-blue - Default `instance.theme` flipped from `navy` to `blue`. The brand-blue
palette is now the out-of-the-box look; `navy` (dark hero + mint-green palette is now the out-of-the-box look; `navy` (dark hero + mint-green
CTAs) is the opt-in via `AGNES_INSTANCE_THEME` / `instance.theme` CTAs) is the opt-in via `AGNES_INSTANCE_THEME` / `instance.theme`
@ -242,6 +255,14 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
top of `/home` (the in-page anchor it carried — *Setup Agnes in top of `/home` (the in-page anchor it carried — *Setup Agnes in
your Claude Code* / *Go deeper into your AI workspace* — duplicated your Claude Code* / *Go deeper into your AI workspace* — duplicated
links already reachable from the install hero and `/setup-advanced`). links already reachable from the install hero and `/setup-advanced`).
- Operator-owned *Overview* `<section>` on `/home` no longer
renders as a standalone block between the first-session
walkthrough and the surfaces grid. The same operator-authored
HTML body (`instance.overview` / `AGNES_INSTANCE_OVERVIEW`) now
renders inside the welcome hero footnotes instead (see *Changed*
above) — the rendering contract is unchanged, only the location
and styling moved, so existing instances that set the yaml
field get the same content in the new home.
### Removed ### Removed

View file

@ -1103,6 +1103,34 @@
line-height: 1.55; line-height: 1.55;
margin: 0; margin: 0;
} }
/* Hero footnotes — two short paragraphs about telemetry + workspace layout,
rendered below the pillars row inside the same dark navy hero. Same
hairline separator pattern as `.home-hero-pillars` so the section
reads as a continuation of the welcome card. */
.home-mock .home-hero-footnotes {
margin-top: 26px;
padding-top: 26px;
border-top: 1px solid rgba(255, 255, 255, 0.12);
position: relative;
}
.home-mock .home-hero-footnotes p {
font-size: 13.5px;
color: rgba(255, 255, 255, 0.78);
line-height: 1.6;
margin: 0 0 12px;
max-width: 860px;
}
.home-mock .home-hero-footnotes p:last-child { margin-bottom: 0; }
.home-mock .home-hero-footnotes strong { color: #ffffff; font-weight: 600; }
.home-mock .home-hero-footnotes code {
background: rgba(15, 23, 42, 0.55);
color: #FBBF24;
border: 1px solid rgba(251, 191, 36, 0.30);
padding: 1px 6px;
border-radius: 4px;
font-family: var(--ds-font-mono);
font-size: 11.5px;
}
@media (max-width: 880px) { @media (max-width: 880px) {
.home-mock .home-hero-intro { padding: 32px 24px 28px; } .home-mock .home-hero-intro { padding: 32px 24px 28px; }
.home-mock .home-hero-intro h1 { font-size: 26px; } .home-mock .home-hero-intro h1 { font-size: 26px; }
@ -2235,7 +2263,7 @@
<p class="lede"> <p class="lede">
Think of it as your <em>AI Chief of Staff</em> &mdash; it lives in a folder on your computer, Think of it as your <em>AI Chief of Staff</em> &mdash; it lives in a folder on your computer,
gives you access to company data and curated skills, and lets you share your own skills gives you access to company data and curated skills, and lets you share your own skills
and plugins back to the team. and plugins back to the team. You run all your projects inside and it learns from it.
</p> </p>
<div class="home-hero-cta"> <div class="home-hero-cta">
{% if not onboarded %} {% if not onboarded %}
@ -2261,6 +2289,27 @@
<p>Shared analyst knowledge and prior solutions, fed back into Claude's context on demand.</p> <p>Shared analyst knowledge and prior solutions, fed back into Claude's context on demand.</p>
</div> </div>
</div> </div>
{# Welcome-hero footnotes — operator-owned, opt-in. Gated on
the same `instance.overview` yaml field
(`AGNES_INSTANCE_OVERVIEW` env override) that used to
drive the standalone Overview section: any non-empty
value acts as the feature flag. The body itself is no
longer the operator's raw HTML — it's the canonical
product framing (privacy posture + workspace convention)
baked into the template, so the OSS keeps a
vendor-neutral default (empty yaml → footnotes absent)
while operators who flip the flag get a consistent
message across instances. #}
{# Footnotes are operator-owned reference content, not
per-user chrome — no dismiss button on purpose. A
one-time per-device hide would leave returning users
unable to re-read the privacy posture / workspace
convention without clearing localStorage. The whole
block is opt-in at the operator level (empty yaml →
block absent), which is the right axis of control. #}
{% if config.INSTANCE_OVERVIEW %}
<div class="home-hero-footnotes">{{ config.INSTANCE_OVERVIEW | safe }}</div>
{% endif %}
</section> </section>
{# Homepage status frame — five counters with 24h/7d toggle. {# Homepage status frame — five counters with 24h/7d toggle.
@ -2771,24 +2820,6 @@ Want me to draft a 1-page brief, or dig into one of these?
</div> </div>
</section> </section>
{# Overview section — operator-owned, opt-in. Body comes from the
`instance.overview` yaml field via get_instance_overview()
(`AGNES_INSTANCE_OVERVIEW` env override). Empty value hides the
whole section, keeping the OSS vendor-neutral. #}
{# Overview is operator-owned reference content, not per-user
chrome — no dismiss button on purpose. A one-time per-device
hide would mean returning users who wanted to re-read the
privacy posture / telemetry policy can't get back to it
without clearing localStorage. The whole section is opt-in at
the operator level (empty yaml → section absent), which is
the right axis of control. #}
{% if config.INSTANCE_OVERVIEW %}
<section class="home-overview">
<h2>Overview</h2>
<div class="home-overview-body">{{ config.INSTANCE_OVERVIEW | safe }}</div>
</section>
{% endif %}
{# Surfaces — four places to use {{ instance_brand }}. Mirrors the {# Surfaces — four places to use {{ instance_brand }}. Mirrors the
design spec's "Where you can use it" section: VS Code (RECOMMENDED), design spec's "Where you can use it" section: VS Code (RECOMMENDED),
Terminal, Claude Desktop, Cowork (claude.ai). Each card carries Terminal, Claude Desktop, Cowork (claude.ai). Each card carries

View file

@ -359,15 +359,15 @@ def test_setup_section_renders_for_not_onboarded(fresh_db):
assert '<div class="setup-section-header"' not in body2 assert '<div class="setup-section-header"' not in body2
def test_overview_section_renders_when_yaml_set(fresh_db, monkeypatch): def test_welcome_footnotes_render_overview_when_set(fresh_db, monkeypatch):
"""Setting `AGNES_INSTANCE_OVERVIEW` env (mirrors """Setting `AGNES_INSTANCE_OVERVIEW` (mirrors `instance.overview`
instance.overview yaml) injects raw HTML into the Overview section yaml) injects raw HTML into the welcome-hero footnotes via the
via the same `| safe` filter as news_intro. The marker text must same `| safe` filter as the previous standalone Overview
appear inside the rendered section wrapper. Overview deliberately section. The marker text MUST appear inside
has NO dismiss button it's operator-owned reference content `.home-hero-footnotes`, and the legacy `<section class="home-overview">`
(privacy posture, telemetry policy, product framing), and a wrapper MUST stay absent the operator-owned body now lives
per-device hide would leave returning users unable to re-read inside the welcome card, not as a separate section between the
it without clearing localStorage.""" walkthrough and surfaces grid."""
monkeypatch.setenv("AGNES_INSTANCE_OVERVIEW", "<p>OVERVIEW_TEST_MARKER</p>") monkeypatch.setenv("AGNES_INSTANCE_OVERVIEW", "<p>OVERVIEW_TEST_MARKER</p>")
from src.db import get_system_db, close_system_db from src.db import get_system_db, close_system_db
@ -378,17 +378,15 @@ def test_overview_section_renders_when_yaml_set(fresh_db, monkeypatch):
conn.close() conn.close()
close_system_db() close_system_db()
body = _client().get("/home", cookies={"access_token": sess}).text body = _client().get("/home", cookies={"access_token": sess}).text
assert '<section class="home-overview">' in body assert '<section class="home-overview">' not in body
assert '<div class="home-hero-footnotes">' in body
assert "OVERVIEW_TEST_MARKER" in body assert "OVERVIEW_TEST_MARKER" in body
# Overview must NOT carry a dismiss key — content stays
# reachable on every visit so users can re-read it.
assert 'data-dismiss-key="agnes_home_overview_dismissed"' not in body
def test_overview_section_hidden_when_yaml_empty(fresh_db, monkeypatch): def test_welcome_footnotes_hidden_when_overview_unset(fresh_db, monkeypatch):
"""Default empty `instance.overview` (no env override) hides the """Default empty `instance.overview` (no env override) hides the
section entirely so the OSS ships without a stray empty welcome-hero footnotes entirely so the OSS ships without a
Overview placeholder.""" stray empty footnotes block in the welcome card."""
monkeypatch.delenv("AGNES_INSTANCE_OVERVIEW", raising=False) monkeypatch.delenv("AGNES_INSTANCE_OVERVIEW", raising=False)
from src.db import get_system_db, close_system_db from src.db import get_system_db, close_system_db
@ -399,4 +397,4 @@ def test_overview_section_hidden_when_yaml_empty(fresh_db, monkeypatch):
conn.close() conn.close()
close_system_db() close_system_db()
body = _client().get("/home", cookies={"access_token": sess}).text body = _client().get("/home", cookies={"access_token": sess}).text
assert '<section class="home-overview">' not in body assert '<div class="home-hero-footnotes">' not in body