feat(brand): wire instance.logo_svg into header brand slot (release 0.54.6) (#289)

* feat(brand): inline operator SVG logo + drop header subtitle (release 0.54.6)

Three header tweaks, one PR:

1. _app_header.html drops the small uppercase subtitle line below the
   brand. instance.subtitle still flows into the CLAUDE.md preamble +
   init welcome template ("Operated by …"); only the web header chrome
   loses it.

2. get_instance_logo_svg() in app/instance_config.py reads
   instance.logo_svg (yaml) / AGNES_INSTANCE_LOGO_SVG (env). The
   yaml field was already documented in instance.yaml.example and the
   template already supported inline <svg> via {{ config.LOGO_SVG |
   safe }}, but router.py:344 hard-coded LOGO_SVG = "" — the middle
   wire was missing. Now operators can paste a lockup directly into
   their instance.yaml under instance.logo_svg: | and have it render
   in the header. Resolution mirrors get_instance_brand (env > yaml >
   ""). instance.name remains independent: drives browser <title>
   tags + page h1s + CLAUDE.md heading; the SVG is the web-header
   visual only.

3. .app-header-logo svg gains max-height: 40px; width: auto; so any
   operator's lockup scales via its viewBox to fit the 72px header
   without per-asset width/height edits. Pairs with #2 — without the
   clamp, raw artwork (e.g. a 1600x430 lockup) overflows the chrome.

Release-cut included per the same-PR rule (Unreleased contained only
these bullets after rebase onto 0.54.5).

* revert: keep app-header-subtitle span — out of scope for this PR

Initial commit dropped the subtitle line on the assumption that
the user wanted both the secondary header line AND the future-SVG
brand cleaned up. The actual ask was narrower: drop the hostname
suffix that renders inside instance.name ("Foundry AI (hostname)"),
which is a startup.sh concern, not a template one. Restore the
subtitle span and the CHANGELOG bullet that announced its removal.
PR scope narrows to LOGO_SVG wiring + CSS clamp only.

* fix(header): hide subtitle span when instance.subtitle is empty

Pre-fix the template fell back to the literal string 'Data Analyst
Portal' when INSTANCE_SUBTITLE was unset, so operators who left the
field empty saw a stray hardcoded label below their brand. Switched
to a Jinja {% if %} guard around the whole <span class="app-header-
subtitle"> so an empty subtitle produces no element at all — clean
header chrome instead of placeholder leak.

* feat(home): hide install-hero once onboarded + X close button

- Wrap the entire install-hero in `{% if not onboarded %}` so once
  `users.onboarded=true` (auto-flipped by `agnes init` POSTing
  /api/me/onboarded, or by the new X / existing fallback button) the
  blue hero disappears entirely. Pre-PR the onboarded branch reused
  the same shell with a "Welcome back" header + "Steps 1–4 done" badge
  + minimize toggle, which visually outweighed the actual nav hub.
- Add a circular × close button (top-right of the hero, rendered only
  when not-onboarded). Click → window.confirm() asking the user to
  acknowledge onboarding → POST /api/me/onboarded → reload. The
  confirm string intentionally avoids the literal phrase
  "Mark me as offboarded" because cli/commands/onboarded.py::status
  scans /home's rendered HTML for that exact marker as a fallback for
  the api/me/profile check.
- Lift the offboard escape hatch out of the hero into a discrete
  `.offboard-strip` rendered below, gated `{% if onboarded %}`. Lets
  the analyst flip back to the install view after wiping their
  workspace folder.
- Centralize the /api/me/onboarded POST into a `postOnboarded()` JS
  helper reused by the hero X, the existing "Mark me as onboarded"
  fallback button, and the new offboard button.

Tests updated to match the new behavior:
- `test_home_onboarded_user_sees_nav_hub` — asserts the hero is gone
  and the offboard strip is the only setup-flow remnant.
- `test_minimize_toggle_no_longer_rendered` (renamed) — asserts the
  minimize toggle is absent in both states (was previously rendered
  inside the now-hidden onboarded branch of the hero).
- `test_home_no_auto_transition_after_post_until_reload` — checks
  offboard-strip presence post-flip instead of the removed
  "Welcome back" hero copy.

* fix(home): X-close button used invalid source enum, hit 422

The X button's data-target-source was 'self_acknowledged_x' to give
audit_log a separate marker for X-vs-button-driven flips. But
app/api/me.py:38's OnboardedRequest pins source to a Literal of
['agnes_init', 'self_acknowledged', 'self_unmark']  — pydantic
returned 422 on every X click.

Confusing side effect: both buttons share self-mark-status as the
status element, so the failed X click rendered 'Failed (422)' next
to the still-functional 'Mark me as onboarded' button. Looked like
the button itself broke.

Fix: drop the _x suffix. Both surfaces now POST source='self_acknowledged'.
Distinction in audit_log is not load-bearing — the source field
captures user intent ('I'm onboarded'), not the specific UI affordance.
This commit is contained in:
Vojtech 2026-05-13 21:25:46 +04:00 committed by GitHub
parent 471c63d711
commit 14ddaf1e8e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 239 additions and 109 deletions

View file

@ -10,6 +10,42 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
## [Unreleased]
## [0.54.6] — 2026-05-13
### Changed
- Header brand: wired `instance.logo_svg` (yaml) /
`AGNES_INSTANCE_LOGO_SVG` (env) into the brand slot via a new
`get_instance_logo_svg()` helper in `app/instance_config.py`.
Previously the yaml field was documented in
`config/instance.yaml.example` and the template already supported
inline SVG via `config.LOGO_SVG | safe`, but the router
hard-coded `LOGO_SVG = ""` — operators can now drop inline SVG
markup into their `instance.yaml` and have it appear in the
header. `instance.name` continues to drive browser titles and
page headings; the two fields are independent.
- Header brand: clamped `.app-header-logo svg` to `max-height: 40px;
width: auto;` (was just `display: block;`) so any operator's
`logo_svg` scales via its viewBox to fit the 72px-tall header
without per-asset width/height edits.
- Header subtitle: empty `instance.subtitle` now renders nothing
(the whole `<span class="app-header-subtitle">` is skipped)
instead of falling back to the literal placeholder string
"Data Analyst Portal". Operators who leave the field unset get a
clean header instead of a stray hardcoded label.
- `/home` install-hero now disappears entirely once the user is
onboarded (`users.onboarded=true`, set by `agnes init`'s POST to
`/api/me/onboarded` or by an explicit click). Pre-fix the hero
kept rendering a "Welcome back — you're set up" variant that
visually outweighed the actual nav hub. Adds a close (×) button
in the top-right of the hero — confirms with a `window.confirm()`
dialog asking the user to acknowledge onboarding before flipping
state, so a stray click won't hide the setup steps. The
offboarding escape hatch (previously living inside the hero's
onboarded branch) moves to a discrete strip below — visible only
when onboarded, so analysts who wipe `~/{{ workspace_dir }}` can
flip back without digging through settings.
## [0.54.5] — 2026-05-13
### Internal

View file

@ -278,6 +278,23 @@ def get_instance_brand() -> str:
return value or "Agnes"
def get_instance_logo_svg() -> str:
"""Raw inline ``<svg>`` markup rendered into the header brand slot
(``_app_header.html``). When non-empty, replaces the text brand in
the header typical use is a lockup that already contains the
brand wordmark. When empty, the header falls back to
:func:`get_instance_name` as text.
Resolution: ``AGNES_INSTANCE_LOGO_SVG`` env > ``instance.logo_svg``
YAML > ``""``. Mirrors :func:`get_instance_brand` so Terraform env
overrides work the same way.
"""
raw = os.environ.get("AGNES_INSTANCE_LOGO_SVG")
if raw is None:
raw = get_value("instance", "logo_svg", default="")
return (raw or "").strip()
def get_workspace_dir_name() -> str:
"""Filesystem-safe folder name for the analyst's local workspace
(``~/<workspace_dir_name>``). Defaults to :func:`get_instance_brand`

View file

@ -25,6 +25,7 @@ from app.instance_config import (
get_gws_oauth_credentials, get_home_automode_visibility,
get_instance_admin_email, get_atlassian_base_url,
get_instance_brand, get_workspace_dir_name,
get_instance_logo_svg,
)
from app.web.connector_prompts import all_connector_prompts
from src.repositories.sync_state import SyncStateRepository
@ -343,7 +344,7 @@ def _build_context(
INSTANCE_NAME = get_instance_name()
INSTANCE_SUBTITLE = get_instance_subtitle()
INSTANCE_COPYRIGHT = ""
LOGO_SVG = ""
LOGO_SVG = get_instance_logo_svg()
TELEGRAM_BOT_USERNAME = os.environ.get("TELEGRAM_BOT_USERNAME", "")
SSH_ALIAS = "data-analyst"
SERVER_HOST = os.environ.get("SERVER_HOST", "")

View file

@ -2109,7 +2109,11 @@ a.slack-badge:hover {
font-weight: 600;
font-size: 16px;
}
.app-header-logo svg { display: block; }
.app-header-logo svg {
display: block;
max-height: 40px;
width: auto;
}
a.app-header-logo:focus-visible {
outline: 2px solid var(--primary, #6366f1);
outline-offset: 2px;

View file

@ -6,7 +6,7 @@
<a class="app-header-logo" href="/" aria-label="Home">
{% if config.LOGO_SVG %}{{ config.LOGO_SVG | safe }}{% else %}{{ config.INSTANCE_NAME or 'Data Analyst Portal' }}{% endif %}
</a>
<span class="app-header-subtitle">{{ config.INSTANCE_SUBTITLE or 'Data Analyst Portal' }}</span>
{% if config.INSTANCE_SUBTITLE %}<span class="app-header-subtitle">{{ config.INSTANCE_SUBTITLE }}</span>{% endif %}
</div>
<div class="app-header-right">
{% set _path = request.url.path %}

View file

@ -24,6 +24,7 @@
.home-mock * { box-sizing: border-box; }
.home-mock .install-hero {
position: relative;
background: linear-gradient(135deg, #0073D1 0%, #0056A3 100%);
color: white;
border-radius: 16px;
@ -31,6 +32,51 @@
margin-bottom: 22px;
box-shadow: 0 8px 24px rgba(0, 86, 163, 0.18);
}
.home-mock .install-hero-close {
position: absolute;
top: 14px;
right: 14px;
width: 32px;
height: 32px;
border: none;
border-radius: 50%;
background: rgba(255, 255, 255, 0.14);
color: white;
font-size: 18px;
line-height: 1;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
transition: background 0.15s;
}
.home-mock .install-hero-close:hover,
.home-mock .install-hero-close:focus-visible {
background: rgba(255, 255, 255, 0.28);
outline: none;
}
.home-mock .offboard-strip {
margin: 0 0 22px;
padding: 10px 14px;
border: 1px solid var(--hp-border);
border-radius: 10px;
background: var(--hp-border-light);
color: var(--hp-text-secondary);
font-size: 13px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.home-mock .offboard-strip button {
border: 1px solid var(--hp-border);
background: white;
border-radius: 6px;
padding: 4px 10px;
font-size: 13px;
cursor: pointer;
}
.home-mock .offboard-strip button:hover { background: var(--hp-border-light); }
.home-mock .install-hero .eyebrow {
font-size: 11px;
font-weight: 600;
@ -1154,29 +1200,22 @@
{% set display_name = (user.name or (user.email or "").split("@")[0] or "there") %}
{# Install-hero renders only for not-onboarded users. Once `agnes init`
POSTs /api/me/onboarded (or the user clicks the in-hero X) the hero
disappears entirely — the rest of /home (connector tiles, news,
etc.) stays. Offboarding escape hatch moved to a discrete strip
below; see `.offboard-strip`. #}
{% if not onboarded %}
<div class="install-hero">
{% if onboarded %}
<div class="eyebrow">Welcome back, {{ display_name }} — your workspace is ready</div>
<h1>You're set up — keep this page handy</h1>
<p class="lead">
Your local {{ instance_brand }} install is confirmed. The steps below stay useful for <strong>adding another machine</strong>, <strong>connecting more services</strong>, or <strong>turning on auto-accept mode</strong>. Skip whatever you don't need; nothing here re-runs unless you click it.
</p>
{% else %}
<button type="button" class="install-hero-close" id="installHeroClose"
data-target-source="self_acknowledged"
aria-label="I'm already set up — close this setup hero">&times;</button>
<div class="eyebrow">Welcome, {{ display_name }} — let's get you set up</div>
<h1>Connect Claude Code on your machine to your team's data</h1>
<p class="lead">
{{ instance_brand }} gives <strong>Claude Code</strong> on your computer access to your team's <strong>curated data, plugins, and shared knowledge</strong> — so you can ask questions and get answers in plain language, right from your terminal. This page walks you through the <strong>one-time setup (~10 minutes)</strong>. Everything it installs lives in your home folder (<code style="background: rgba(255,255,255,0.12); padding: 1px 6px; border-radius: 4px; font-family: var(--hp-font-mono); font-size: 12.5px;">~/{{ workspace_dir }}</code>) and can be removed in one command.
</p>
{% endif %}
{% if onboarded %}
<div class="install-done" role="status" aria-live="polite">
<span class="check" aria-hidden="true">&#x2705;</span>
<span><strong>Steps 14 done</strong> — Claude Code installed, auto-mode set, workspace folder created, {{ instance_brand }} ready in <code>~/{{ workspace_dir }}</code>. The full install steps stay one click away under the offboard control below.</span>
</div>
{% endif %}
{% if not onboarded %}
<div class="install-block">
<div class="label">Step 1 — install Claude Code</div>
<div class="os-tabs" role="tablist" aria-label="Operating system">
@ -1281,53 +1320,45 @@ Set-Location "$HOME\{{ workspace_dir }}"</span>
</details>
</div>
{% endif %}
{# P1-6 — auto-detect badge is the PRIMARY affordance after the
install-script copy: agnes-init's first POST to
/api/me/onboarded flips state automatically and the page
reloads. The manual "Mark me as onboarded" button below it
stays as a fallback when auto-flip never lands. #}
{% if not onboarded %}
<div class="auto-detect-badge" role="status" aria-live="polite">
<span class="pulse" aria-hidden="true"></span>
<span>Waiting for your first <code>agnes pull</code> — auto-detects within ~30 s of the setup script finishing.</span>
</div>
{% endif %}
{# Self-mark control lives inside the blue hero in both states.
When onboarded, the install steps above are hidden so this is
the only thing rendered below the lead paragraph. #}
{# Self-mark fallback for the auto-flip. The hero's X close button
does the same thing more visibly; both target the not-onboarded
→ onboarded direction. The onboarded → offboarded variant lives
below the hero (.offboard-strip) so it stays reachable once the
hero is gone. #}
<div class="self-mark">
{% if onboarded %}
Wiped your workspace or want the full setup view back?
<button id="self-mark-btn" type="button"
data-target-onboarded="false"
data-target-source="self_unmark">Mark me as offboarded</button>
{% else %}
Already set this up?
<button id="self-mark-btn" type="button"
data-target-onboarded="true"
data-target-source="self_acknowledged">Mark me as onboarded</button>
{% endif %}
<span id="self-mark-status" class="status" role="status" aria-live="polite"></span>
</div>
{% if onboarded %}
{# User-controlled minimize toggle for Connect-your-tools.
Default OFF (section renders flat). State persists in
localStorage so the choice is per-device. The agnes-init
auto-flip of users.onboarded never triggers a collapse on
its own — only an explicit click here does. The auto-mode
block used to be a peer collapsible (`step3`); it now lives
inside the install-hero as Step 2 and is not collapsible. #}
<div class="setup-minimize">
<button id="setupMinimizeToggle" type="button" aria-pressed="false">
Minimize setup view
</button>
</div>
{% endif %}
{% if onboarded %}
{# Offboarding escape hatch shown only after the hero has disappeared.
Lets the analyst (e.g. after wiping ~/FoundryAI) flip the
users.onboarded boolean back to false so the full install hero
renders again on next reload. Discrete by design — onboarded
users land on /home expecting the nav hub, not a setup screen. #}
<div class="offboard-strip">
<span>Workspace ready — wiped it and need the full setup view back?</span>
<button id="offboard-btn" type="button"
data-target-source="self_unmark">Mark me as offboarded</button>
<span id="offboard-status" class="status" role="status" aria-live="polite"></span>
</div>
{% endif %}
{# Auto-mode card used to live here as a `<details>` reference block;
moved into the install-hero as the new Step 2 so users enable it
@ -1638,16 +1669,13 @@ Set-Location "$HOME\{{ workspace_dir }}"</span>
});
}
var btn = document.getElementById('self-mark-btn');
var status = document.getElementById('self-mark-status');
if (!btn) return;
btn.addEventListener('click', function () {
// Direction comes from data-attrs the template sets per render —
// onboarded view → flip to FALSE (offboard), not-onboarded → flip to TRUE.
var targetOnboarded = btn.getAttribute('data-target-onboarded') === 'true';
var targetSource = btn.getAttribute('data-target-source') || 'self_acknowledged';
btn.disabled = true;
status.textContent = targetOnboarded ? 'Marking…' : 'Resetting…';
// Shared poster for /api/me/onboarded — reused by every UI surface
// that flips users.onboarded (in-hero X close, "Mark me as onboarded"
// fallback button, the offboard strip). Reloads on success so the
// template re-renders with the new state.
function postOnboarded(triggerBtn, statusEl, targetOnboarded, targetSource) {
if (triggerBtn) triggerBtn.disabled = true;
if (statusEl) statusEl.textContent = targetOnboarded ? 'Marking…' : 'Resetting…';
fetch('/api/me/onboarded', {
method: 'POST',
credentials: 'same-origin',
@ -1655,17 +1683,59 @@ Set-Location "$HOME\{{ workspace_dir }}"</span>
body: JSON.stringify({ source: targetSource, onboarded: targetOnboarded }),
}).then(function (resp) {
if (resp.ok) {
status.textContent = 'Done. Reloading…';
if (statusEl) statusEl.textContent = 'Done. Reloading…';
window.location.reload();
} else {
status.textContent = 'Failed (' + resp.status + '). Try again.';
btn.disabled = false;
if (statusEl) statusEl.textContent = 'Failed (' + resp.status + '). Try again.';
if (triggerBtn) triggerBtn.disabled = false;
}
}).catch(function () {
status.textContent = 'Network error. Try again.';
btn.disabled = false;
if (statusEl) statusEl.textContent = 'Network error. Try again.';
if (triggerBtn) triggerBtn.disabled = false;
});
}
// "Mark me as onboarded" fallback button inside the hero (rendered
// only when not-onboarded — the X close button is the primary path).
var btn = document.getElementById('self-mark-btn');
var status = document.getElementById('self-mark-status');
if (btn) {
btn.addEventListener('click', function () {
postOnboarded(
btn, status, true,
btn.getAttribute('data-target-source') || 'self_acknowledged'
);
});
}
// Hero X close — confirm first so a stray click doesn't flip state.
var heroClose = document.getElementById('installHeroClose');
if (heroClose) {
heroClose.addEventListener('click', function () {
var ok = window.confirm(
"Are you already onboarded? Closing this will mark your account as onboarded " +
"and hide the setup steps. You can revert later from the strip below the hero."
);
if (!ok) return;
postOnboarded(
heroClose, status, true,
heroClose.getAttribute('data-target-source') || 'self_acknowledged'
);
});
}
// Offboarding strip — only rendered when onboarded. Flips back to
// the install hero on next reload.
var offBtn = document.getElementById('offboard-btn');
var offStatus = document.getElementById('offboard-status');
if (offBtn) {
offBtn.addEventListener('click', function () {
postOnboarded(
offBtn, offStatus, false,
offBtn.getAttribute('data-target-source') || 'self_unmark'
);
});
}
// ── Minimize-setup toggle ────────────────────────────────────────
// Default OFF: sections render flat (no <summary> visible).

View file

@ -28,8 +28,14 @@ instance:
# "FoundryAI"). Set explicitly only if you want a folder
# name that differs from the auto-derivation. Env override:
# AGNES_WORKSPACE_DIR_NAME.
# logo_svg: Full <svg> element for header logo (optional, default: Keboola logo)
# Example: '<svg width="120" height="30" viewBox="0 0 100 30" xmlns="http://www.w3.org/2000/svg"><text y="22" font-size="24" fill="#333">Logo</text></svg>'
# logo_svg: | # Inline <svg> element rendered into the header brand slot.
# <svg width="120" height="30" viewBox="0 0 100 30" xmlns="http://www.w3.org/2000/svg">
# <text y="22" font-size="24" fill="#333">Logo</text>
# </svg>
# # When set, the SVG replaces the text brand in the header.
# # `name` above still drives browser <title> text and page
# # headings — keep it populated. Env override:
# # AGNES_INSTANCE_LOGO_SVG.
# sync_interval: "1 hour" # Cadence shown in analyst CLAUDE.md (e.g., "1 hour", "30 minutes", "daily")
# admin_email: "ops@acme.com" # Operator contact shown on /home GWS connector tile as
# an "Email admin" mailto button (analysts whose operator

View file

@ -1,6 +1,6 @@
[project]
name = "agnes-the-ai-analyst"
version = "0.54.5"
version = "0.54.6"
description = "Agnes — AI Data Analyst platform for AI analytical systems"
requires-python = ">=3.11,<3.14"
license = "MIT"

View file

@ -75,11 +75,14 @@ def test_home_not_onboarded_user_sees_setup_view(fresh_db):
def test_home_onboarded_user_sees_nav_hub(fresh_db):
"""A TRUE-onboarded user gets the post-onboarding view, identifiable by
the 'Welcome back' hero, the 'Step 1 & Step 2 done' completion badge,
the offboard control, and the absence of the inline Step 1 / Step 2
install commands. Step 3 (auto-mode), connectors, and the rest stay
visible they remain useful after onboarding."""
"""A TRUE-onboarded user gets the post-onboarding view: the blue
install-hero is gone entirely (no welcome banner, no completion
badge, no inline step commands), the offboard escape strip is the
only setup-flow remnant rendered, and the rest of /home (connector
tiles, news, etc.) stays. PR #289 collapsed the dual-state hero
into a single not-onboarded-only render pre-PR the onboarded
branch reused the same `.install-hero` shell with welcome copy
and a "Steps 14 done" badge."""
from src.db import get_system_db, close_system_db
conn = get_system_db()
@ -93,13 +96,11 @@ def test_home_onboarded_user_sees_nav_hub(fresh_db):
resp = c.get("/home", cookies={"access_token": sess})
assert resp.status_code == 200
body = resp.text
assert "Welcome back" in body
# Banner copy updated when the explicit "create workspace folder"
# step was inserted between auto-mode and install-Agnes — completion
# badge now spans Steps 1-4 (install Claude Code, auto-mode, mkdir
# workspace, install Agnes from Claude Code).
assert "Steps 1&#8211;4 done" in body or "Steps 14 done" in body
assert "Mark me as offboarded" in body # offboard control visible
# Install hero entirely absent for onboarded users.
assert '<div class="install-hero">' not in body
# Offboard escape strip + its button replace the in-hero self-mark control.
assert '<div class="offboard-strip">' in body
assert "Mark me as offboarded" in body
# All four inline install-blocks are hidden post-onboarding — the
# labels rendered inside the install-block divs go away.
assert "Step 1 — install Claude Code" not in body
@ -152,16 +153,22 @@ def test_connectors_render_flat_when_onboarded_by_default(fresh_db):
assert 'class="home-mock"\n' in body or '<div class="home-mock">' in body
def test_minimize_toggle_visible_only_when_onboarded(fresh_db):
"""The "Minimize setup view" toggle markup is rendered for onboarded
users (so they can opt into the collapsed view) and absent for
not-onboarded users (where the install steps already dominate)."""
def test_minimize_toggle_no_longer_rendered(fresh_db):
"""The "Minimize setup view" toggle used to live inside the
onboarded-branch of the install-hero. PR #289 hides the hero
entirely once `users.onboarded=true`, so the minimize toggle
has no rendering site anymore verify the markup is absent
from both states. (The localStorage `agnes_home_setup_minimized`
flag and its applyMinimize() JS handler stay in the page so a
stale flag from a pre-PR session no-ops cleanly.)"""
from src.db import get_system_db, close_system_db
# Not-onboarded → no toggle button.
for onboarded in (False, True):
conn = get_system_db()
try:
_, sess = _make_user_and_session(conn, onboarded=False)
_, sess = _make_user_and_session(
conn, email=f"user-{onboarded}@example.com", onboarded=onboarded
)
finally:
conn.close()
close_system_db()
@ -171,19 +178,6 @@ def test_minimize_toggle_visible_only_when_onboarded(fresh_db):
assert '<button id="setupMinimizeToggle"' not in resp.text
assert 'class="setup-minimize"' not in resp.text
# Onboarded → toggle button rendered inside the install-hero.
conn = get_system_db()
try:
_, sess2 = _make_user_and_session(conn, email="b@example.com", onboarded=True)
finally:
conn.close()
close_system_db()
c2 = _client()
resp2 = c2.get("/home", cookies={"access_token": sess2})
assert resp2.status_code == 200
assert '<button id="setupMinimizeToggle"' in resp2.text
assert 'class="setup-minimize"' in resp2.text
def test_home_no_auto_transition_after_post_until_reload(fresh_db):
"""POST /api/me/onboarded flips the flag in the DB but the in-flight
@ -213,7 +207,9 @@ def test_home_no_auto_transition_after_post_until_reload(fresh_db):
assert flip.status_code == 200
post = c.get("/home", cookies={"access_token": sess})
assert "Welcome back" in post.text # nav hub view
# PR #289: hero disappears entirely; offboard strip is the
# only setup-flow remnant. Use either as the nav-hub view marker.
assert '<div class="offboard-strip">' in post.text
assert 'class="install-block"' not in post.text

View file

@ -24,7 +24,7 @@ wheels = [
[[package]]
name = "agnes-the-ai-analyst"
version = "0.54.1"
version = "0.54.6"
source = { editable = "." }
dependencies = [
{ name = "a2wsgi" },