feat: v12 UX Polish & Onboarding (#150, #87, #60, #9)#156
feat: v12 UX Polish & Onboarding (#150, #87, #60, #9)#156seanthimons merged 1 commit intointegrationfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR delivers V12 UX polish across the Shiny app: onboarding/welcome workflow, better feedback for long-running synthesis actions, prompt transparency UI, version surfacing, and a verbose OpenAlex logging toggle—plus dark-mode styling updates to support the new UI elements.
Changes:
- Reworked welcome modal + welcome landing page into a 5-step workflow, added contextual “info” text on key sidebar views.
- Added synthesis progress modals/status text and introduced (partial) prompt editor UI for chat/presets.
- Added
SERAPEUM_VERSIONand surfaced it in the title bar + About page; added optional OpenAlex verbose logging hook + dark-mode CSS updates.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/plans/2026-03-13-feat-v12-ux-polish-onboarding-plan.md | New implementation plan documenting V12 scope and acceptance criteria |
| docs/brainstorms/2026-03-13-v12-ux-polish-onboarding-brainstorm.md | New brainstorm doc capturing decisions and rationale |
| app.R | Version in window/title, welcome wizard + landing page overhaul, JS handlers (wizard + console logging) |
| R/theme_catppuccin.R | Dark-mode CSS overrides for modals, prompt editor, version badge |
| R/rag.R | Added preset-instruction helper + generate_preset() support for custom_prompt |
| R/mod_topic_explorer.R | Added contextual help text at top of UI |
| R/mod_settings.R | Added “Verbose API logging” toggle + persistence via DB setting + R option |
| R/mod_seed_discovery.R | Added contextual help text at top of UI |
| R/mod_search_notebook.R | Added contextual help text, prompt editor UI, synthesis modal/status plumbing |
| R/mod_query_builder.R | Added contextual help text at top of UI |
| R/mod_document_notebook.R | Added contextual help text, synthesis modal/status updates, prompt editor wiring (partial) |
| R/mod_citation_network.R | Added contextual help text at top of UI |
| R/mod_citation_audit.R | Added contextual help text at top of UI |
| R/mod_about.R | Added “What’s New” section using SERAPEUM_VERSION |
| R/config.R | Added SERAPEUM_VERSION constant |
| R/api_openalex.R | Wrapped OpenAlex requests in perform_openalex() with optional verbose logging |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| observeEvent(input$wizard_analyze, { | ||
| removeModal() | ||
| current_notebook(NULL) | ||
| current_view("query_builder") |
| # Show wizard on first load — only if no notebooks exist and not previously dismissed | ||
| observe({ | ||
| con <- con_r() | ||
| req(con) | ||
| notebooks <- list_notebooks(con) | ||
| if (nrow(notebooks) == 0) { | ||
| has_seen <- isTRUE(input$has_seen_wizard) | ||
| if (nrow(notebooks) == 0 && !has_seen) { | ||
| shiny::onFlushed(function() { |
R/mod_settings.R
Outdated
| "Changes only apply to newly uploaded documents."), | ||
| bslib::input_switch(ns("verbose_mode"), "Verbose API logging", value = FALSE), | ||
| p(class = "text-muted small", | ||
| "Log OpenAlex API calls to the browser console for debugging."), |
R/mod_search_notebook.R
Outdated
| # Collapsible prompt editor (opt-in) | ||
| tags$details( | ||
| class = "mb-2", | ||
| tags$summary(class = "text-muted small", style = "cursor: pointer;", | ||
| icon_edit(), " View/Edit Prompt"), | ||
| div( | ||
| class = "border rounded p-2 mt-1", | ||
| textAreaInput(ns("prompt_edit"), NULL, | ||
| placeholder = "The assembled prompt will appear here when you type a message or click a preset...", | ||
| rows = 4, width = "100%"), | ||
| p(class = "text-muted small mb-0", | ||
| "Edit the prompt text before sending. Collapse this section to use the default prompts.") | ||
| ) | ||
| ), |
| update_synthesis_status <- function(message) { | ||
| session$sendCustomMessage("updateSynthesisStatus", list( | ||
| msg_id = ns("synthesis_status"), | ||
| message = message | ||
| )) | ||
| } |
app.R
Outdated
| // Verbose API logging to browser console | ||
| Shiny.addCustomMessageHandler('consoleLog', function(data) { | ||
| console.log('[' + data.label + ']', data.url); | ||
| }); | ||
|
|
| #' @return httr2 response | ||
| perform_openalex <- function(req) { | ||
| if (isTRUE(getOption("serapeum.verbose_api", FALSE))) { | ||
| url <- req$url | ||
| message("[OpenAlex API] ", url) |
| observeEvent(input$btn_overview_generate, { | ||
| req(!is_processing()) | ||
| req(has_api_key()) | ||
| is_processing(TRUE) | ||
|
|
||
| depth <- input$overview_depth %||% "concise" | ||
| mode <- input$overview_mode %||% "quick" | ||
|
|
||
| depth_label <- if (identical(depth, "detailed")) "Detailed" else "Concise" | ||
| mode_label <- if (identical(mode, "thorough")) "Thorough" else "Quick" | ||
|
|
||
| toggle_popover(id = ns("overview_popover")) | ||
| show_synthesis_modal(paste0("Overview (", depth_label, ", ", mode_label, ")")) | ||
|
|
R/mod_document_notebook.R
Outdated
| # If prompt editor is expanded and empty, populate it with the system prompt | ||
| # and wait for the user to review/edit before sending | ||
| edited_prompt <- trimws(input$prompt_edit %||% "") | ||
| editor_open <- isTRUE(input$prompt_editor_open) | ||
| if (editor_open && nchar(edited_prompt) == 0) { | ||
| rag_system <- "You are a helpful research assistant. Answer questions based ONLY on the provided sources. Always cite your sources using the format [Document Name, p.X] or [Paper Title]." | ||
| assembled <- paste0("[System Prompt]\n", rag_system, "\n\n[Your Question]\n", user_msg) | ||
| session$sendCustomMessage("populatePromptEditor", list( | ||
| input_id = ns("prompt_edit"), | ||
| text = assembled | ||
| )) | ||
| showNotification("Prompt loaded into editor. Edit and click Send when ready.", | ||
| type = "message", duration = 3) | ||
| # Re-enable the send button (it was disabled by the click handler JS) | ||
| session$sendCustomMessage("docChatReady", ns("")) | ||
| return() | ||
| } | ||
|
|
||
| effective_msg <- if (nchar(edited_prompt) > 0) edited_prompt else user_msg | ||
|
|
| if (is_processing()) { | ||
| doc_count <- tryCatch(nrow(list_documents(con(), notebook_id())), error = function(e) 0L) | ||
| status_text <- if (doc_count > 0) { | ||
| sprintf("Analyzing %d document%s...", doc_count, if (doc_count == 1) "" else "s") | ||
| } else { | ||
| "Thinking..." | ||
| } |
- Fix wizard_analyze label/route mismatch (button now says "Query Builder")
- Fix verbose logging text ("browser console" → "R console") and remove dead consoleLog JS handler
- Move updateSynthesisStatus JS handler to global scope in app.R
- Add empty notebook guard to search notebook overview preset
- Cache doc_count in reactiveVal to avoid repeated DB queries during render
- Strip half-baked prompt editor UI and server logic (tracked in #164)
There was a problem hiding this comment.
Pull request overview
This PR targets the v12 UX/onboarding polish set: improving first-run guidance, adding progress feedback for long-running synthesis actions, introducing app version surfacing, adding contextual sidebar help text, and adding a verbose OpenAlex logging toggle.
Changes:
- Reworks the welcome wizard + welcome landing view into a 5-step workflow with routing actions.
- Adds synthesis progress modals + more contextual “Thinking…” status text in notebook chat UIs, plus empty-notebook guards for some presets.
- Adds version constant + title-bar badge and an About “What’s New” section; adds a Settings toggle + OpenAlex request wrapper for verbose logging.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/plans/2026-03-13-feat-v12-ux-polish-onboarding-plan.md | Adds the implementation plan for v12 UX/onboarding scope. |
| docs/brainstorms/2026-03-13-v12-ux-polish-onboarding-brainstorm.md | Captures feature brainstorming/decisions for the v12 scope. |
| app.R | Adds version in title/tab, welcome wizard + landing workflow, and a global JS handler for synthesis status updates. |
| README.md | Updates feature list to mention onboarding, progress feedback, verbose logging, and prompt editing. |
| R/theme_catppuccin.R | Adds dark-mode CSS overrides for modals, prompt-editor styling, and version badge. |
| R/rag.R | Adds preset instruction helper and custom_prompt support in generate_preset(). |
| R/mod_settings.R | Adds “Verbose API logging” switch and persists it to DB; sets a global R option. |
| R/api_openalex.R | Introduces perform_openalex() wrapper and routes OpenAlex calls through it. |
| R/mod_document_notebook.R | Adds contextual help text, synthesis modal + inline status improvements, and empty-notebook guards for presets. |
| R/mod_search_notebook.R | Adds contextual help text, synthesis modal helpers, inline status improvements, and an empty-notebook guard for Overview. |
| R/mod_seed_discovery.R | Adds contextual help text on landing state. |
| R/mod_query_builder.R | Adds contextual help text on landing state. |
| R/mod_topic_explorer.R | Adds contextual help text on landing state. |
| R/mod_citation_network.R | Adds contextual help text on landing state. |
| R/mod_citation_audit.R | Adds contextual help text on landing state. |
| R/mod_about.R | Adds “What’s New” section referencing SERAPEUM_VERSION. |
| R/config.R | Adds SERAPEUM_VERSION constant. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| observeEvent(input$wizard_search, { | ||
| removeModal() | ||
| current_notebook(NULL) | ||
| current_view("discover") | ||
| }) |
| perform_openalex <- function(req) { | ||
| if (isTRUE(getOption("serapeum.verbose_api", FALSE))) { | ||
| url <- req$url | ||
| message("[OpenAlex API] ", url) |
R/rag.R
Outdated
| prompt <- presets[[preset_type]] | ||
| generate_preset <- function(con, config, notebook_id, preset_type, session_id = NULL, | ||
| custom_prompt = NULL) { | ||
| prompt <- custom_prompt %||% get_preset_instruction(preset_type) |
| req(!is_processing()) | ||
| req(has_api_key()) | ||
| is_processing(TRUE) | ||
| show_synthesis_modal("Research Questions") | ||
|
|
| observeEvent(input$welcome_search, { | ||
| current_notebook(NULL) | ||
| current_view("discover") | ||
| }) |
| # Show wizard on first load — only if no notebooks exist and not previously dismissed | ||
| observe({ | ||
| con <- con_r() | ||
| req(con) | ||
| notebooks <- list_notebooks(con) | ||
| if (nrow(notebooks) == 0) { | ||
| has_seen <- isTRUE(input$has_seen_wizard) | ||
| if (nrow(notebooks) == 0 && !has_seen) { | ||
| shiny::onFlushed(function() { | ||
| showModal(wizard_modal()) | ||
| }, once = TRUE) | ||
| } | ||
| }) |> bindEvent(con_r(), once = TRUE) |
R/mod_settings.R
Outdated
| updateNumericInput(session, "chunk_overlap", value = chunk_overlap) | ||
|
|
||
| verbose_mode <- get_db_setting(con(), "verbose_mode") %||% FALSE | ||
| bslib::update_switch("verbose_mode", value = isTRUE(verbose_mode)) |
| - **One-click presets** - Summarize, Key Points, Study Guide, Outline, and more | ||
| - **Synthesis progress feedback** - Status modal with rotating stage indicators for long-running presets | ||
| - **Editable prompts** - Expand the prompt editor to view and customize LLM prompts before sending | ||
| - **Chat export** - Download conversations as Markdown (.md) or styled HTML (.html) |
| req(!is_processing()) | ||
| req(has_api_key()) | ||
| is_processing(TRUE) | ||
| show_synthesis_modal("Conclusion Synthesis") | ||
|
|
- Add empty notebook guards to search notebook Conclusions and Research Questions presets (prevents modal flash on empty notebooks) - Redact api_key from verbose OpenAlex log output - Fix wizard dismissal race by requiring has_seen_wizard input before check - Fix misplaced roxygen docs on generate_preset() and document custom_prompt - Remove "Editable prompts" from README (feature not yet implemented)
Without the session parameter, the verbose API logging toggle may not restore correctly from saved settings on app startup.
Summary
Testing
Closes #150, closes #87, closes #60, closes #9