Skip to content

V2: show the configured web search and AI notices - #1383

Merged
Paul Lizer (paullizer) merged 1 commit into
paullizer-react-v2-uifrom
paullizer-v2-chat-disclaimer-banners
Sep 2, 2026
Merged

V2: show the configured web search and AI notices#1383
Paul Lizer (paullizer) merged 1 commit into
paullizer-react-v2-uifrom
paullizer-v2-chat-disclaimer-banners

Conversation

@paullizer

Copy link
Copy Markdown
Collaborator

Problem

The classic chat page renders two administrator-configured notices around the composer. V2 rendered neither, so anyone who switched interfaces silently stopped seeing them — including the notice warning that their message is about to leave the tenant.

Instead, V2 ended its composer with a hardcoded line of its own:

AI responses can be inaccurate. Verify important information.

That is not the same thing. It is not the administrator's wording, it cannot be turned off, it cannot be dismissed, and it appeared even for organisations that had deliberately configured different text.

Legacy (V1) V2 before V2 after
Web search notice
AI notice
AI notice disabled Nothing Hardcoded line Nothing

Why it needed a server-side change

Both notices reach the classic page through Jinja template context, which a SPA cannot read — and neither was derivable from what /api/v2/bootstrap already carried:

  • The AI notice's hash is a SHA-256 of its message and frequency. It is what invalidates stored dismissals when an administrator edits the wording, and whether the caller's dismissal still applies depends on a stored, server-timestamped record and a date window.
  • The web search notice's condition includes web_search_consent_accepted, which does not start with enable_ and so was never forwarded by _build_feature_flags. That is why it could not live in composerGating.ts.

So bootstrap gains a notices block built from the same functions_ai_notice helpers the classic page uses. route_backend_v2.py deliberately does not hash anything itself, and a test asserts that — a second implementation of the hash would let the two interfaces disagree about whether an edited notice should reappear for someone who had dismissed the previous wording.

"notices": {
  "ai": { "enabled": true, "message": "", "frequency": "every_session", "hash": "<sha256>", "dismissed": false },
  "web_search": { "enabled": true, "text": "" }
}

Behaviour

The web search notice sits above the input and appears only while the Web toggle is armed — a banner that is always present stops being read, and the thing it warns about only happens when web search is on. Turning web search off hides it without consuming the dismissal.

The AI notice sits below the composer. Where a dismissal is stored depends on how long it has to last:

Frequency Stored Why
non_dismissible No dismiss control is rendered
every_session sessionStorage A browser-session fact; the server has nothing to add
daily, once /api/user/settingsaiNoticeDismissal Must outlive the tab, and the window is evaluated against a server timestamp

The notice hides only after the write lands. Hiding first would tell a user the notice is gone for the day when it may be back on the next load. A failed write raises a toast rather than looking like a dead button.

daily/once go through a dedicated dismissAiNotice() helper rather than the debounced userSettingsStore: that store rolls failures back silently into a preference cache, but the route replaces the posted value with its own timestamped record, so the cached value would never match what was stored.

Session keys are shared with the classic interface (webSearchNoticeDismissed, simplechat.aiNoticeDismissal.<hash>) rather than namespaced the way v2RailCollapsed is. A dismissal is a statement about the person, not about which interface they happened to be looking at; namespacing would make a just-dismissed notice reappear on switching interfaces in the same tab. sessionStorage throws rather than returning null in some privacy modes, so reads fail closed and writes report failure.

⚠️ Behaviour change

The hardcoded "AI responses can be inaccurate. Verify important information." line is removed. When the AI notice is disabled, V2 now shows nothing there, matching the classic interface — an organisation that turned the notice off did so on purpose. To get a line back, enable it under Admin Settings → Notices & Agreements → Chat AI Notice. Flagged in the release notes.

Two bugs caught while building this

  • V1 preserves the line breaks an administrator typed (white-space: pre-line); the first pass collapsed them. Fixed, and pinned by a test that reads both sides.
  • CUSTOM_AI_NOTICE.md still said the setting lives on the General tab — it moved to Notices & Agreements. Corrected.

Validation

  • functional_tests/test_v2_chat_notices.py8/8 (new)
  • test_ai_notice, test_user_settings_allowlist_keys, test_v2_api_payload_shapes, test_v2_settings_and_workspace_tags, test_v2_settings_tabs, test_v2_api_security, test_v2_ui_local_assets, test_v2_conversation_details_and_gating, test_v2_research_voice, test_docs_app_surface_coverage, test_docs_site_quality, and the admin-settings contract tests — all pass
  • npm run build (tsc + vite) clean
  • _build_notices verified at runtime, confirming that editing the notice text invalidates existing dismissals and that all three web-search keys are required

Two pre-existing failures are unrelated and untouched: test_chat_template_json_bootstrap_safety.py (a missing window.multiEndpointNotice literal in chats.html) and a Windows console-encoding quirk in test_user_settings_allowlist_keys.py that passes under PYTHONIOENCODING=utf-8.

Notes

  • No new settings keys — both capabilities already exist in admin settings and are documented, so docs/_data/app_surface.yml is unchanged.
  • No new browser assets — existing lucide-react icons and existing info/info-soft theme tokens, so CSP is untouched.
  • Notice text is administrator-entered and rendered as a plain React child, so React escapes it. No dangerouslySetInnerHTML, matching V1's use of textContent/Jinja escaping.
  • Version 0.261.0270.261.028.

Docs

  • docs/explanation/fixes/V2_CHAT_NOTICES_FIX.md (new)
  • docs/explanation/features/REACT_V2_UI.md — new "Notices" section, bootstrap field, test table row
  • docs/explanation/features/CUSTOM_AI_NOTICE.md — both interfaces, corrected tab name
  • docs/explanation/release_notes.md

Two notices an administrator configures appeared in the classic chat page but
never in V2, so anyone who switched interfaces stopped seeing them -- including
the warning that a message is about to leave the tenant.

Both reach the classic page through Jinja context, which the SPA cannot read,
and neither is derivable from what /api/v2/bootstrap already carried. The AI
notice's version hash is a SHA-256 of its message and frequency, and whether the
caller's dismissal still applies depends on a stored, server-timestamped record;
the web search notice additionally requires web_search_consent_accepted, which
does not start with enable_ and so never reached the feature flags. Bootstrap
therefore gains a notices block built from the same functions_ai_notice helpers
the classic page uses, rather than a second implementation of the hash.

Dismissals go where they can survive long enough: every_session stays in session
storage, daily and once are written to /api/user/settings, and non_dismissible
renders no control. The notice hides only after the write lands, so it cannot
claim a dismissal that never happened. The session keys are the ones the classic
client already writes, because a dismissal is a statement about the person, not
about which interface they were looking at.

V2's hardcoded "AI responses can be inaccurate" line is removed. It was not the
administrator's wording, could not be turned off or dismissed, and appeared even
where different text had been configured. When the AI notice is disabled V2 now
shows nothing there, matching the classic interface.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@paullizer
Paul Lizer (paullizer) merged commit 8fe4700 into paullizer-react-v2-ui Sep 2, 2026
2 checks passed
Paul Lizer (paullizer) added a commit that referenced this pull request Sep 2, 2026
The base moved on twice while this was open: PR #1383 (web search and AI
notices) and PR #1386 (inline image proposals). Both conflicts were version
bookkeeping rather than code -- chatStore.ts auto-merged, and the two branches
touch different parts of it.

VERSION goes to 0.261.030. 0.261.028 was taken by the notices work and
0.261.029 by the image proposals, so this claims the next free number rather
than reusing one; the mermaid branch is taking 0.261.031. The release note
entry moves to a new 0.261.030 section above both of theirs, which are left
intact, and the feature doc keeps both its testing-table rows.

Verified after resolving: no leftover markers, the feature doc differs from the
base only by additions, the full V2 suite (24 Python files plus the inline
image proposal runtime checks) passes, both docs tests pass, and the bundle
builds against the merged dependencies.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant