feat(orbit): proactive returning-user greeting (§9.3)#172
Open
Hackshaven wants to merge 2 commits into
Open
Conversation
When a returning visitor reopens the catalog after a gap of more than 24 h, Orbit proactively opens the chat and streams a short, warm welcome-back turn — steered by a local-only returning-user context block (days since last visit, datasets added since, recently viewed). - docentContext: ReturningUserContext + buildReturningUserBlock; thread an optional returningUserBlock into buildSystemPrompt (turn-0 only). - docentService: triggerOpeningTurn — once-per-session latch, skips when the LLM is unconfigured, delegates to processMessage for the full streaming + discovery-tool + load-chip path. - chatUI: playReturningGreeting opens the panel, streams the turn, and attaches a one-time 'Orbit remembers locally' disclosure footnote. - main: maybeGreetReturningVisitor gates on catalog mode + >24 h gap and hands local visit memory (new-since / recently-viewed titles) to the UI. All inputs are local-only (visit memory); nothing is sent server-side except the resulting prompt block to the configured LLM. Also fixes a latent bug where restoreSession left stale messages on re-init when sessionStorage was empty. Signed-off-by: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Implements Phase 7 §9.3 “proactive returning-user greeting” for Orbit chat: when a visitor returns after a >24h gap in catalog mode, the chat panel auto-opens and streams an LLM-generated welcome-back message steered by a local-only returning-visitor context block, with a one-time privacy disclosure footnote.
Changes:
- Add a
ReturningUserContext+buildReturningUserBlock()and thread an optionalreturningUserBlockinto the system prompt (turn-0 only). - Add
triggerOpeningTurn()indocentService(once-per-session latch; skips when LLM unconfigured) andplayReturningGreeting()inchatUI(auto-open, stream, one-time disclosure). - Add UI + i18n + styling for the disclosure footnote, plus a session-restore fix to reset stale messages when sessionStorage is empty.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/ui/chatUI.ts | Adds proactive greeting streaming + one-time disclosure footnote; fixes restoreSession() stale messages when storage is empty. |
| src/ui/chatUI.test.ts | Adds tests for greeting streaming, disclosure gating, and empty-stream placeholder drop. |
| src/types/index.ts | Extends ChatMessage with disclosureFootnote?: boolean. |
| src/styles/chat.css | Styles the disclosure footnote as muted, small text. |
| src/services/docentService.ts | Adds triggerOpeningTurn() and threads returningUserBlock through processMessage → buildSystemPrompt. |
| src/services/docentService.test.ts | Adds coverage for opening-turn latch, skip when unconfigured, and prompt injection. |
| src/services/docentContext.ts | Adds ReturningUserContext, buildReturningUserBlock(), and optional returning-user block inclusion in system prompt. |
| src/services/docentContext.test.ts | Adds tests for returning-user block construction and conditional system-prompt inclusion. |
| src/main.ts | Adds maybeGreetReturningVisitor() gating (>24h gap, catalog mode) and passes local visit context to UI. |
| locales/en.json | Adds disclosure footnote strings. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Address Copilot review on #172: - playReturningGreeting now handles the full DocentStreamChunk union like handleSend: 'rewrite' applies the validated final text (invalid dataset ids stripped) and 'done' converts <<LOAD:...>> markers to inline [[LOAD:...]] placeholders + attaches llmContext (non-enumerable) for feedback payloads. Without this, raw marker syntax could leak into the greeting text and load chips wouldn't render inline. - Announce 'docent responded' on greeting completion so screen-reader users get a cue for the auto-opened turn, matching handleSend. - Add a test asserting marker conversion + rewrite application. Signed-off-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Phase 7 §9.3 of
docs/WEB_CATALOG_FEATURES_PLAN.md— proactive Orbit greeting for returning visitors.When someone reopens the catalog after a gap of more than 24 h, Orbit proactively opens the chat panel and streams a short, warm "welcome back" turn. The greeting is steered by a local-only returning-user context block:
Orbit opens with one short paragraph and offers one or two concrete next steps (a new dataset, returning to something they viewed, or a tour) rather than dumping a list.
How
docentContext.tsReturningUserContextinterface +buildReturningUserBlock()pure string builder; optionalreturningUserBlockthreaded intobuildSystemPrompt(prepended after the role intro, turn-0 only). Lines with no data are omitted.docentService.tstriggerOpeningTurn()— once-per-session latch (resetOpeningTurnForTests()for tests), skips entirely when the LLM is unconfigured (a canned local-engine greeting would feel off), delegates toprocessMessageso the greeting reuses the full streaming + discovery-tool +<<LOAD:>>→chip path. New optionalreturningUserBlockparam onprocessMessage.chatUI.tsplayReturningGreeting()opens the panel, streams the turn, and attaches a one-time "Orbit remembers locally" disclosure footnote (gated bysos-orbit-greeting-disclosed.v1). Self-gates on: callbacks present, not already streaming, no existing user turn, LLM configured.main.tsmaybeGreetReturningVisitor()gates on catalog mode +getLastSession()existing + a >24 h gap, then hands local visit memory to the UI. Fire-and-forget.types/index.tsChatMessage.disclosureFootnote?: boolean.chat.css/en.jsonchat.greeting.disclosure,chat.greeting.disclosure.link).Privacy
All inputs are local-only (visit memory from §9.2). Nothing new is sent server-side except the resulting prompt block to the LLM the user has already configured for chat. No new telemetry events. A one-time, dismissible-by-reading disclosure footnote explains the local memory and links to the privacy surface.
Incidental fix
restoreSession()left the module-levelmessagesarray stale whensessionStoragewas empty — it now resets to[]. This is correct production behavior (re-init shouldn't resurrect a prior conversation) and makes the chat tests order-independent.Testing
npm run test— 3840 passing / 1 skipped (new:buildReturningUserBlock+ system-prompt inclusion indocentContext.test.ts;triggerOpeningTurnlatch/skip/injection indocentService.test.ts;playReturningGreetingstream/footnote/empty-drop inchatUI.test.ts).npm run type-check(incl.check:i18n-strings) — clean.npm run check:locales— artifacts up to date.npm run build— clean.