feat(home): Getting Started + Overview + Usage modes sections (release 0.54.7) (#291)

* feat(home): Getting Started + Overview + Usage modes sections

Three new content cards rendered between the install-hero and the
existing connector tiles on /home. Order: Getting Started → Overview
→ Usage modes → connectors.

- Getting Started — dismissible card with two clickable rows linking
  to /setup (install flow) and /setup-advanced (deeper reference).
  Subsumes the legacy `.advanced-pointer` row that sat above the news
  section. Per-device dismiss via a generic localStorage handler:
  `.home-card-close[data-dismiss-key="..."]` inside a <section> wires
  itself up at page load — drop in any future dismissible card without
  per-card JS.
- Overview — operator-owned HTML body sourced from the new
  `instance.overview` yaml field (env override
  `AGNES_INSTANCE_OVERVIEW`). HTML in, HTML out via the same `| safe`
  filter as news_intro. Empty default hides the section entirely,
  keeping the OSS vendor-neutral; operators paste their product
  framing / privacy posture into instance.yaml. New helper
  `get_instance_overview()` in app/instance_config.py mirrors
  `get_instance_logo_svg()`.
- Usage modes — three OSS-shipped tiles (Terminal / VS Code / Claude
  Desktop · claude.ai) explaining each surface and linking to the
  matching /setup-advanced anchors. Closes the gap for users
  wondering "where do I actually run this".

Supporting changes:
- setup_advanced.html gains a new `#claude-app` section between
  #vscode and #workspace, anchored by the Usage modes Claude Desktop
  tile. Covers the marketplace registration paths and when to prefer
  the terminal. Added to the table of contents.
- Three new tests in test_web_home_page.py pin the Getting Started
  card markup, the Overview-on-when-yaml-set path, and the
  Overview-off-by-default path. All 13 tests in the file pass.

Operator follow-up (separate infra PR — NOT this PR): paste the
Foundry-specific Overview body into instance.yaml's
`instance.overview` field. OSS ships with an empty default.

* fix(home): Overview is operator-owned content — drop dismiss button

Earlier iteration added a close X to the Overview section to match
the Getting Started card's dismiss UX. Wrong call: Overview is
operator-authored reference content (privacy posture, telemetry
policy, project framing) and a per-device localStorage hide means
returning users who want to re-read the policy can't recover it
without clearing storage.

Reverts the close button + the data-dismiss-key on the Overview
section. Test inverted to assert the dismiss key is absent (defends
against a future drive-by adding it back). Getting Started still
dismisses — that's procedural getting-started content users
legitimately stop needing once they've finished setup. Overview is
always reachable; whole section is still opt-in at the operator
level via the empty-yaml default.

* fix(home): Terminal usage-mode tile is informational (no click-through)

The setup hero above /home's Usage modes already walks the user
through the Claude Code CLI install — the Terminal tile click-through
to /setup just round-trips back to content the user already scrolled
past. Switch Terminal to a non-anchor <div> and scope the hover
affordance to a.home-usage-item so VS Code + Claude Desktop tiles
keep their click-through (those legitimately deep-link into
/setup-advanced anchors).

* fix(home): point Usage modes guidance at ~/{workspace}/Projects/ subfolder

The bundled plugin scopes the session-analysis loop and the
central-catalog sync to ~/<workspace>/Projects/, not the workspace
root itself — that convention already appears in the install hero's
Step 4 manual-fallback note ('Don't create ~/<workspace>/Projects/
manually — the bundled plugin offers to set it up after install').
Usage modes' footer guidance now matches: 'create every project
under ~/<workspace>/Projects/'. Also calls out that the
session-analysis loop is scoped to that root so users understand
why working outside the workspace dir is invisible to the platform.
This commit is contained in:
Vojtech 2026-05-13 23:44:11 +04:00 committed by GitHub
parent 14ddaf1e8e
commit 1e87354d7e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 401 additions and 11 deletions

View file

@ -10,6 +10,35 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
## [Unreleased]
## [0.54.7] — 2026-05-13
### Added
- `instance.overview` yaml field (env override
`AGNES_INSTANCE_OVERVIEW`) — operator-authored HTML body rendered in
the new Overview section on `/home`. HTML in, HTML out via the same
`| safe` filter as `news_intro`. Empty default hides the section,
keeping the OSS vendor-neutral.
- `/home` Getting Started card — dismissible, two clickable rows
linking to `/setup` (install) and `/setup-advanced` (deeper
reference). Per-device dismiss via localStorage key
`agnes_home_gs_dismissed`. Generic `.home-card-close[data-dismiss-key]`
+ `<section>` pattern — drop-in for any future dismissible card.
- `/home` Usage modes section — three OSS-shipped tiles (Terminal /
VS Code / Claude Desktop · claude.ai) explaining each surface and
linking to the relevant `/setup-advanced` anchors.
- `setup_advanced.html` `#claude-app` section anchored by the Usage
modes tile — covers the marketplace registration paths (git
smart-HTTP + ZIP fallback) and when to prefer the terminal anyway.
### Changed
- `/home` legacy `.advanced-pointer` row (the "Going deeper —
Advanced setup" link that sat above the news section) removed —
the same link now lives in the new Getting Started card. Supporting
`.advanced-pointer` CSS stays in place as dead style to keep the
diff focused.
## [0.54.6] — 2026-05-13
### Changed

View file

@ -295,6 +295,22 @@ def get_instance_logo_svg() -> str:
return (raw or "").strip()
def get_instance_overview() -> str:
"""Operator-authored Overview body rendered on ``/home``. Markdown is
NOT auto-converted operators paste HTML (matches the existing
``news_intro`` ``| safe`` filter). Empty default = section hidden,
keeping the OSS vendor-neutral when an instance ships without
operator-specific framing.
Resolution: ``AGNES_INSTANCE_OVERVIEW`` env > ``instance.overview``
YAML > ``""``. Mirrors :func:`get_instance_logo_svg`.
"""
raw = os.environ.get("AGNES_INSTANCE_OVERVIEW")
if raw is None:
raw = get_value("instance", "overview", 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,7 +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,
get_instance_logo_svg, get_instance_overview,
)
from app.web.connector_prompts import all_connector_prompts
from src.repositories.sync_state import SyncStateRepository
@ -345,6 +345,7 @@ def _build_context(
INSTANCE_SUBTITLE = get_instance_subtitle()
INSTANCE_COPYRIGHT = ""
LOGO_SVG = get_instance_logo_svg()
INSTANCE_OVERVIEW = get_instance_overview()
TELEGRAM_BOT_USERNAME = os.environ.get("TELEGRAM_BOT_USERNAME", "")
SSH_ALIAS = "data-analyst"
SERVER_HOST = os.environ.get("SERVER_HOST", "")

View file

@ -101,6 +101,155 @@
}
.home-mock .install-hero .lead strong { font-weight: 600; }
/* ── Getting Started + Overview + Usage modes (added in PR #289) ───
Three new content cards rendered between the install-hero and the
connector tiles. Getting Started + Usage modes ship in OSS;
Overview body comes from instance.overview yaml (operator-owned),
hidden when unset. Generic dismiss pattern: any element with
`<button class="home-card-close" data-dismiss-key="...">` gets
per-device localStorage dismissibility for free. */
.home-mock .home-getting-started,
.home-mock .home-overview,
.home-mock .home-usage {
position: relative;
background: white;
border: 1px solid var(--hp-border);
border-radius: 12px;
padding: 22px 24px;
margin-bottom: 18px;
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
}
.home-mock .home-getting-started > header h2,
.home-mock .home-overview > h2,
.home-mock .home-usage > header h2 {
font-size: 18px;
font-weight: 600;
margin: 0 0 6px;
color: var(--hp-text-primary);
}
.home-mock .home-getting-started > header p,
.home-mock .home-usage > header p {
font-size: 13px;
color: var(--hp-text-secondary);
margin: 0 0 14px;
}
.home-mock .home-card-close {
position: absolute;
top: 12px;
right: 12px;
width: 28px;
height: 28px;
border: none;
border-radius: 50%;
background: transparent;
color: var(--hp-text-muted);
font-size: 18px;
line-height: 1;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
}
.home-mock .home-card-close:hover,
.home-mock .home-card-close:focus-visible {
background: var(--hp-border-light);
color: var(--hp-text-primary);
outline: none;
}
.home-mock .home-gs-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 14px;
border: 1px solid var(--hp-border);
border-radius: 10px;
text-decoration: none;
color: inherit;
margin-top: 8px;
transition: border-color 0.15s, background 0.15s;
}
.home-mock .home-gs-item:hover {
border-color: var(--hp-primary);
background: var(--hp-primary-light);
}
.home-mock .home-gs-item .ico {
font-size: 20px;
flex: 0 0 24px;
text-align: center;
}
.home-mock .home-gs-item .text {
flex: 1;
display: flex;
flex-direction: column;
gap: 2px;
}
.home-mock .home-gs-item .text strong {
font-weight: 600;
color: var(--hp-text-primary);
}
.home-mock .home-gs-item .text .desc {
font-size: 13px;
color: var(--hp-text-secondary);
}
.home-mock .home-gs-item .arrow {
color: var(--hp-text-muted);
font-size: 16px;
}
.home-mock .home-overview-body {
font-size: 14px;
line-height: 1.6;
color: var(--hp-text-primary);
}
.home-mock .home-overview-body p { margin: 0 0 10px; }
.home-mock .home-overview-body p:last-child { margin-bottom: 0; }
.home-mock .home-overview-body a { color: var(--hp-primary); }
.home-mock .home-usage-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin-bottom: 12px;
}
.home-mock .home-usage-item {
display: flex;
flex-direction: column;
gap: 4px;
padding: 14px;
border: 1px solid var(--hp-border);
border-radius: 10px;
text-decoration: none;
color: inherit;
transition: border-color 0.15s, background 0.15s;
}
/* Hover affordance only on the anchor variants (VS Code, Claude
Desktop). The Terminal tile renders as a plain <div> — the setup
hero above already covers the terminal install path, so a
click-through from here would round-trip to content the user just
scrolled past. */
.home-mock a.home-usage-item:hover {
border-color: var(--hp-primary);
background: var(--hp-primary-light);
}
.home-mock .home-usage-item .ico { font-size: 20px; }
.home-mock .home-usage-item strong { font-weight: 600; }
.home-mock .home-usage-item span {
font-size: 13px;
color: var(--hp-text-secondary);
line-height: 1.5;
}
.home-mock .home-usage-foot {
font-size: 13px;
color: var(--hp-text-secondary);
margin: 0;
}
@media (max-width: 720px) {
.home-mock .home-usage-grid { grid-template-columns: 1fr; }
}
.home-mock .what-is {
display: grid;
grid-template-columns: repeat(2, 1fr);
@ -1365,6 +1514,88 @@ Set-Location "$HOME\{{ workspace_dir }}"</span>
BEFORE Step 3's install runs ~20 commands. Gated by the same
`home_automode.show` flag at the call site. #}
{# Getting Started card — dismissible per-device via localStorage.
Two clickable rows pointing at the install flow (/setup) and the
deeper reference (/setup-advanced). Subsumes the legacy
`.advanced-pointer` row that used to sit above the news section. #}
<section class="home-getting-started" id="homeGettingStarted">
<button type="button" class="home-card-close"
data-dismiss-key="agnes_home_gs_dismissed"
aria-label="Dismiss Getting Started">&times;</button>
<header>
<h2>Getting Started</h2>
<p>Two quick next steps to get the most out of {{ instance_brand }}.</p>
</header>
<a class="home-gs-item" href="/setup">
<span class="ico" aria-hidden="true">&#x1F680;</span>
<div class="text">
<strong>Setup {{ instance_brand }} in your Claude Code</strong>
<span class="desc">One-time install: copies a setup script to your clipboard, paste into Claude Code, done in ~10 minutes.</span>
</div>
<span class="arrow" aria-hidden="true">&rarr;</span>
</a>
<a class="home-gs-item" href="/setup-advanced">
<span class="ico" aria-hidden="true">&#x1F6E0;&#xFE0F;</span>
<div class="text">
<strong>Go deeper into your AI workspace</strong>
<span class="desc">VS Code layout, recommended plugins, multi-model second opinions, custom skills + rules + hooks, project workflows.</span>
</div>
<span class="arrow" aria-hidden="true">&rarr;</span>
</a>
</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 %}
{# Usage modes — Terminal / VS Code / Claude Desktop · claude.ai.
Generic OSS-shipped copy linking to /setup and to /setup-advanced
anchors. Helps onboarded users find the right surface once they
know which workflow fits them. #}
<section class="home-usage">
<header>
<h2>Where you can use {{ instance_brand }}</h2>
<p>Same workspace, three surfaces. Pick whichever fits your flow — all three share the same plugins, data access, and credentials.</p>
</header>
<div class="home-usage-grid">
{# Terminal tile is informational — the setup hero above already
walks through the Claude Code CLI install, so a click-through
here would round-trip back to content the user just scrolled
past. Rendered as a non-anchor div; hover/cursor styles only
apply to the anchor variants below. #}
<div class="home-usage-item">
<span class="ico" aria-hidden="true">&#x2328;&#xFE0F;</span>
<strong>Terminal</strong>
<span>Default. Run <code>claude</code> from any project under <code>~/{{ workspace_dir }}</code>. Lowest overhead, fastest feedback loop.</span>
</div>
<a class="home-usage-item" href="/setup-advanced#vscode">
<span class="ico" aria-hidden="true">&#x1F4D1;</span>
<strong>VS Code</strong>
<span>Split-terminal layout — conversation on one side, diffs and tool output on the other. See <em>Go deeper</em> for the recommended layout.</span>
</a>
<a class="home-usage-item" href="/setup-advanced#claude-app">
<span class="ico" aria-hidden="true">&#x1F4BB;</span>
<strong>Claude Desktop / claude.ai</strong>
<span>Connect this {{ instance_brand }} instance's plugin marketplace to your Claude Desktop or claude.ai account — same plugins, no terminal required.</span>
</a>
</div>
<p class="home-usage-foot">For the deepest integration, create every project under <code>~/{{ workspace_dir }}/Projects/</code> — existing or new. The bundled plugin keeps each project in sync with the central catalog automatically, and the session-analysis loop is scoped to that root.</p>
</section>
<details class="setup-collapsible" data-section="connectors" open>
<summary>
<span class="ico" aria-hidden="true">&#x1F517;</span>
@ -1492,14 +1723,10 @@ Set-Location "$HOME\{{ workspace_dir }}"</span>
{% endif %}
</div>
<a class="advanced-pointer" href="/setup-advanced">
<span class="ico">&#x1F6E0;&#xFE0F;</span>
<div class="advanced-pointer-text">
<strong>Going deeper — Advanced setup</strong>
VS Code split-terminal layout, recommended Claude Code plugins (with copy-able install commands), multi-model second opinions (Codex + Gemini), custom skills + rules + hooks, project workflows, plan tier guidance.
</div>
<span class="arrow">&rarr;</span>
</a>
{# Legacy `.advanced-pointer` row removed — same link now lives in
the Getting Started card at the top of /home. `.advanced-pointer`
CSS (~line 721) is harmless dead style; left in place to keep
this diff focused. #}
{% if news_intro %}
<section class="home-news">
@ -1778,6 +2005,29 @@ Set-Location "$HOME\{{ workspace_dir }}"</span>
applyMinimize(nowOn);
});
}
// ── Generic dismiss-card handler ─────────────────────────────────
// Any `<button class="home-card-close" data-dismiss-key="...">`
// inside a `<section>` becomes dismissible: clicking sets the
// localStorage key to '1' and hides the section; on next page
// load the section starts hidden if the key is set. Used by the
// Getting Started card; future dismissible cards drop in with
// zero per-card JS.
document.querySelectorAll('.home-card-close[data-dismiss-key]').forEach(function (btn) {
var key = btn.getAttribute('data-dismiss-key');
var section = btn.closest('section');
if (!section || !key) return;
try {
if (localStorage.getItem(key) === '1') {
section.hidden = true;
return;
}
} catch (e) { /* private-mode: render visible, no-op */ }
btn.addEventListener('click', function () {
try { localStorage.setItem(key, '1'); } catch (e) { /* ignore */ }
section.hidden = true;
});
});
})();
</script>
{% endblock %}

View file

@ -291,6 +291,7 @@
<h2>On this page</h2>
<ol>
<li><a href="#vscode">VS Code as your workspace</a></li>
<li><a href="#claude-app">Claude Desktop &amp; claude.ai</a></li>
<li><a href="#workspace">~/{{ workspace_dir }} workspace anatomy</a></li>
<li><a href="#projects">Project workflows</a></li>
<li><a href="#plugins">Recommended plugins</a></li>
@ -323,6 +324,21 @@
</ul>
</section>
<section class="ad-section" id="claude-app">
<h2>1b. Claude Desktop &amp; claude.ai</h2>
<p class="ad-lead">{{ instance_brand }}'s plugin marketplace also works from Claude Desktop and claude.ai — same plugins, same RBAC, no terminal required. Useful when you're already inside Claude for chat and want one-click access to your team's curated tools.</p>
<p>Two channels share the same RBAC-filtered marketplace feed:</p>
<ul>
<li><strong>Git smart-HTTP</strong> — preferred. Register once with <code>/plugin marketplace add</code>; Claude owns the clone/fetch cycle from then on.</li>
<li><strong>ZIP download</strong> — fallback for environments where the git path can't reach the server (private-CA TLS, restrictive proxies). A SessionStart hook unpacks the served bundle into <code>./marketplace/</code> on each session start.</li>
</ul>
<p>The exact registration commands (URL, PAT, hook setup) are baked into the clipboard payload at <a href="/setup">/setup</a> — that page detects your platform and chooses the right channel automatically. The setup script also installs the bundled hooks that keep the marketplace + session telemetry in sync between Claude Desktop and the central catalog.</p>
<p class="ad-lead" style="margin-top: 14px;">When to prefer the terminal anyway: large refactors, anything that touches files outside of <code>~/{{ workspace_dir }}</code>, or workflows that need Claude Code's CLI-only features (auto-mode, YOLO, custom hooks). Claude Desktop and claude.ai are best for short-form chat with read-only / well-scoped tools.</p>
</section>
<section class="ad-section" id="workspace">
<h2>2. <code>~/{{ workspace_dir }}</code> workspace anatomy</h2>
<p class="ad-lead">Your workspace was created by <code>agnes init</code> and the bundled {{ instance_brand }} plugin. Here's what each folder is for.</p>

View file

@ -36,6 +36,14 @@ instance:
# # `name` above still drives browser <title> text and page
# # headings — keep it populated. Env override:
# # AGNES_INSTANCE_LOGO_SVG.
# overview: | # Operator-authored Overview body rendered in the new
# <p>Free-form HTML — paragraphs, links, lists.</p>
# # Overview section on /home (between Getting Started and
# # Usage modes). Use for product framing, privacy posture,
# # what-data-flows summary — operator-specific copy stays
# # out of the OSS this way. HTML in, HTML out (same `| safe`
# # filter as news_intro). Empty/unset = section hidden.
# # Env override: AGNES_INSTANCE_OVERVIEW.
# 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.6"
version = "0.54.7"
description = "Agnes — AI Data Analyst platform for AI analytical systems"
requires-python = ">=3.11,<3.14"
license = "MIT"

View file

@ -329,3 +329,73 @@ def test_home_renders_connector_prompts_from_shared_module(fresh_db):
f"the home tile and setup script will paste different text. "
f"len(home)={len(actual)} len(module)={len(expected)}"
)
# ── Getting Started + Overview + Usage modes (PR #289 home additions) ────
def test_getting_started_card_renders_on_home(fresh_db):
"""The dismissible Getting Started card sits between the install
hero and the connector tiles. Both rows must be present and point
at /setup and /setup-advanced respectively. State-independent:
renders for both onboarded and not-onboarded users (per-device
localStorage dismiss is the only off switch)."""
from src.db import get_system_db, close_system_db
for onboarded in (False, True):
conn = get_system_db()
try:
_, sess = _make_user_and_session(
conn, email=f"gs-{onboarded}@example.com", onboarded=onboarded
)
finally:
conn.close()
close_system_db()
body = _client().get("/home", cookies={"access_token": sess}).text
assert '<section class="home-getting-started"' in body
assert 'data-dismiss-key="agnes_home_gs_dismissed"' in body
assert 'class="home-gs-item" href="/setup"' in body
assert 'class="home-gs-item" href="/setup-advanced"' in body
def test_overview_section_renders_when_yaml_set(fresh_db, monkeypatch):
"""Setting `AGNES_INSTANCE_OVERVIEW` env (mirrors
instance.overview yaml) injects raw HTML into the Overview section
via the same `| safe` filter as news_intro. The marker text must
appear inside the rendered section wrapper. Overview deliberately
has NO dismiss button it's operator-owned reference content
(privacy posture, telemetry policy, product framing), and a
per-device hide would leave returning users unable to re-read
it without clearing localStorage."""
monkeypatch.setenv("AGNES_INSTANCE_OVERVIEW", "<p>OVERVIEW_TEST_MARKER</p>")
from src.db import get_system_db, close_system_db
conn = get_system_db()
try:
_, sess = _make_user_and_session(conn)
finally:
conn.close()
close_system_db()
body = _client().get("/home", cookies={"access_token": sess}).text
assert '<section class="home-overview">' 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):
"""Default empty `instance.overview` (no env override) hides the
section entirely so the OSS ships without a stray empty
Overview placeholder."""
monkeypatch.delenv("AGNES_INSTANCE_OVERVIEW", raising=False)
from src.db import get_system_db, close_system_db
conn = get_system_db()
try:
_, sess = _make_user_and_session(conn)
finally:
conn.close()
close_system_db()
body = _client().get("/home", cookies={"access_token": sess}).text
assert '<section class="home-overview">' not in body

View file

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