Skip to content

Ask AI: migrate to Kapa Agent SDK — tools, saved history, sign-in gate#395

Open
JakeSCahill wants to merge 4 commits into
mainfrom
feature/ask-ai-agent-sdk
Open

Ask AI: migrate to Kapa Agent SDK — tools, saved history, sign-in gate#395
JakeSCahill wants to merge 4 commits into
mainfrom
feature/ask-ai-agent-sdk

Conversation

@JakeSCahill

Copy link
Copy Markdown
Contributor

Migrates the Ask AI widget from Kapa's Chat SDK to the Agent SDK (@kapaai/agent-react) and builds the signed-in "AI agent" experience on top of it.

Pairs with docs-site redpanda-data/docs-site#190 (the /kapa/session endpoint + docs login). This bundle talks to that backend by URL at runtime, so there's no code dependency — it can merge independently and is simply inert until the backend ships (anonymous users still get today's stock Kapa widget; the agent panel shows a sign-in prompt). Prerequisite before it does anything live: KAPA_PROJECT_ID + Agent integration ID provisioned (already wired in the partials).

Highlights

  • Agent SDK migrationAskAI.jsx (AgentProvider + getSessionToken), ChatInterface.jsx rewritten onto useAgentChat (flat messages, single streaming state, sources from tool-call blocks, approval cards). Old localStorage persistence removed in favor of server-side history.
  • Client tools (agentTools.js) — navigate to page, switch product, run_bloblang (executes the agent's mapping in the playground WASM and feeds errors back so it self-corrects before presenting), open playground, submit feedback, open community Slack. All approval-gated except the sandboxed run_bloblang.
  • Custom instructions (server-side system prompt, never shown to users) — deployment clarifying questions (asks Cloud vs Self-Managed first, drills into version only when it changes the answer), Bloblang verify-before-present, feedback consent/PII rules. Structured per Kapa's best-practices guidance.
  • Two-tier gating — the agent panel is signed-in only and doubles as the discovery/upsell surface; anonymous Ask AI falls back to the stock Kapa widget. Header account control (26-docs-account.js): Sign in opens a feature modal, signed-in shows an avatar menu with a component-scoped console link (Cloud/Data Platform → Cloud Console, ADP → ADP Console).
  • HistoryAgentThreadHistory, gated on the session's authenticated flag.
  • Feedback — Heap-only (Agent SDK dropped addFeedback); signed-in submissions attach email + thread id server-side.
  • Sources — de-duped and disambiguated (same title across versions → "(Streaming 25.2)"; anchors → section name).
  • Theming/UX — unified indigo palette + WCAG-AA contrast across drawer, header modal, and login page in light and dark; single-scroll panel with sticky input; full-screen toggle; textarea input (Enter sends, Shift+Enter newline).

Testing

  • gulp lint clean; playground WASM tests pass; production bundle ~660 KB smaller than the old Chat SDK bundle.
  • Verified end-to-end on a local docs-site (netlify dev, integration Auth0 tenant): sign-in gate → login → agent chat with tool approvals → verified Bloblang mapping → saved history resume → clarifying questions → sign-out. Both light and dark.

Note

The standalone Bump.sh widget shares this bundle, so it gets the same behavior; the ai.redpanda.com ADP console URL is used as a placeholder pending product confirmation.

🤖 Generated with Claude Code

Replaces the Kapa Chat SDK (@kapaai/react-sdk) with the Agent SDK
(@kapaai/agent-react) and builds the signed-in "AI agent" experience on
top of it. Requires the docs-site backend (kapa-session endpoint + docs
login) — inert until that ships.

- Agent SDK migration: AskAI.jsx (AgentProvider + getSessionToken), and
  ChatInterface.jsx rewritten onto useAgentChat (flat messages, isStreaming,
  sources from tool-call blocks, tool-call approval cards). Drops the old
  localStorage persistence (chatPersistence/persistentApiService) in favor of
  server-side history.
- Client tools (agentTools.js): navigate_to_page, switch_product,
  run_bloblang (verifies mappings in the playground WASM before the agent
  presents them), open_bloblang_playground, submit_docs_feedback,
  ask_community. All approval-gated except the sandboxed run_bloblang.
- Custom instructions (system prompt, not shown to users): deployment
  clarifying questions (Cloud vs Self-Managed first, version only when it
  matters), Bloblang verify-before-present, feedback consent/PII rules.
- Two-tier gating: agent panel is signed-in only; the panel's sign-in screen
  is the discovery surface. Header account control (26-docs-account.js) —
  Sign in opens a feature modal; signed-in shows an avatar menu. Component-
  scoped console link (Cloud/Data Platform -> Cloud Console; ADP -> ADP
  Console). conversation history UI (AgentThreadHistory).
- Feedback is Heap-only (Agent SDK dropped addFeedback); signed-in feedback
  attaches email + thread id server-side.
- Sources list de-dupes and disambiguates repeated titles by version/section.
- Theming/UX: unified indigo palette + WCAG-AA contrast across drawer, header
  modal and login page in both themes; single-scroll panel with sticky input;
  full-screen toggle; textarea input (Enter sends, Shift+Enter newline).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@netlify

netlify Bot commented Jul 6, 2026

Copy link
Copy Markdown

Deploy Preview for docs-ui ready!

Name Link
🔨 Latest commit dc76236
🔍 Latest deploy log https://app.netlify.com/projects/docs-ui/deploys/6a4ccfbfa33dbb0008ddd3cf
😎 Deploy Preview https://deploy-preview-395--docs-ui.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 28 (🔴 down 2 from production)
Accessibility: 89 (no change from production)
Best Practices: 92 (no change from production)
SEO: 89 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fa3757e1-6738-4ead-9d78-be89fc38f3a5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

This PR migrates the Ask AI chat widget from the Kapa react-sdk with local persistence to the Kapa Agent SDK (@kapaai/agent-react), introducing session-token-based authentication, a browser-side agentTools module (navigation, Bloblang execution, product switching, playground link, feedback submission, community link), conversation history UI, and a fullscreen chat panel mode. It also adds a header "Docs account" widget with a sign-in modal, cookie-based auth hinting, and account dropdown, alongside extensive CSS reworks for chat layout/scrolling and new sign-in/account styles. Build config and dependencies are updated accordingly, and prior local conversation-persistence code is removed.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ChatInterface
  participant AskAI as AskAI (AgentProvider)
  participant Backend
  participant agentTools

  User->>ChatInterface: open chat
  ChatInterface->>AskAI: probeSession via cookie hint
  AskAI->>Backend: POST session endpoint
  Backend-->>AskAI: token or 401
  AskAI-->>ChatInterface: kapa-session event (authenticated/loginUrl)
  alt authenticated
    User->>ChatInterface: submit question
    ChatInterface->>AskAI: sendMessage
    AskAI->>agentTools: invoke tool if needed
    agentTools-->>AskAI: tool result
    AskAI-->>ChatInterface: streamed answer + sources
  else not authenticated
    ChatInterface-->>User: show sign-in gate / quick ask
  end
Loading

Suggested reviewers: micheleRP

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: migrating Ask AI to the Kapa Agent SDK with tools, history, and sign-in gating.
Description check ✅ Passed The description is detailed and directly describes the Agent SDK migration, tools, server-backed history, and login gate changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/ask-ai-agent-sdk

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/partials/header-content.hbs (1)

63-82: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Console link URL/markup duplicated 3× — high risk given placeholder ADP URL.

The Cloud/ADP console href, icon SVG, and label text are repeated across desktop nav (Lines 63-82), mobile overflow menu (Lines 97-102), and the account dropdown (Lines 143-148). Per the PR objectives, the ADP console URL is a placeholder pending confirmation — when it's finalized, all three (soon four, once mobile+desktop+dropdown are counted) occurrences must be updated in lockstep, or the UI will show inconsistent URLs.

Consider extracting this into a Handlebars partial (e.g., {{> console-link mode="nav"}}) parameterized by placement, so the URL/label only needs to change once.

Also applies to: 97-102, 143-148

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/partials/header-content.hbs` around lines 63 - 82, The console link
markup is duplicated across multiple branches in header-content.hbs, so the
Cloud/ADP URL, icon SVG, and label can drift when the placeholder ADP URL
changes. Refactor the repeated link blocks in the page.component.name
conditionals into a shared Handlebars partial or helper (for example, a
console-link partial) and pass in the placement-specific label/class while
keeping the href defined in one place. Ensure every existing console-link usage
in the header template reads from the same source so future URL updates only
need to happen once.
♻️ Duplicate comments (1)
src/css/header.css (1)

1252-1262: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Same modal-card overflow gap as header-bump.css.

Mirrors the missing max-height/overflow-y issue flagged in src/css/header-bump.css (Lines 979-989); fix both files consistently since they are kept in sync.

🛠️ Proposed fix
 .tb-signin-modal-card {
   position: relative;
   width: 100%;
   max-width: 440px;
+  max-height: 90vh;
+  overflow-y: auto;
   background: `#fff`;
   color: `#181818`;
   border-radius: 14px;
   padding: 28px 28px 24px;
   box-shadow: 0 20px 60px -12px rgba(15, 23, 42, 0.4);
   text-align: left;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/css/header.css` around lines 1252 - 1262, The .tb-signin-modal-card style
is missing the same vertical overflow constraints as the matching modal card in
header-bump.css, so the sign-in modal can grow beyond the viewport. Update the
.tb-signin-modal-card rule to stay in sync with the corresponding modal-card
styles by adding the same max-height and overflow-y handling used elsewhere in
the header styles. Keep the change consistent across both header.css and
header-bump.css using the shared modal-card selectors/classes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/css/chat-panel.css`:
- Around line 1167-1184: The .scroll-down-button in chat-panel mode is
absolutely positioned but its .chat-footer-wrapper container is still static, so
it anchors to .chat-scroll and can be clipped by overflow handling. Update the
panel-scoped .chat-footer-wrapper in chat-panel.css to be the positioned
containing block for the button, and mirror the same positioning change in
chat-panel-bump.css so the scroll-down button is consistently placed in both
panel styles.

In `@src/css/header-bump.css`:
- Around line 979-989: The sign-in modal card can exceed short viewport height
and has no internal scroll fallback. Update .tb-signin-modal-card to constrain
its height with a viewport-based max-height and enable vertical scrolling (or
otherwise make the modal content scrollable) so the CTA and close controls
remain reachable on small/landscape screens. Use the .tb-signin-modal and
.tb-signin-modal-card selectors to locate the modal styling and keep the layout
visually intact while adding the overflow handling.

In `@src/js/26-docs-account.js`:
- Around line 96-107: The signed-in user email is being stored in sessionStorage
in cleartext via the /auth/me flow and the kapa-session handling, which should
be removed or reduced. Update the docs account logic in showUser and the related
session update path to avoid caching the raw email; instead store only non-PII
state needed for the UI (for example, an avatar initial) or re-fetch the user
when needed. Keep the UI behavior in sync by using the existing showUser
rendering path without persisting sensitive data.
- Around line 36-48: The sign-in modal in openModal/closeModal only toggles
visibility and Escape handling, but it does not manage keyboard focus. Update
openModal to move focus into the modal when it opens, update closeModal to
restore focus to the element that triggered it, and add a focus trap so tabbing
stays within the modal while it is open. Use the existing openModal, closeModal,
and onModalKey hooks to keep the behavior centralized.

In `@src/js/react/components/ChatInterface.jsx`:
- Around line 113-207: The new helper and component declarations in
ChatInterface.jsx are missing the required space before function parentheses,
which will trip the project’s space-before-function-paren lint rule. Update the
declarations for extractSources, baseTitle, humanize, sourceQualifier,
AnswerSources, and autosizeTextarea to match the repo’s existing style used in
AskAI.jsx, keeping the same behavior while fixing only the function signature
spacing.

---

Outside diff comments:
In `@src/partials/header-content.hbs`:
- Around line 63-82: The console link markup is duplicated across multiple
branches in header-content.hbs, so the Cloud/ADP URL, icon SVG, and label can
drift when the placeholder ADP URL changes. Refactor the repeated link blocks in
the page.component.name conditionals into a shared Handlebars partial or helper
(for example, a console-link partial) and pass in the placement-specific
label/class while keeping the href defined in one place. Ensure every existing
console-link usage in the header template reads from the same source so future
URL updates only need to happen once.

---

Duplicate comments:
In `@src/css/header.css`:
- Around line 1252-1262: The .tb-signin-modal-card style is missing the same
vertical overflow constraints as the matching modal card in header-bump.css, so
the sign-in modal can grow beyond the viewport. Update the .tb-signin-modal-card
rule to stay in sync with the corresponding modal-card styles by adding the same
max-height and overflow-y handling used elsewhere in the header styles. Keep the
change consistent across both header.css and header-bump.css using the shared
modal-card selectors/classes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f4edf8f5-4ee6-4ea0-90fa-f4cc660b04c7

📥 Commits

Reviewing files that changed from the base of the PR and between 42657e2 and 43c5be6.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (18)
  • gulp.d/tasks/bundle-react.js
  • package.json
  • src/css/chat-panel-bump.css
  • src/css/chat-panel.css
  • src/css/header-bump.css
  • src/css/header.css
  • src/js/19-chat-panel.js
  • src/js/26-docs-account.js
  • src/js/react/AskAI.jsx
  • src/js/react/agentTools.js
  • src/js/react/chatPersistence.js
  • src/js/react/components/ChatInterface.jsx
  • src/js/react/heap.js
  • src/js/react/persistentApiService.js
  • src/partials/chat-panel-bump.hbs
  • src/partials/chat-panel.hbs
  • src/partials/head-scripts.hbs
  • src/partials/header-content.hbs
💤 Files with no reviewable changes (2)
  • src/js/react/persistentApiService.js
  • src/js/react/chatPersistence.js

Comment thread src/css/chat-panel.css
Comment on lines +1167 to +1184
/* ——— Scroll-to-input button (panel-scoped; home.css only styles it under .home) ——— */
#chat-panel-kapa-root .scroll-down-button {
position: absolute;
top: -3rem;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
justify-content: center;
background: var(--kapa-surface, #fff);
color: var(--kapa-text, #181818);
border: 1px solid var(--kapa-border, #e5e5e5);
border-radius: 50%;
padding: 8px;
z-index: 10;
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.18);
cursor: pointer;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Files ==\n'
git ls-files 'src/css/*panel*.css' 'src/css/*home*.css' | sed 's#^`#-` #'

printf '\n== Outline chat-panel.css ==\n'
ast-grep outline src/css/chat-panel.css --view expanded || true

printf '\n== Relevant line ranges ==\n'
for f in src/css/chat-panel.css src/css/chat-panel-bump.css; do
  echo "--- $f"
  wc -l "$f"
  rg -n "chat-footer-wrapper|chat-scroll|scroll-down-button|position:" "$f" -n -C 3
done

Repository: redpanda-data/docs-ui

Length of output: 5184


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== chat-panel.css: footer / scroll button context ==\n'
sed -n '330,380p' src/css/chat-panel.css
printf '\n---\n'
sed -n '1158,1195p' src/css/chat-panel.css

printf '\n== chat-panel-bump.css: matching area ==\n'
sed -n '330,380p' src/css/chat-panel-bump.css
printf '\n---\n'
sed -n '1158,1195p' src/css/chat-panel-bump.css

Repository: redpanda-data/docs-ui

Length of output: 4247


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in src/css/chat-panel.css src/css/chat-panel-bump.css; do
  echo "===== $f ====="
  sed -n '340,380p' "$f"
  echo
  sed -n '1160,1190p' "$f"
  echo
done

Repository: redpanda-data/docs-ui

Length of output: 3530


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

for path in [Path('src/css/chat-panel.css'), Path('src/css/chat-panel-bump.css')]:
    text = path.read_text()
    for needle in ['.chat-footer-wrapper', '.chat-scroll', '.scroll-down-button']:
        print(f'[{path}] {needle}')
        idx = text.find(needle)
        if idx == -1:
            print('  not found')
            continue
        start = max(0, text.rfind('\n', 0, idx-1) - 200)
        end = min(len(text), text.find('\n}', idx) + 2)
        print(text[start:end])
        print('---')
PY

Repository: redpanda-data/docs-ui

Length of output: 3874


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== file list ==='
git ls-files 'src/css/*panel*.css' 'src/css/*home*.css' | sed 's#^`#-` #'

for f in src/css/chat-panel.css src/css/chat-panel-bump.css; do
  echo
  echo "=== $f (context around relevant selectors) ==="
  rg -n "chat-footer-wrapper|chat-scroll|scroll-down-button|position:" "$f" -n -C 4
done

echo
echo '=== chat-panel.css excerpt ==='
sed -n '340,380p' src/css/chat-panel.css
echo '---'
sed -n '1160,1190p' src/css/chat-panel.css

echo
echo '=== chat-panel-bump.css excerpt ==='
sed -n '340,380p' src/css/chat-panel-bump.css
echo '---'
sed -n '1160,1190p' src/css/chat-panel-bump.css

Repository: redpanda-data/docs-ui

Length of output: 9529


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' --glob '!coverage' "scroll-down-button|chat-footer-wrapper|chat-scroll" .

Repository: redpanda-data/docs-ui

Length of output: 2291


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '740,790p' src/js/react/components/ChatInterface.jsx

Repository: redpanda-data/docs-ui

Length of output: 1903


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== chat-panel.hbs ==\n'
sed -n '1,120p' src/partials/chat-panel.hbs

printf '\n== chat-panel-bump.hbs ==\n'
sed -n '1,120p' src/partials/chat-panel-bump.hbs

printf '\n== ChatInterface.jsx around scroll/footer ==\n'
sed -n '660,790p' src/js/react/components/ChatInterface.jsx

Repository: redpanda-data/docs-ui

Length of output: 24573


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "`#chat-panel-kapa-root`|chat-scroll" src/css src/js/react/components/ChatInterface.jsx

Repository: redpanda-data/docs-ui

Length of output: 26944


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in src/css/chat-panel.css src/css/chat-panel-bump.css; do
  echo "===== $f ====="
  sed -n '118,145p' "$f"
  echo
done

Repository: redpanda-data/docs-ui

Length of output: 1578


.scroll-down-button needs a positioned footer wrapper in panel mode.
#chat-panel-kapa-root .chat-footer-wrapper is position: static, so the absolute button falls back to .chat-scroll and gets clipped by its overflow: hidden. Make the footer wrapper the containing block, and apply the same change in chat-panel-bump.css.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/css/chat-panel.css` around lines 1167 - 1184, The .scroll-down-button in
chat-panel mode is absolutely positioned but its .chat-footer-wrapper container
is still static, so it anchors to .chat-scroll and can be clipped by overflow
handling. Update the panel-scoped .chat-footer-wrapper in chat-panel.css to be
the positioned containing block for the button, and mirror the same positioning
change in chat-panel-bump.css so the scroll-down button is consistently placed
in both panel styles.

Comment thread src/css/header-bump.css
Comment on lines +979 to +989
.tb-signin-modal-card {
position: relative;
width: 100%;
max-width: 440px;
background: #fff;
color: #181818;
border-radius: 14px;
padding: 28px 28px 24px;
box-shadow: 0 20px 60px -12px rgba(15, 23, 42, 0.4);
text-align: left;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Sign-in modal card can overflow on short viewports with no scroll affordance.

.tb-signin-modal-card has no max-height/overflow-y. With a title, subtitle, 4-item feature list, CTA, and footer, the card can exceed the viewport height on short/landscape mobile screens, making the CTA or close button unreachable since .tb-signin-modal (Lines 963-971) doesn't scroll either.

🛠️ Proposed fix
 .tb-signin-modal-card {
   position: relative;
   width: 100%;
   max-width: 440px;
+  max-height: 90vh;
+  overflow-y: auto;
   background: `#fff`;
   color: `#181818`;
   border-radius: 14px;
   padding: 28px 28px 24px;
   box-shadow: 0 20px 60px -12px rgba(15, 23, 42, 0.4);
   text-align: left;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.tb-signin-modal-card {
position: relative;
width: 100%;
max-width: 440px;
background: #fff;
color: #181818;
border-radius: 14px;
padding: 28px 28px 24px;
box-shadow: 0 20px 60px -12px rgba(15, 23, 42, 0.4);
text-align: left;
}
.tb-signin-modal-card {
position: relative;
width: 100%;
max-width: 440px;
max-height: 90vh;
overflow-y: auto;
background: `#fff`;
color: `#181818`;
border-radius: 14px;
padding: 28px 28px 24px;
box-shadow: 0 20px 60px -12px rgba(15, 23, 42, 0.4);
text-align: left;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/css/header-bump.css` around lines 979 - 989, The sign-in modal card can
exceed short viewport height and has no internal scroll fallback. Update
.tb-signin-modal-card to constrain its height with a viewport-based max-height
and enable vertical scrolling (or otherwise make the modal content scrollable)
so the CTA and close controls remain reachable on small/landscape screens. Use
the .tb-signin-modal and .tb-signin-modal-card selectors to locate the modal
styling and keep the layout visually intact while adding the overflow handling.

Comment thread src/js/26-docs-account.js
Comment on lines +36 to +48
function openModal () {
if (!modal) return
modal.hidden = false
document.addEventListener('keydown', onModalKey)
}
function closeModal () {
if (!modal) return
modal.hidden = true
document.removeEventListener('keydown', onModalKey)
}
function onModalKey (e) {
if (e.key === 'Escape') closeModal()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Sign-in modal lacks focus management.

openModal/closeModal toggle hidden and only wire Escape-to-close, but never move focus into the modal on open or restore it to the trigger on close, and there's no focus trap. Keyboard users can tab past the visible modal into background content behind the overlay.

♿ Suggested focus handling
   function openModal () {
     if (!modal) return
     modal.hidden = false
+    var focusable = modal.querySelector('[data-signin-modal-continue], [data-signin-modal-close]')
+    if (focusable) focusable.focus()
     document.addEventListener('keydown', onModalKey)
   }
   function closeModal () {
     if (!modal) return
     modal.hidden = true
     document.removeEventListener('keydown', onModalKey)
+    signinLink.focus()
   }
   function onModalKey (e) {
     if (e.key === 'Escape') closeModal()
+    if (e.key === 'Tab') {
+      var f = modal.querySelectorAll('a[href], button:not([disabled])')
+      if (!f.length) return
+      var first = f[0], last = f[f.length - 1]
+      if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus() }
+      else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus() }
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function openModal () {
if (!modal) return
modal.hidden = false
document.addEventListener('keydown', onModalKey)
}
function closeModal () {
if (!modal) return
modal.hidden = true
document.removeEventListener('keydown', onModalKey)
}
function onModalKey (e) {
if (e.key === 'Escape') closeModal()
}
function openModal () {
if (!modal) return
modal.hidden = false
var focusable = modal.querySelector('[data-signin-modal-continue], [data-signin-modal-close]')
if (focusable) focusable.focus()
document.addEventListener('keydown', onModalKey)
}
function closeModal () {
if (!modal) return
modal.hidden = true
document.removeEventListener('keydown', onModalKey)
signinLink.focus()
}
function onModalKey (e) {
if (e.key === 'Escape') closeModal()
if (e.key === 'Tab') {
var f = modal.querySelectorAll('a[href], button:not([disabled])')
if (!f.length) return
var first = f[0], last = f[f.length - 1]
if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus() }
else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus() }
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/js/26-docs-account.js` around lines 36 - 48, The sign-in modal in
openModal/closeModal only toggles visibility and Escape handling, but it does
not manage keyboard focus. Update openModal to move focus into the modal when it
opens, update closeModal to restore focus to the element that triggered it, and
add a focus trap so tabbing stays within the modal while it is open. Use the
existing openModal, closeModal, and onModalKey hooks to keep the behavior
centralized.

Comment thread src/js/26-docs-account.js
Comment on lines +96 to +107

showUser(null)
fetch('/auth/me', { credentials: 'include' })
.then(function (res) { return res.ok ? res.json() : null })
.then(function (me) {
if (!me) return
showUser(me)
try {
sessionStorage.setItem(CACHE_KEY, JSON.stringify({ email: me.email || null }))
} catch (e) { /* private browsing */ }
})
.catch(function () { /* header still shows generic signed-in state */ })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

PII (email) cached in sessionStorage.

The verified email from /auth/me and from the kapa-session event is cached in sessionStorage as plaintext. Static analysis flags this as cleartext storage of sensitive data (CWE-312) — any XSS on the page can read it, and it persists for the tab's lifetime. Since the email is already rendered in the DOM for the signed-in UI, the marginal exposure is real but limited; still, consider avoiding raw email storage (e.g., cache only the initial for the avatar, or re-fetch each session) to reduce PII footprint.

🔒 Example: avoid caching the raw email
-        try {
-          sessionStorage.setItem(CACHE_KEY, JSON.stringify({ email: me.email || null }))
-        } catch (e) { /* private browsing */ }
+        try {
+          sessionStorage.setItem(CACHE_KEY, JSON.stringify({ initial: (me.email || '').charAt(0).toUpperCase() || null }))
+        } catch (e) { /* private browsing */ }

Also applies to: 122-130

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 103-103: Do not store sensitive data (credentials, tokens, PII) in localStorage or sessionStorage; it is readable by any script and persists on the device.
Context: sessionStorage.setItem(CACHE_KEY, JSON.stringify({ email: me.email || null }))
Note: [CWE-312] Cleartext Storage of Sensitive Information.

(local-storage-sensitive-data)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/js/26-docs-account.js` around lines 96 - 107, The signed-in user email is
being stored in sessionStorage in cleartext via the /auth/me flow and the
kapa-session handling, which should be removed or reduced. Update the docs
account logic in showUser and the related session update path to avoid caching
the raw email; instead store only non-PII state needed for the UI (for example,
an avatar initial) or re-fetch the user when needed. Keep the UI behavior in
sync by using the existing showUser rendering path without persisting sensitive
data.

Source: Linters/SAST tools

Comment on lines +113 to +207
function extractSources(blocks) {
const seen = new Set()
const sources = []
for (const block of blocks || []) {
if (block.type !== 'tool_calls') continue
for (const call of block.toolCalls || []) {
for (const source of call.sources || []) {
if (!source?.sourceUrl || seen.has(source.sourceUrl)) continue
// Only render web URLs — React doesn't block javascript: hrefs
if (!/^https?:\/\//i.test(source.sourceUrl)) continue
seen.add(source.sourceUrl)
sources.push(source)
}
}
}
return sources
}

// Kapa titles can arrive pipe-joined ("Page title|Page title") or empty
function baseTitle(source) {
const title = (source.title || '').split('|').map((p) => p.trim()).filter(Boolean)[0]
if (title) return title
try {
const segs = new URL(source.sourceUrl).pathname.split('/').filter(Boolean)
const last = segs[segs.length - 1] || ''
return humanize(last) || source.sourceUrl
} catch {
return source.sourceUrl
}
}

function humanize(slug) {
return decodeURIComponent(slug).replace(/[-_]+/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()).trim()
}

// Friendly names for the docs product/section path segments
const PRODUCT_LABELS = {
'redpanda-cloud': 'Cloud',
'cloud-data-platform': 'Cloud',
'self-managed': 'Self-Managed',
'data-platform': 'Data Platform',
streaming: 'Streaming',
connect: 'Connect',
'redpanda-connect': 'Connect',
}

// A short qualifier to disambiguate same-titled sources: the section (for
// same-page anchors) or the product/version (for the same page across versions).
function sourceQualifier(url) {
try {
const u = new URL(url)
if (u.hash && u.hash.length > 1) return humanize(u.hash.slice(1))
const segs = u.pathname.split('/').filter(Boolean)
const version = segs.find((s) => /^\d+\.\d+$/.test(s) || s === 'current')
const product = PRODUCT_LABELS[segs[0]]
const ver = version === 'current' ? 'latest' : version
if (product && ver) return `${product} ${ver}`
return ver || product || ''
} catch {
return ''
}
}

function AnswerSources({ blocks }) {
const sources = extractSources(blocks)
if (sources.length === 0) return null
// Only qualify titles that repeat, so unique sources stay clean
const titles = sources.map(baseTitle)
const counts = titles.reduce((acc, t) => ({ ...acc, [t]: (acc[t] || 0) + 1 }), {})
return (
<div className="feedback-container">
<div className="feedback-group">
<button
className="feedback-button"
type="button"
onClick={() => handleFeedback('upvote')}
title="This was helpful"
>
<ThumbsUp className="feedback-icon" />
</button>
<button
className="feedback-button"
type="button"
onClick={() => handleFeedback('downvote')}
title="This wasn't helpful"
>
<ThumbsDown className="feedback-icon" />
</button>
</div>
<div className="answer-sources">
<span className="answer-sources-label">Sources</span>
<ul>
{sources.map((s, i) => {
const title = titles[i]
const qualifier = counts[title] > 1 ? sourceQualifier(s.sourceUrl) : ''
return (
<li key={s.sourceUrl}>
<a href={s.sourceUrl} target="_blank" rel="noopener noreferrer">
{title}{qualifier ? ` (${qualifier})` : ''}
</a>
</li>
)
})}
</ul>
</div>
)
}

// Grow the input with its content, capped by the CSS max-height
function autosizeTextarea(el) {
if (!el) return
el.style.height = 'auto'
el.style.height = `${el.scrollHeight}px`
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Missing space before function parentheses (fails project lint).

The new helpers (extractSources, baseTitle, humanize, sourceQualifier, AnswerSources, autosizeTextarea) are declared without a space before (, which violates the style the repo enforces elsewhere — AskAI.jsx in this same PR uses function announceSession (…), getSessionToken (), etc. Static analysis flags each of these lines with space-before-function-paren, so lint/CI will fail.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 190-192: A list component should have a key to prevent re-rendering
Context:
{title}{qualifier ? (${qualifier}) : ''}

Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)

🪛 ESLint

[error] 113-113: Missing space before function parentheses.

(space-before-function-paren)


[error] 132-132: Missing space before function parentheses.

(space-before-function-paren)


[error] 144-144: Missing space before function parentheses.

(space-before-function-paren)


[error] 161-161: Missing space before function parentheses.

(space-before-function-paren)


[error] 176-176: 'AnswerSources' is defined but never used.

(no-unused-vars)


[error] 176-176: Missing space before function parentheses.

(space-before-function-paren)


[error] 203-203: Missing space before function parentheses.

(space-before-function-paren)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/js/react/components/ChatInterface.jsx` around lines 113 - 207, The new
helper and component declarations in ChatInterface.jsx are missing the required
space before function parentheses, which will trip the project’s
space-before-function-paren lint rule. Update the declarations for
extractSources, baseTitle, humanize, sourceQualifier, AnswerSources, and
autosizeTextarea to match the repo’s existing style used in AskAI.jsx, keeping
the same behavior while fixing only the function signature spacing.

Source: Linters/SAST tools

JakeSCahill and others added 3 commits July 7, 2026 10:04
Kapa is an approved processor for this PII, so set AgentProvider `user={email}`
(from the session probe) — conversations are attributed to the person in Kapa's
dashboard. external_owner_id stays the salted-sub hash; the SDK reads `user`
reactively so setting it after the probe resolves is fine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Logged-out users now get the same drawer UI powered by the Chat SDK
(@kapaai/react-sdk) instead of being routed to the stock Kapa widget.
Signed-in users keep the Agent SDK (tools, history, email attribution).

- AskAI.jsx App() picks the provider by session tier: agent (true),
  Chat SDK (false), brief loading (null). Probe now falls to the anonymous
  tier on any non-authenticated outcome (401, missing backend, network
  error) so the drawer never hangs.
- ChatSdkInterface.jsx — anonymous drawer on useChat: welcome, chips,
  QA rendering, textarea input, Heap analytics, a slim sign-in upsell
  banner. No session backend needed (Chat SDK uses its own bot protection).
- chatShared.jsx — extracted marked/Answer/Toast so both tiers render
  identically; ChatInterface imports them.
- KAPA_CHAT_INTEGRATION_ID global (partials) for the Chat SDK integration.
- Bundle +~72KB gzipped (both SDKs) — expected cost of the two-tier UX.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The signed-in welcome screen now shows capability-demonstrating actions
instead of generic doc questions, so the agent's value is legible on first
open. Bloblang leads — it auto-runs (no approval) and visibly does something.

- ChatInterface.jsx: AGENT_EXAMPLES (run_bloblang, navigate, feedback,
  community) rendered as icon'd action cards; description updated.
- CSS: .suggestion-card-action flex layout with accent icon.
- Anonymous tier keeps the generic AI_SUGGESTIONS questions, reinforcing the
  tier difference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.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