release: 0.54.14 — changelog repair + post-#305 dead-code sweep (#309)

Cuts 0.54.14. Repairs the [Unreleased] changelog state left by three PRs that merged since v0.54.13 without proper changelog hygiene, sweeps dead code orphaned by #305 and earlier PRs, and bumps the version.

- CHANGELOG: #307 bullets moved out of the released [0.54.10] section into [Unreleased]; backfilled missing entries for #305 (Removed) and #308 (Changed); [Unreleased] -> [0.54.14] release cut.
- pyproject.toml: 0.54.13 -> 0.54.14.
- app/web/router.py: removed orphaned gws_oauth / instance_admin_email / connector_prompts keys from the shared _build_context ctx dict.
- app/web/templates/home_not_onboarded.html: swept dead connector-tile, automode, and setup-collapsible/minimize CSS + the orphaned JS click-wiring.
This commit is contained in:
ZdenekSrotyr 2026-05-14 20:41:04 +02:00 committed by GitHub
parent cb13f80241
commit e290baa31b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 98 additions and 538 deletions

View file

@ -10,6 +10,98 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
## [Unreleased] ## [Unreleased]
## [0.54.14] — 2026-05-14
### Changed
- **Marketplace submission surfaces — clearer CTA + fuller guides
(#308).** The curated-tab action-row CTA now reads "Submit a skill
or plugin" (was "Submit a plugin") — skills are first-class on the
curated shelf — with the same wording mirrored in the empty-state
JS and the route titles so the surfaces can't drift. The curated
guide (`/marketplace/guide/curated`) grows from a 4-line stub into
a 3-step walkthrough of the Named Curator handoff plus a
`.guide-fastpath` callout pointing lighter submissions at the Flea
Market; the flea guide (`/marketplace/guide/flea`) grows from a
3-line stub into a 4-step walkthrough of the `/store/new`
self-serve flow and its automated guardrails (manifest,
content-quality, and prompt-injection scans).
### Fixed
- **`agnes refresh-marketplace` now enables stack plugins in workspace
settings (#307).** The reconcile step previously stopped at `claude plugin
install --scope project`, which only writes the global plugin registry
(`~/.claude/plugins/installed_plugins.json`). Without a corresponding
entry in the workspace `.claude/settings.json` `enabledPlugins` map,
Claude Code treats every installed stack plugin as disabled — `/plugins`
hides them from the active section and their slash commands, skills,
and agents are unreachable. Refresh now writes
`"<plugin>@agnes": true` to the workspace settings file after install
and update, treating the user's marketplace stack as the source of
truth and re-enabling any plugin that a prior local `claude plugin
disable` had turned off.
- **Runtime CLI commands now work on Initial Workspace Template
(override) workspaces (#307).** The `.claude/init-complete` sentinel
carrying `override: true` previously short-circuited **every**
Agnes writer to `.claude/`, which trapped admin-templated workspaces
at a stale snapshot: `agnes refresh-marketplace` couldn't write the
`enabledPlugins` map (the fix above stayed inert), and
`agnes self-upgrade`'s `maybe_refresh_claude_hooks` couldn't migrate
workspaces to new Agnes hook layouts. The sentinel was meant to gate
**init-time** skip only — let admins ship the *initial* `.claude/`
contents — not to lock the workspace permanently. The override check
moves from inside the writers
(`cli/lib/hooks.py::install_claude_hooks`,
`cli/lib/hooks.py::maybe_refresh_claude_hooks`,
`cli/lib/commands.py::install_claude_commands`,
`cli/commands/refresh_marketplace.py::_enable_plugins_in_workspace_settings`)
to the init-time call site that always was the right place
(`cli/commands/init.py::init`, `if not override_active:`). Init-time
behavior unchanged — `agnes init` on an override workspace still
defers the workspace skeleton to admin's template. Admin custom hooks
survive runtime refresh: Agnes only rewrites entries matching
`_OUR_COMMAND_MARKERS` (`agnes self-upgrade` / `agnes pull` / ...
substring set in `cli/lib/hooks.py`); foreign commands fall through
unchanged, same contract as in default workspaces. Existing override
workspaces auto-converge on the next `agnes self-upgrade` (which
fires from every SessionStart hook); no manual operator action
needed. Retracts the earlier *"full responsibility transfer; future
Agnes hook fixes will NOT auto-propagate"* contract documented in
the `[0.54.10]` `### Internal — risk-accepted by design` bullets —
that scope was wider than the feature's actual intent.
### Removed
- **`/home` connectors block dropped — the onboarding flow covers it
(#305).** The dedicated `<details data-section="connectors">` section
on `/home` (three tiles — Asana / Google Workspace / Atlassian — each
with a "Copy prompt" button) duplicated content the install-hero's
Step 4 clipboard payload already inlines via
`app/web/setup_instructions.py::_connectors_block`: users walking the
setup script visit every connector inline. The install-hero lead
paragraph now names the connector families so the benefit stays
visible before kick-off. The per-instance "Email admin" mailto CTA —
previously gated inside the GWS tile when an operator contact email
was set and GWS OAuth was unconfigured — was dropped along with the
block; the GWS connector setup prompt still tells the user to ask an
admin, but without the pre-filled per-instance contact address.
### Internal
- Post-#305 cleanup. Removed the now-orphaned `gws_oauth`,
`instance_admin_email`, and `connector_prompts` keys from the shared
`_build_context` ctx dict in `app/web/router.py` — no template
referenced them once the connectors block was dropped, and
`connector_prompts` was calling `all_connector_prompts()` on every
page render app-wide. Swept the dead `.connector-tile*`,
`.connector-copy`, `.connector-preview`, `.copy-next-hint`,
`.time-badge`, `.gating-note`, `.email-admin`, `.card-mini-cmd`, and
`.connector-head` CSS rules plus the orphaned `.connector-copy`
click-wiring JS from `home_not_onboarded.html`. Also removed the
dead `.automode-*`, `.setup-collapsible`, and `.setup-minimize`
CSS blocks and the `setupMinimizeToggle` / `data-setup-minimized`
JS handler from the same template — the `<details data-section>`
sections and the "Minimize setup view" toggle they styled were
removed by earlier PRs (#243 onward), leaving the whole
minimize-mode machinery unreachable.
## [0.54.13] — 2026-05-14 ## [0.54.13] — 2026-05-14
### Security ### Security
@ -189,47 +281,6 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
fine because `.mp-type-row` contributes its own 24px. fine because `.mp-type-row` contributes its own 24px.
### Fixed ### Fixed
- **`agnes refresh-marketplace` now enables stack plugins in workspace
settings.** The reconcile step previously stopped at `claude plugin
install --scope project`, which only writes the global plugin registry
(`~/.claude/plugins/installed_plugins.json`). Without a corresponding
entry in the workspace `.claude/settings.json` `enabledPlugins` map,
Claude Code treats every installed stack plugin as disabled — `/plugins`
hides them from the active section and their slash commands, skills,
and agents are unreachable. Refresh now writes
`"<plugin>@agnes": true` to the workspace settings file after install
and update, treating the user's marketplace stack as the source of
truth and re-enabling any plugin that a prior local `claude plugin
disable` had turned off.
- **Runtime CLI commands now work on Initial Workspace Template
(override) workspaces.** The `.claude/init-complete` sentinel
carrying `override: true` previously short-circuited **every**
Agnes writer to `.claude/`, which trapped admin-templated workspaces
at a stale snapshot: `agnes refresh-marketplace` couldn't write the
`enabledPlugins` map (the fix above stayed inert), and
`agnes self-upgrade`'s `maybe_refresh_claude_hooks` couldn't migrate
workspaces to new Agnes hook layouts. The sentinel was meant to gate
**init-time** skip only — let admins ship the *initial* `.claude/`
contents — not to lock the workspace permanently. The override check
moves from inside the writers
(`cli/lib/hooks.py::install_claude_hooks`,
`cli/lib/hooks.py::maybe_refresh_claude_hooks`,
`cli/lib/commands.py::install_claude_commands`,
`cli/commands/refresh_marketplace.py::_enable_plugins_in_workspace_settings`)
to the init-time call site that always was the right place
(`cli/commands/init.py::init`, `if not override_active:`). Init-time
behavior unchanged — `agnes init` on an override workspace still
defers the workspace skeleton to admin's template. Admin custom hooks
survive runtime refresh: Agnes only rewrites entries matching
`_OUR_COMMAND_MARKERS` (`agnes self-upgrade` / `agnes pull` / ...
substring set in `cli/lib/hooks.py`); foreign commands fall through
unchanged, same contract as in default workspaces. Existing override
workspaces auto-converge on the next `agnes self-upgrade` (which
fires from every SessionStart hook); no manual operator action
needed. Retracts the earlier *"full responsibility transfer; future
Agnes hook fixes will NOT auto-propagate"* contract documented in
the `### Internal — risk-accepted by design` bullets immediately
below — that scope was wider than the feature's actual intent.
- **Store guardrails — post-#290 follow-up.** Admin Rescan still writes `status='blocked_inline'` (the only post-v30 producer of that status). Re-add `blocked_inline` to the admin queue's "Needs review" filter chip and to `TERMINAL_BLOCKED_STATUSES` in the bundle-purge job, so a rescan-produced row surfaces in the default operator view and its bundle gets swept by the TTL purge instead of lingering on disk indefinitely. Documents the rescan-only asymmetry inline (chip + purge tuple + new code comments). - **Store guardrails — post-#290 follow-up.** Admin Rescan still writes `status='blocked_inline'` (the only post-v30 producer of that status). Re-add `blocked_inline` to the admin queue's "Needs review" filter chip and to `TERMINAL_BLOCKED_STATUSES` in the bundle-purge job, so a rescan-produced row surfaces in the default operator view and its bundle gets swept by the TTL purge instead of lingering on disk indefinitely. Documents the rescan-only asymmetry inline (chip + purge tuple + new code comments).
- Stale doc strings referring to the pre-#290 `blocked_inline` quota counter on `app/api/store.py` spam-quota comment, `app/instance_config.py::get_guardrails_blocked_quota_per_day` docstring, and the operator-facing hint in `/admin/server-config` (`blocked_quota_per_day`). All three now correctly describe the narrowed `blocked_llm + review_error` counter that #290 actually shipped. - Stale doc strings referring to the pre-#290 `blocked_inline` quota counter on `app/api/store.py` spam-quota comment, `app/instance_config.py::get_guardrails_blocked_quota_per_day` docstring, and the operator-facing hint in `/admin/server-config` (`blocked_quota_per_day`). All three now correctly describe the narrowed `blocked_llm + review_error` counter that #290 actually shipped.
@ -270,7 +321,7 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
### Internal — risk-accepted by design (see Initial Workspace Template feature) ### Internal — risk-accepted by design (see Initial Workspace Template feature)
- `agnes init --force` on override workspaces does NOT back up `CLAUDE.md` (no `CLAUDE.md.bak.<timestamp>` file). Source of truth is the admin's Git repo; recovery is `git log` / `git checkout`. Not a regression of #164. - `agnes init --force` on override workspaces does NOT back up `CLAUDE.md` (no `CLAUDE.md.bak.<timestamp>` file). Source of truth is the admin's Git repo; recovery is `git log` / `git checkout`. Not a regression of #164.
- `.claude/CLAUDE.local.md` IS overwritten by override extraction when the admin's repo includes it. The default-mode "never overwrite CLAUDE.local.md" promise is a default-mode promise; override mode hands full file-level control to admin. Documented. - `.claude/CLAUDE.local.md` IS overwritten by override extraction when the admin's repo includes it. The default-mode "never overwrite CLAUDE.local.md" promise is a default-mode promise; override mode hands full file-level control to admin. Documented.
- `cli/lib/override.py::is_override_workspace` gates the **init-time** skip block in `cli/commands/init.py` (the `if not override_active:` branch). Runtime CLI commands (`agnes refresh-marketplace`, `agnes self-upgrade`'s `maybe_refresh_claude_hooks`) do NOT consult the sentinel and keep the workspace in sync — see the `### Fixed` entry "Runtime CLI commands now work on Initial Workspace Template workspaces" above for the full contract. - `cli/lib/override.py::is_override_workspace` gates the **init-time** skip block in `cli/commands/init.py` (the `if not override_active:` branch). Runtime CLI commands (`agnes refresh-marketplace`, `agnes self-upgrade`'s `maybe_refresh_claude_hooks`) do NOT consult the sentinel and keep the workspace in sync — see the `### Fixed` entry "Runtime CLI commands now work on Initial Workspace Template workspaces" in the `[0.54.14]` release notes for the full contract.
- `app/api/marketplaces.py::_persist_token` removed; both marketplaces and the new initial-workspace endpoint now route through the shared `app/secrets.py::persist_overlay_token` helper, which wraps the `.env_overlay` read-modify-write in a process-wide `threading.Lock`. Closes a pre-existing race where two concurrent `/admin/marketplaces` Save clicks could clobber each other's PATs on the overlay file. - `app/api/marketplaces.py::_persist_token` removed; both marketplaces and the new initial-workspace endpoint now route through the shared `app/secrets.py::persist_overlay_token` helper, which wraps the `.env_overlay` read-modify-write in a process-wide `threading.Lock`. Closes a pre-existing race where two concurrent `/admin/marketplaces` Save clicks could clobber each other's PATs on the overlay file.
## [0.54.8] — 2026-05-13 ## [0.54.8] — 2026-05-13

View file

@ -433,9 +433,9 @@ def _build_context(
server_host = request.url.netloc server_host = request.url.netloc
ca_pem = _read_agnes_ca_pem() ca_pem = _read_agnes_ca_pem()
# Connector prompts wired through so step 9 inlines the same text # Connector prompts wired through so the setup script's connector
# the /home tiles render. all_connector_prompts() reads operator # step inlines them. all_connector_prompts() reads operator GWS
# GWS OAuth config so the GCP-frictionless branch fires when the # OAuth config so the GCP-frictionless branch fires when the
# admin has provisioned a shared client_id+secret. # admin has provisioned a shared client_id+secret.
_connector_prompts = all_connector_prompts( _connector_prompts = all_connector_prompts(
gws_oauth=get_gws_oauth_credentials(), gws_oauth=get_gws_oauth_credentials(),
@ -471,14 +471,6 @@ def _build_context(
# single env flip routes the primary nav target between /home # single env flip routes the primary nav target between /home
# (state-aware landing) and /dashboard (legacy table inventory). # (state-aware landing) and /dashboard (legacy table inventory).
"home_route": _resolved_home_route(), "home_route": _resolved_home_route(),
# Pre-configured Google Workspace CLI OAuth client for the
# /home connector prompt. {} when unset → template falls back
# to manual `gws auth setup`. See app.instance_config docstring.
"gws_oauth": get_gws_oauth_credentials(),
# Operator-facing contact email used by the /home GWS connector
# tile's "Email admin" mailto button. Empty string hides the
# button — template guards with `{% if instance_admin_email %}`.
"instance_admin_email": get_instance_admin_email(),
# Branding: `instance_name` is the deploying org's display name # Branding: `instance_name` is the deploying org's display name
# (page titles); `instance_brand` is the product name used in body # (page titles); `instance_brand` is the product name used in body
# copy and CTAs ("Setup {brand}", "{brand} runs SELECT…"); `workspace_dir` # copy and CTAs ("Setup {brand}", "{brand} runs SELECT…"); `workspace_dir`
@ -489,18 +481,6 @@ def _build_context(
"instance_name": get_instance_name(), "instance_name": get_instance_name(),
"instance_brand": get_instance_brand(), "instance_brand": get_instance_brand(),
"workspace_dir": get_workspace_dir_name(), "workspace_dir": get_workspace_dir_name(),
# Resolved connector setup prompts — single source of truth for
# both the /home "Copy prompt" tiles and the main setup script
# (app/web/setup_instructions.py inlines them in step 9). The
# gws prompt branches on `gws_oauth.configured` so both surfaces
# render the operator-provisioned shortcut when credentials are
# set, and the manual GCP walkthrough when they're not.
"connector_prompts": all_connector_prompts(
gws_oauth=get_gws_oauth_credentials(),
instance_admin_email=get_instance_admin_email(),
atlassian_base_url=get_atlassian_base_url(),
instance_brand=get_instance_brand(),
),
# Whether /home renders the "Step 3 — turn on auto-accept mode" # Whether /home renders the "Step 3 — turn on auto-accept mode"
# install-block. Operator can hide it via AGNES_HOME_SHOW_AUTOMODE=0 # install-block. Operator can hide it via AGNES_HOME_SHOW_AUTOMODE=0
# for cautious rollouts; same content stays on /setup-advanced. # for cautious rollouts; same content stays on /setup-advanced.

View file

@ -352,47 +352,6 @@
margin-bottom: 22px; margin-bottom: 22px;
} }
.home-mock .connector-tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 16px;
margin-bottom: 26px;
}
.home-mock .connector-tile {
background: white;
border: 1px solid var(--hp-border);
border-radius: 12px;
padding: 20px 22px;
display: flex;
flex-direction: column;
}
.home-mock .connector-tile .ico {
font-size: 24px;
margin-bottom: 10px;
line-height: 1;
}
.home-mock .connector-tile .ttl {
font-size: 15px;
font-weight: 600;
color: var(--hp-text-primary);
margin-bottom: 6px;
}
.home-mock .connector-tile .desc {
font-size: 13px;
color: var(--hp-text-secondary);
line-height: 1.55;
margin-bottom: 14px;
flex: 1;
}
.home-mock .connector-tile .connector-actions {
display: flex;
flex-direction: column;
gap: 8px;
}
.home-mock .connector-tile .connector-copy {
align-self: flex-start;
}
.home-mock .install-block { .home-mock .install-block {
background: rgba(15, 23, 42, 0.55); background: rgba(15, 23, 42, 0.55);
border: 1px solid rgba(255, 255, 255, 0.10); border: 1px solid rgba(255, 255, 255, 0.10);
@ -492,118 +451,6 @@
text-decoration-color: #ffffff; text-decoration-color: #ffffff;
} }
.home-mock .automode-card {
margin: 22px 0;
background: white;
border: 1px solid var(--hp-border);
border-radius: 12px;
padding: 18px 22px;
}
.home-mock .automode-head {
display: flex;
gap: 14px;
align-items: flex-start;
margin-bottom: 14px;
}
.home-mock .automode-head .ico {
font-size: 22px;
flex-shrink: 0;
line-height: 1;
}
.home-mock .automode-head h3 {
font-size: 14px;
font-weight: 600;
margin-bottom: 4px;
color: var(--hp-text-primary);
}
.home-mock .automode-head p {
font-size: 13px;
color: var(--hp-text-secondary);
line-height: 1.55;
margin: 0;
}
.home-mock .automode-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
.home-mock .automode-step {
display: flex;
gap: 10px;
align-items: flex-start;
padding: 12px;
background: var(--hp-border-light);
border-radius: 8px;
}
.home-mock .automode-step .num {
width: 22px; height: 22px;
border-radius: 50%;
background: var(--hp-primary-light);
color: var(--hp-primary);
flex-shrink: 0;
display: flex; align-items: center; justify-content: center;
font-weight: 700; font-size: 11px;
}
.home-mock .automode-step .step-text {
flex: 1;
font-size: 12.5px;
line-height: 1.5;
color: var(--hp-text-secondary);
}
.home-mock .automode-step .step-text strong {
display: block;
color: var(--hp-text-primary);
margin-bottom: 4px;
font-size: 13px;
}
.home-mock .automode-step kbd {
background: white;
border: 1px solid var(--hp-border);
border-bottom-width: 2px;
border-radius: 4px;
padding: 1px 6px;
font-size: 11px;
font-family: var(--hp-font-mono);
color: var(--hp-text-primary);
}
.home-mock .automode-step code {
font-family: var(--hp-font-mono);
font-size: 11.5px;
background: white;
padding: 1px 5px;
border-radius: 3px;
color: var(--hp-text-primary);
border: 1px solid var(--hp-border);
}
.home-mock .automode-code {
background: #0F172A;
color: #FBBF24;
border-radius: 6px;
padding: 8px 10px;
margin-top: 6px;
font-family: var(--hp-font-mono);
font-size: 11.5px;
line-height: 1.5;
overflow-x: auto;
white-space: pre;
}
.home-mock .automode-foot {
margin-top: 12px;
padding-top: 10px;
border-top: 1px solid var(--hp-border-light);
font-size: 12px;
color: var(--hp-text-secondary);
line-height: 1.5;
}
.home-mock .automode-foot a { color: var(--hp-primary); text-decoration: underline; }
.home-mock .automode-foot code {
font-family: var(--hp-font-mono);
font-size: 11px;
background: var(--hp-border-light);
padding: 1px 4px;
border-radius: 3px;
}
.home-mock .section-label { .home-mock .section-label {
font-size: 11px; font-size: 11px;
font-weight: 600; font-weight: 600;
@ -648,8 +495,7 @@
} }
.home-mock .card-list .step-text { flex: 1; line-height: 1.55; } .home-mock .card-list .step-text { flex: 1; line-height: 1.55; }
.home-mock .card-list .step-text strong { display: block; margin-bottom: 2px; } .home-mock .card-list .step-text strong { display: block; margin-bottom: 2px; }
.home-mock .card-list .step-text code, .home-mock .card-list .step-text code {
.home-mock .card-mini-cmd code {
font-family: var(--hp-font-mono); font-family: var(--hp-font-mono);
font-size: 11.5px; font-size: 11.5px;
background: var(--hp-border-light); background: var(--hp-border-light);
@ -658,72 +504,6 @@
color: var(--hp-text-primary); color: var(--hp-text-primary);
} }
.home-mock .card-mini-cmd {
margin-top: 6px;
padding: 8px 10px;
background: #0F172A;
color: #FBBF24;
border-radius: 6px;
font-family: var(--hp-font-mono);
font-size: 11.5px;
line-height: 1.5;
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 8px;
}
.home-mock .card-mini-cmd code {
background: transparent;
color: inherit;
padding: 0;
flex: 1;
white-space: pre-wrap;
word-break: break-word;
}
.home-mock .connector-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.home-mock .connector-head strong {
display: inline;
margin-bottom: 0;
}
.home-mock .connector-copy {
background: var(--hp-primary);
color: white;
border: none;
border-radius: 6px;
padding: 5px 12px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
font-family: inherit;
flex-shrink: 0;
}
.home-mock .connector-copy:hover { background: var(--hp-primary-dark); }
.home-mock .connector-copy.copied { background: #047857; }
.home-mock details.connector-preview {
margin-top: 6px;
}
.home-mock details.connector-preview > summary {
cursor: pointer;
list-style: none;
font-size: 11.5px;
color: var(--hp-text-secondary);
padding: 2px 0;
user-select: none;
}
.home-mock details.connector-preview > summary::-webkit-details-marker { display: none; }
.home-mock details.connector-preview > summary::before {
content: "▸ ";
margin-right: 2px;
}
.home-mock details.connector-preview[open] > summary::before { content: "▾ "; }
.home-mock details.connector-preview > summary:hover { color: var(--hp-text-primary); }
.home-mock details.connector-preview .card-mini-cmd { margin-top: 6px; }
.home-mock .explore-list { list-style: none; padding: 0; margin: 0; } .home-mock .explore-list { list-style: none; padding: 0; margin: 0; }
.home-mock .explore-list li + li { margin-top: 8px; } .home-mock .explore-list li + li { margin-top: 8px; }
.home-mock .explore-item { .home-mock .explore-item {
@ -1012,106 +792,6 @@
.home-mock .home-news-body p { margin: 0 0 8px; } .home-mock .home-news-body p { margin: 0 0 8px; }
.home-mock .home-news-body p:last-child { margin-bottom: 0; } .home-mock .home-news-body p:last-child { margin-bottom: 0; }
/* Setup-collapsible: each post-install section (Step 3 auto-mode,
Connect-your-tools) is wrapped in a <details open> so it keyboard-
collapses without JS. Default state on /home: summary hidden, body
flat — visually identical to the pre-collapse layout.
When the user clicks "Minimize setup view" in the hero, JS sets
`data-setup-minimized="1"` on `.home-mock` and removes the `open`
attribute on each <details>. The summary then appears as a slim
gray bar; the body collapses; clicking the bar re-opens that one
section. localStorage persists the minimize state per device. */
.home-mock .setup-collapsible { margin-top: 22px; }
.home-mock .setup-collapsible > summary {
/* Default: summary is structural-only. The "flat" rendering is
handled by the inner blocks (.automode-card already provides its
own padding/border/background; the connect-your-tools section
starts with a .section-label-flat). */
display: none;
}
.home-mock .setup-collapsible > summary::-webkit-details-marker { display: none; }
/* Minimize ON: summary becomes a slim clickable bar; the inner body
inherits the existing block styling but tucks under the summary
when the <details> is closed. */
.home-mock[data-setup-minimized="1"] .setup-collapsible {
border: 1px solid var(--hp-border);
border-radius: 12px;
background: white;
overflow: hidden;
}
.home-mock[data-setup-minimized="1"] .setup-collapsible > summary {
list-style: none;
cursor: pointer;
padding: 14px 22px;
display: flex;
align-items: center;
gap: 12px;
font-size: 14px;
font-weight: 600;
color: var(--hp-text-primary);
background: #F9FAFB;
border-bottom: 1px solid transparent;
transition: background 120ms ease;
user-select: none;
}
.home-mock[data-setup-minimized="1"] .setup-collapsible > summary:hover {
background: #F3F4F6;
}
.home-mock[data-setup-minimized="1"] .setup-collapsible > summary .ico {
font-size: 18px;
line-height: 1;
}
.home-mock[data-setup-minimized="1"] .setup-collapsible > summary .ttl { flex: 1; }
.home-mock[data-setup-minimized="1"] .setup-collapsible > summary .ttl small {
color: var(--hp-text-secondary);
font-weight: 400;
margin-left: 6px;
}
.home-mock[data-setup-minimized="1"] .setup-collapsible > summary .chev {
font-size: 18px;
color: var(--hp-text-secondary);
transition: transform 160ms ease;
}
.home-mock[data-setup-minimized="1"] .setup-collapsible[open] > summary {
border-bottom-color: var(--hp-border);
background: #F3F4F6;
}
.home-mock[data-setup-minimized="1"] .setup-collapsible[open] > summary .chev {
transform: rotate(90deg);
}
/* Tighten the body padding so collapsed sections don't gain margin
when inside the white wrapper card. */
.home-mock[data-setup-minimized="1"] .setup-collapsible[open] > .automode-card,
.home-mock[data-setup-minimized="1"] .setup-collapsible[open] > .connector-tiles {
margin: 18px 22px 22px;
}
.home-mock[data-setup-minimized="1"] .setup-collapsible[open] > .section-label-flat {
padding: 14px 22px 0;
}
/* When a section's <details> is closed in minimize mode, the body
already auto-hides via the browser's <details> behaviour — no extra
rule needed. */
/* Minimize toggle in the blue hero, only present when onboarded. */
.home-mock .setup-minimize {
margin-top: 14px;
font-size: 13px;
color: rgba(255, 255, 255, 0.85);
}
.home-mock .setup-minimize button {
background: rgba(255, 255, 255, 0.10);
color: white;
border: 1px solid rgba(255, 255, 255, 0.25);
padding: 6px 14px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
cursor: pointer;
}
.home-mock .setup-minimize button:hover { background: rgba(255, 255, 255, 0.18); }
/* Callout, video-embed, news-hero, news-grid-*, news-cta — shared news /* Callout, video-embed, news-hero, news-grid-*, news-cta — shared news
content vocabulary now lives in app/web/static/style-custom.css under content vocabulary now lives in app/web/static/style-custom.css under
"News content vocabulary (shared)". Editing those rules in one place "News content vocabulary (shared)". Editing those rules in one place
@ -1172,84 +852,6 @@
color: white; color: white;
} }
/* P0-3 — connector card time badge ("~5 min · self-serve") and the
post-copy "now paste into Step 2" hint with arrow back. */
.home-mock .connector-tile .ttl-row {
display: flex; align-items: center; justify-content: space-between; gap: 8px;
margin-bottom: 6px;
}
.home-mock .time-badge {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 11px;
font-weight: 600;
padding: 2px 8px;
border-radius: 999px;
background: var(--hp-primary-light);
color: var(--hp-primary-dark);
flex-shrink: 0;
}
.home-mock .time-badge.is-warn {
background: #FEF3C7;
color: #92400E;
}
.home-mock .copy-next-hint {
font-size: 12px;
color: var(--hp-primary-dark);
background: var(--hp-primary-light);
padding: 6px 10px;
border-radius: 6px;
margin-top: 6px;
display: none;
align-items: center;
gap: 6px;
line-height: 1.45;
}
.home-mock .copy-next-hint.is-visible { display: inline-flex; }
.home-mock .copy-next-hint code {
background: rgba(0, 0, 0, 0.06);
padding: 1px 5px;
border-radius: 3px;
font-family: var(--hp-font-mono);
font-size: 11px;
}
/* P1-8 — GWS gating note + Email-admin button. Yellow background
matches the same friction-level used by other "admin help likely"
surfaces (advanced setup warnings). */
.home-mock .gating-note {
font-size: 12px;
color: #92400E;
background: #FEF3C7;
border: 1px solid #FCD34D;
border-radius: 6px;
padding: 8px 10px;
margin-bottom: 10px;
line-height: 1.5;
}
.home-mock .email-admin {
display: inline-flex;
align-items: center;
gap: 6px;
background: white;
border: 1px solid var(--hp-border);
color: var(--hp-text-primary);
padding: 5px 12px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
text-decoration: none;
cursor: pointer;
align-self: flex-start;
margin-top: 6px;
}
.home-mock .email-admin:hover {
text-decoration: none;
border-color: var(--hp-primary);
color: var(--hp-primary-dark);
}
/* P1-6 — Auto-detect badge replaces the "Mark me as onboarded" /* P1-6 — Auto-detect badge replaces the "Mark me as onboarded"
button as the primary affordance. Pulse dot signals it's actively button as the primary affordance. Pulse dot signals it's actively
listening; the manual self-mark button stays as a fallback. */ listening; the manual self-mark button stays as a fallback. */
@ -1788,37 +1390,6 @@ Set-Location "$HOME\{{ workspace_dir }}"</span>
} }
document.querySelectorAll('.copy-btn[data-copy-target]').forEach(wireCopy); document.querySelectorAll('.copy-btn[data-copy-target]').forEach(wireCopy);
// P0-3 + P2-12 — Connector copy buttons. Three things on click:
// 1) copy the prompt (same textContent trick so closed <details>
// still copies);
// 2) flip the button to "✓ Copied" + disabled for 8 s so the user
// stops mashing it while figuring out where to paste;
// 3) reveal a paste hint pointing the user at Claude Code.
document.querySelectorAll('.connector-copy[data-copy-target]').forEach(function (btn) {
btn.addEventListener('click', function () {
var src = document.getElementById(btn.getAttribute('data-copy-target'));
if (!src) return;
var raw = src.textContent || '';
var connector = btn.getAttribute('data-connector') || '';
var hint = connector
? document.querySelector('.copy-next-hint[data-hint-for="' + connector + '"]')
: null;
navigator.clipboard.writeText(raw).then(function () {
var orig = btn.textContent;
btn.classList.add('copied');
btn.textContent = '✓ Copied';
btn.disabled = true;
if (hint) hint.classList.add('is-visible');
setTimeout(function () {
btn.classList.remove('copied');
btn.textContent = orig;
btn.disabled = false;
if (hint) hint.classList.remove('is-visible');
}, 8000);
}).catch(function () { btn.textContent = 'Copy failed'; });
});
});
// OS tab switching for Step 1. Flips both the command panel AND the // OS tab switching for Step 1. Flips both the command panel AND the
// P0-1 terminal-howto body so the howto matches the active OS. // P0-1 terminal-howto body so the howto matches the active OS.
document.querySelectorAll('.os-tab[data-os-tab]').forEach(function (tab) { document.querySelectorAll('.os-tab[data-os-tab]').forEach(function (tab) {
@ -1948,48 +1519,6 @@ Set-Location "$HOME\{{ workspace_dir }}"</span>
}); });
} }
// ── Minimize-setup toggle ────────────────────────────────────────
// Default OFF: sections render flat (no <summary> visible).
// ON: data-setup-minimized="1" on .home-mock activates the slim
// gray summary bars; <details> open/close handles per-section
// expansion. Per-device via localStorage; survives reloads but
// resets on a new machine — which is correct (the user might
// want the full reference there).
var KEY = 'agnes_home_setup_minimized';
var mockEl = document.querySelector('.home-mock');
var minToggle = document.getElementById('setupMinimizeToggle');
var collapsibles = document.querySelectorAll('.setup-collapsible');
function applyMinimize(on) {
if (!mockEl) return;
if (on) {
mockEl.setAttribute('data-setup-minimized', '1');
collapsibles.forEach(function (d) { d.removeAttribute('open'); });
if (minToggle) {
minToggle.textContent = 'Show full setup view';
minToggle.setAttribute('aria-pressed', 'true');
}
} else {
mockEl.removeAttribute('data-setup-minimized');
collapsibles.forEach(function (d) { d.setAttribute('open', ''); });
if (minToggle) {
minToggle.textContent = 'Minimize setup view';
minToggle.setAttribute('aria-pressed', 'false');
}
}
}
// Initial state — only when the toggle exists (= onboarded view).
if (minToggle) {
try { applyMinimize(localStorage.getItem(KEY) === '1'); }
catch (e) { /* ignore localStorage failures (private mode) */ }
minToggle.addEventListener('click', function () {
var nowOn = mockEl.getAttribute('data-setup-minimized') !== '1';
try { localStorage.setItem(KEY, nowOn ? '1' : '0'); } catch (e) {}
applyMinimize(nowOn);
});
}
// ── Generic dismiss-card handler ───────────────────────────────── // ── Generic dismiss-card handler ─────────────────────────────────
// Any `<button class="home-card-close" data-dismiss-key="...">` // Any `<button class="home-card-close" data-dismiss-key="...">`
// inside a `<section>` becomes dismissible: clicking sets the // inside a `<section>` becomes dismissible: clicking sets the

View file

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