From 1a60ecbd8b54953c713e32b6dabf2e0fa9365694 Mon Sep 17 00:00:00 2001 From: ducoterra Date: Sat, 29 Aug 2026 17:27:04 -0400 Subject: [PATCH] =?UTF-8?q?feat(chat):=20stop=20an=20in-flight=20answer=20?= =?UTF-8?q?=E2=80=94=20Send=20becomes=20Stop,=20the=20partial=20is=20kept?= =?UTF-8?q?=20and=20persisted,=20the=20model=20stream=20is=20torn=20down?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../02_e2e_story_suite_commit.md | 70 --- .../01_fix_background_animation.md | 60 --- .../23_containerfile_build/00_phase.md | 120 ----- .../01_fix_containerfile_build.md | 91 ---- .../02_integration_test_commit.md | 70 --- .../24_whole_document_context/00_phase.md | 138 ------ .../01_remove_context_cap.md | 62 --- .../02_whole_doc_e2e_suite.md | 84 ---- .../03_story_docs_plan_commit.md | 76 ---- .../01_still_background_css.md | 128 ------ .../26_document_modal_viewer/00_phase.md | 44 -- .../01_modal_css_and_html.md | 62 --- .../02_app_js_modal_intercept.md | 40 -- .../03_document_js_modal_mode.md | 29 -- .../04_e2e_regression_suite.md | 33 -- .../01_steering_put_endpoint.md | 46 -- .../02_tuning_page_html_css.md | 52 --- .../27_global_tuning/03_tuning_js_crud.md | 38 -- .../04_header_button_and_e2e.md | 34 -- .../01_env_var_and_settings.md | 37 -- .../02_git_clone_pull_utility.md | 67 --- .../03_import_docs_script_refactor.md | 49 -- .../04_integration_test_and_docs.md | 37 -- .../complete/29_tuning_nav_link/00_phase.md | 40 -- .../01_tuning_links_markup.md | 41 -- .../02_tuning_nav_e2e_and_regression.md | 53 --- .../30_document_summaries/00_phase.md | 48 -- .../01_lite_model_client.md | 26 -- .../02_migration_summary_columns.md | 23 - .../03_summarizer_module.md | 30 -- .../04_importer_summary_integration.md | 39 -- .../05_pipeline_summary_resolution.md | 31 -- .../30_document_summaries/06_mock_and_e2e.md | 35 -- .../31_kb_overview_prompt/00_phase.md | 45 -- .../01_migration_kb_overview.md | 22 - .../02_overview_generator.md | 29 -- .../03_prompt_injection.md | 30 -- .../04_import_trigger.md | 33 -- .../31_kb_overview_prompt/05_mock_and_e2e.md | 31 -- .../complete/32_admin_sync_button/00_phase.md | 45 -- .../32_admin_sync_button/01_sync_api.md | 37 -- .../32_admin_sync_button/02_ui_button.md | 40 -- .../32_admin_sync_button/03_e2e_and_docs.md | 34 -- .../complete/33_cache_busting/00_phase.md | 40 -- .../01_asset_version_token.md | 28 -- .../33_cache_busting/02_caching_middleware.md | 38 -- .../33_cache_busting/03_e2e_and_docs.md | 32 -- .../complete/34_consistent_navbar/00_phase.md | 46 -- .../01_steering_moves_to_module.md | 33 -- .../02_sync_and_chat_moves_to_module.md | 37 -- .../03_full_header_all_pages.md | 39 -- .../04_viewer_titlebar_styles.md | 29 -- .../05_e2e_and_contract_update.md | 33 -- .../complete/35_git_sources_admin/00_phase.md | 47 -- .../01_model_and_migration.md | 46 -- .../02_git_sources_api.md | 39 -- .../03_sync_and_importer_use_db.md | 38 -- .../35_git_sources_admin/04_admin_page.md | 41 -- .../35_git_sources_admin/05_nav_link.md | 32 -- .../35_git_sources_admin/06_e2e_and_docs.md | 37 -- .../complete/36_summary_in_viewer/00_phase.md | 40 -- .../01_content_api_summary_field.md | 32 -- .../02_viewer_summary_panel.md | 44 -- .../03_e2e_and_regression.md | 31 -- .../01_probe_tool_calling.md | 24 - .../02_llm_client_tools.md | 36 -- .../37_agent_document_tools/03_agent_loop.md | 50 --- .../04_api_sse_tool_event.md | 36 -- .../05_frontend_tool_states.md | 27 -- .../01_kind_column.md | 24 - .../02_api_local_kind.md | 27 -- .../03_sync_import_local.md | 30 -- .../04_admin_page_local.md | 24 - .../39_configurable_brand/00_phase.md | 39 -- .../01_config_endpoint.md | 27 -- .../02_frontend_branding.md | 34 -- .../03_e2e_docs_commit.md | 27 -- .../01_toggle_ships_hidden.md | 26 -- .../01_config_round_cap.md | 52 --- .../02_mock_multi_read_flow.md | 27 -- .../46_mobile_hamburger_nav/00_phase.md | 40 -- .../01_hamburger_markup_all_pages.md | 42 -- .../02_toggle_behavior.md | 63 --- .../03_hamburger_e2e_and_commit.md | 28 -- .../01_config_formats.md | 29 -- .../02_chunker_dispatch_fixtures.md | 39 -- .../03_importer_integration.md | 30 -- .../48_nav_rename_sources/00_phase.md | 40 -- .../01_nav_label_swap.md | 46 -- .../02_existing_test_labels.md | 34 -- .../03_story_e2e_regression_commit.md | 33 -- .../49_archive_upload_sources/00_phase.md | 51 --- .../01_settings_and_unpack_utility.md | 36 -- .../02_upload_api.md | 43 -- .../03_admin_page_upload.md | 58 --- .../04_story_e2e_docs_commit.md | 34 -- .../todo/40_tuning_toggle_flash/00_phase.md | 37 -- .../02_flash_e2e_and_regression.md | 26 -- .../todo/41_sync_fail_fast_models/00_phase.md | 38 -- .../01_model_probe_fail_fast.md | 37 -- .../02_sync_error_modal.md | 29 -- .../03_model_down_e2e_and_commit.md | 28 -- .../todo/42_no_reply_autoscroll/00_phase.md | 36 -- .../01_remove_autofollow.md | 35 -- .../02_no_autoscroll_e2e_and_commit.md | 27 -- .../todo/43_thinking_scroll_back/00_phase.md | 38 -- .../01_window_user_scrollable.md | 28 -- .../02_gated_bottom_pin.md | 59 --- .../03_thinking_scroll_e2e_and_commit.md | 28 -- .../todo/44_markdown_tables/00_phase.md | 39 -- .../01_table_renderer_and_styles.md | 40 -- .../02_mock_table_trigger.md | 40 -- .../03_tables_e2e_and_commit.md | 28 -- .../todo/45_agent_unlimited_tools/00_phase.md | 42 -- .../03_unlimited_tools_e2e_and_commit.md | 39 -- .../todo/47_quadlet_jinja_import/00_phase.md | 42 -- .../04_quadlet_e2e_and_docs_commit.md | 30 -- .../21_thinking_no_scroll/phase_report.md | 55 --- .../reports/22_background_animation/report.md | 186 -------- .../23_containerfile_build/phase_report.md | 120 ----- .../reports/25_background_no_motion/report.md | 97 ---- .../22_background_animation/after_frame1.png | Bin 137310 -> 0 bytes .../22_background_animation/after_frame2.png | Bin 142837 -> 0 bytes .../after_gridonly_frame1.png | Bin 71993 -> 0 bytes .../after_gridonly_frame2.png | Bin 81587 -> 0 bytes .../22_background_animation/before_frame1.png | Bin 114698 -> 0 bytes .../22_background_animation/before_frame2.png | Bin 110303 -> 0 bytes .../before_glow_only.png | Bin 98012 -> 0 bytes .../before_grid_only.png | Bin 65288 -> 0 bytes .../before_gridonly_frame1.png | Bin 67963 -> 0 bytes .../before_gridonly_frame2.png | Bin 66560 -> 0 bytes .../before_occlusion_probe.png | Bin 105987 -> 0 bytes .../diagnosis_diff.txt | 24 - .../22_background_animation/diagnosis_log.txt | 13 - .../22_background_animation/final_360px.png | Bin 73718 -> 0 bytes .../22_background_animation/final_frame1.png | Bin 140355 -> 0 bytes .../22_background_animation/final_frame2.png | Bin 129849 -> 0 bytes .../25_background_no_motion/after.png | Bin 279980 -> 0 bytes .../25_background_no_motion/after_4s.png | Bin 292659 -> 0 bytes .../25_background_no_motion/before.png | Bin 142707 -> 0 bytes .../25_background_no_motion/before_4s.png | Bin 139623 -> 0 bytes .agent/user_stories/admin-sync-button.md | 92 ---- .agent/user_stories/agent-unlimited-tools.md | 103 ----- .agent/user_stories/archive-upload-sources.md | 75 ---- .agent/user_stories/background-animation.md | 128 ------ .agent/user_stories/background-no-motion.md | 181 -------- .agent/user_stories/cache-busting.md | 91 ---- .agent/user_stories/containerfile-build.md | 131 ------ .agent/user_stories/document-modal.md | 54 --- .agent/user_stories/document-summaries.md | 70 --- .agent/user_stories/git-sources-admin.md | 53 --- .agent/user_stories/git-sources.md | 40 -- .agent/user_stories/global-tuning.md | 51 --- .agent/user_stories/kb-overview-prompt.md | 80 ---- .agent/user_stories/markdown-tables.md | 100 ----- .agent/user_stories/mobile-hamburger-nav.md | 110 ----- .agent/user_stories/nav-consistency.md | 57 --- .agent/user_stories/nav-sources-rag-rename.md | 49 -- .agent/user_stories/no-reply-autoscroll.md | 91 ---- .agent/user_stories/quadlet-jinja-import.md | 98 ---- .agent/user_stories/summary-in-viewer.md | 45 -- .agent/user_stories/sync-model-fail-fast.md | 97 ---- .agent/user_stories/thinking-no-scroll.md | 99 ----- .agent/user_stories/thinking-scroll-back.md | 98 ---- .agent/user_stories/tuning-toggle-flash.md | 87 ---- .agent/user_stories/whole-document-context.md | 67 --- app/api/chat.py | 383 ++++++++-------- app/rag/agent.py | 32 +- app/rag/llm.py | 33 +- frontend/assets/app.js | 197 ++++++-- frontend/assets/styles.css | 26 ++ frontend/index.html | 19 +- tests/e2e/test_agent_document_tools.py | 79 +++- tests/e2e/test_agent_unlimited_tools.py | 19 +- tests/e2e/test_loading_feedback.py | 29 +- tests/e2e/test_no_reply_autoscroll.py | 18 +- tests/e2e/test_sources_midstream_bug.py | 13 +- tests/e2e/test_stop_generation.py | 334 ++++++++++++++ tests/e2e/test_thinking_display.py | 7 +- tests/e2e/test_thinking_scroll.py | 13 +- tests/unit/test_chat_cancel.py | 419 ++++++++++++++++++ tests/unit/test_frontend_feedback.py | 180 +++++++- tests/unit/test_frontend_scroll.py | 4 +- tests/unit/test_frontend_tool_states.py | 15 +- tests/unit/test_llm_client.py | 6 + tests/unit/test_llm_stream_teardown.py | 203 +++++++++ 186 files changed, 1738 insertions(+), 7796 deletions(-) delete mode 100644 .agent/phases/complete/21_thinking_no_scroll/02_e2e_story_suite_commit.md delete mode 100644 .agent/phases/complete/22_background_animation/01_fix_background_animation.md delete mode 100644 .agent/phases/complete/23_containerfile_build/00_phase.md delete mode 100644 .agent/phases/complete/23_containerfile_build/01_fix_containerfile_build.md delete mode 100644 .agent/phases/complete/23_containerfile_build/02_integration_test_commit.md delete mode 100644 .agent/phases/complete/24_whole_document_context/00_phase.md delete mode 100644 .agent/phases/complete/24_whole_document_context/01_remove_context_cap.md delete mode 100644 .agent/phases/complete/24_whole_document_context/02_whole_doc_e2e_suite.md delete mode 100644 .agent/phases/complete/24_whole_document_context/03_story_docs_plan_commit.md delete mode 100644 .agent/phases/complete/25_background_no_motion/01_still_background_css.md delete mode 100644 .agent/phases/complete/26_document_modal_viewer/00_phase.md delete mode 100644 .agent/phases/complete/26_document_modal_viewer/01_modal_css_and_html.md delete mode 100644 .agent/phases/complete/26_document_modal_viewer/02_app_js_modal_intercept.md delete mode 100644 .agent/phases/complete/26_document_modal_viewer/03_document_js_modal_mode.md delete mode 100644 .agent/phases/complete/26_document_modal_viewer/04_e2e_regression_suite.md delete mode 100644 .agent/phases/complete/27_global_tuning/01_steering_put_endpoint.md delete mode 100644 .agent/phases/complete/27_global_tuning/02_tuning_page_html_css.md delete mode 100644 .agent/phases/complete/27_global_tuning/03_tuning_js_crud.md delete mode 100644 .agent/phases/complete/27_global_tuning/04_header_button_and_e2e.md delete mode 100644 .agent/phases/complete/28_git_based_sources/01_env_var_and_settings.md delete mode 100644 .agent/phases/complete/28_git_based_sources/02_git_clone_pull_utility.md delete mode 100644 .agent/phases/complete/28_git_based_sources/03_import_docs_script_refactor.md delete mode 100644 .agent/phases/complete/28_git_based_sources/04_integration_test_and_docs.md delete mode 100644 .agent/phases/complete/29_tuning_nav_link/00_phase.md delete mode 100644 .agent/phases/complete/29_tuning_nav_link/01_tuning_links_markup.md delete mode 100644 .agent/phases/complete/29_tuning_nav_link/02_tuning_nav_e2e_and_regression.md delete mode 100644 .agent/phases/complete/30_document_summaries/00_phase.md delete mode 100644 .agent/phases/complete/30_document_summaries/01_lite_model_client.md delete mode 100644 .agent/phases/complete/30_document_summaries/02_migration_summary_columns.md delete mode 100644 .agent/phases/complete/30_document_summaries/03_summarizer_module.md delete mode 100644 .agent/phases/complete/30_document_summaries/04_importer_summary_integration.md delete mode 100644 .agent/phases/complete/30_document_summaries/05_pipeline_summary_resolution.md delete mode 100644 .agent/phases/complete/30_document_summaries/06_mock_and_e2e.md delete mode 100644 .agent/phases/complete/31_kb_overview_prompt/00_phase.md delete mode 100644 .agent/phases/complete/31_kb_overview_prompt/01_migration_kb_overview.md delete mode 100644 .agent/phases/complete/31_kb_overview_prompt/02_overview_generator.md delete mode 100644 .agent/phases/complete/31_kb_overview_prompt/03_prompt_injection.md delete mode 100644 .agent/phases/complete/31_kb_overview_prompt/04_import_trigger.md delete mode 100644 .agent/phases/complete/31_kb_overview_prompt/05_mock_and_e2e.md delete mode 100644 .agent/phases/complete/32_admin_sync_button/00_phase.md delete mode 100644 .agent/phases/complete/32_admin_sync_button/01_sync_api.md delete mode 100644 .agent/phases/complete/32_admin_sync_button/02_ui_button.md delete mode 100644 .agent/phases/complete/32_admin_sync_button/03_e2e_and_docs.md delete mode 100644 .agent/phases/complete/33_cache_busting/00_phase.md delete mode 100644 .agent/phases/complete/33_cache_busting/01_asset_version_token.md delete mode 100644 .agent/phases/complete/33_cache_busting/02_caching_middleware.md delete mode 100644 .agent/phases/complete/33_cache_busting/03_e2e_and_docs.md delete mode 100644 .agent/phases/complete/34_consistent_navbar/00_phase.md delete mode 100644 .agent/phases/complete/34_consistent_navbar/01_steering_moves_to_module.md delete mode 100644 .agent/phases/complete/34_consistent_navbar/02_sync_and_chat_moves_to_module.md delete mode 100644 .agent/phases/complete/34_consistent_navbar/03_full_header_all_pages.md delete mode 100644 .agent/phases/complete/34_consistent_navbar/04_viewer_titlebar_styles.md delete mode 100644 .agent/phases/complete/34_consistent_navbar/05_e2e_and_contract_update.md delete mode 100644 .agent/phases/complete/35_git_sources_admin/00_phase.md delete mode 100644 .agent/phases/complete/35_git_sources_admin/01_model_and_migration.md delete mode 100644 .agent/phases/complete/35_git_sources_admin/02_git_sources_api.md delete mode 100644 .agent/phases/complete/35_git_sources_admin/03_sync_and_importer_use_db.md delete mode 100644 .agent/phases/complete/35_git_sources_admin/04_admin_page.md delete mode 100644 .agent/phases/complete/35_git_sources_admin/05_nav_link.md delete mode 100644 .agent/phases/complete/35_git_sources_admin/06_e2e_and_docs.md delete mode 100644 .agent/phases/complete/36_summary_in_viewer/00_phase.md delete mode 100644 .agent/phases/complete/36_summary_in_viewer/01_content_api_summary_field.md delete mode 100644 .agent/phases/complete/36_summary_in_viewer/02_viewer_summary_panel.md delete mode 100644 .agent/phases/complete/36_summary_in_viewer/03_e2e_and_regression.md delete mode 100644 .agent/phases/complete/37_agent_document_tools/01_probe_tool_calling.md delete mode 100644 .agent/phases/complete/37_agent_document_tools/02_llm_client_tools.md delete mode 100644 .agent/phases/complete/37_agent_document_tools/03_agent_loop.md delete mode 100644 .agent/phases/complete/37_agent_document_tools/04_api_sse_tool_event.md delete mode 100644 .agent/phases/complete/37_agent_document_tools/05_frontend_tool_states.md delete mode 100644 .agent/phases/complete/38_local_directory_sources/01_kind_column.md delete mode 100644 .agent/phases/complete/38_local_directory_sources/02_api_local_kind.md delete mode 100644 .agent/phases/complete/38_local_directory_sources/03_sync_import_local.md delete mode 100644 .agent/phases/complete/38_local_directory_sources/04_admin_page_local.md delete mode 100644 .agent/phases/complete/39_configurable_brand/00_phase.md delete mode 100644 .agent/phases/complete/39_configurable_brand/01_config_endpoint.md delete mode 100644 .agent/phases/complete/39_configurable_brand/02_frontend_branding.md delete mode 100644 .agent/phases/complete/39_configurable_brand/03_e2e_docs_commit.md delete mode 100644 .agent/phases/complete/40_tuning_toggle_flash/01_toggle_ships_hidden.md delete mode 100644 .agent/phases/complete/45_agent_unlimited_tools/01_config_round_cap.md delete mode 100644 .agent/phases/complete/45_agent_unlimited_tools/02_mock_multi_read_flow.md delete mode 100644 .agent/phases/complete/46_mobile_hamburger_nav/00_phase.md delete mode 100644 .agent/phases/complete/46_mobile_hamburger_nav/01_hamburger_markup_all_pages.md delete mode 100644 .agent/phases/complete/46_mobile_hamburger_nav/02_toggle_behavior.md delete mode 100644 .agent/phases/complete/46_mobile_hamburger_nav/03_hamburger_e2e_and_commit.md delete mode 100644 .agent/phases/complete/47_quadlet_jinja_import/01_config_formats.md delete mode 100644 .agent/phases/complete/47_quadlet_jinja_import/02_chunker_dispatch_fixtures.md delete mode 100644 .agent/phases/complete/47_quadlet_jinja_import/03_importer_integration.md delete mode 100644 .agent/phases/complete/48_nav_rename_sources/00_phase.md delete mode 100644 .agent/phases/complete/48_nav_rename_sources/01_nav_label_swap.md delete mode 100644 .agent/phases/complete/48_nav_rename_sources/02_existing_test_labels.md delete mode 100644 .agent/phases/complete/48_nav_rename_sources/03_story_e2e_regression_commit.md delete mode 100644 .agent/phases/complete/49_archive_upload_sources/00_phase.md delete mode 100644 .agent/phases/complete/49_archive_upload_sources/01_settings_and_unpack_utility.md delete mode 100644 .agent/phases/complete/49_archive_upload_sources/02_upload_api.md delete mode 100644 .agent/phases/complete/49_archive_upload_sources/03_admin_page_upload.md delete mode 100644 .agent/phases/complete/49_archive_upload_sources/04_story_e2e_docs_commit.md delete mode 100644 .agent/phases/todo/40_tuning_toggle_flash/00_phase.md delete mode 100644 .agent/phases/todo/40_tuning_toggle_flash/02_flash_e2e_and_regression.md delete mode 100644 .agent/phases/todo/41_sync_fail_fast_models/00_phase.md delete mode 100644 .agent/phases/todo/41_sync_fail_fast_models/01_model_probe_fail_fast.md delete mode 100644 .agent/phases/todo/41_sync_fail_fast_models/02_sync_error_modal.md delete mode 100644 .agent/phases/todo/41_sync_fail_fast_models/03_model_down_e2e_and_commit.md delete mode 100644 .agent/phases/todo/42_no_reply_autoscroll/00_phase.md delete mode 100644 .agent/phases/todo/42_no_reply_autoscroll/01_remove_autofollow.md delete mode 100644 .agent/phases/todo/42_no_reply_autoscroll/02_no_autoscroll_e2e_and_commit.md delete mode 100644 .agent/phases/todo/43_thinking_scroll_back/00_phase.md delete mode 100644 .agent/phases/todo/43_thinking_scroll_back/01_window_user_scrollable.md delete mode 100644 .agent/phases/todo/43_thinking_scroll_back/02_gated_bottom_pin.md delete mode 100644 .agent/phases/todo/43_thinking_scroll_back/03_thinking_scroll_e2e_and_commit.md delete mode 100644 .agent/phases/todo/44_markdown_tables/00_phase.md delete mode 100644 .agent/phases/todo/44_markdown_tables/01_table_renderer_and_styles.md delete mode 100644 .agent/phases/todo/44_markdown_tables/02_mock_table_trigger.md delete mode 100644 .agent/phases/todo/44_markdown_tables/03_tables_e2e_and_commit.md delete mode 100644 .agent/phases/todo/45_agent_unlimited_tools/00_phase.md delete mode 100644 .agent/phases/todo/45_agent_unlimited_tools/03_unlimited_tools_e2e_and_commit.md delete mode 100644 .agent/phases/todo/47_quadlet_jinja_import/00_phase.md delete mode 100644 .agent/phases/todo/47_quadlet_jinja_import/04_quadlet_e2e_and_docs_commit.md delete mode 100644 .agent/reports/21_thinking_no_scroll/phase_report.md delete mode 100644 .agent/reports/22_background_animation/report.md delete mode 100644 .agent/reports/23_containerfile_build/phase_report.md delete mode 100644 .agent/reports/25_background_no_motion/report.md delete mode 100644 .agent/screenshots/22_background_animation/after_frame1.png delete mode 100644 .agent/screenshots/22_background_animation/after_frame2.png delete mode 100644 .agent/screenshots/22_background_animation/after_gridonly_frame1.png delete mode 100644 .agent/screenshots/22_background_animation/after_gridonly_frame2.png delete mode 100644 .agent/screenshots/22_background_animation/before_frame1.png delete mode 100644 .agent/screenshots/22_background_animation/before_frame2.png delete mode 100644 .agent/screenshots/22_background_animation/before_glow_only.png delete mode 100644 .agent/screenshots/22_background_animation/before_grid_only.png delete mode 100644 .agent/screenshots/22_background_animation/before_gridonly_frame1.png delete mode 100644 .agent/screenshots/22_background_animation/before_gridonly_frame2.png delete mode 100644 .agent/screenshots/22_background_animation/before_occlusion_probe.png delete mode 100644 .agent/screenshots/22_background_animation/diagnosis_diff.txt delete mode 100644 .agent/screenshots/22_background_animation/diagnosis_log.txt delete mode 100644 .agent/screenshots/22_background_animation/final_360px.png delete mode 100644 .agent/screenshots/22_background_animation/final_frame1.png delete mode 100644 .agent/screenshots/22_background_animation/final_frame2.png delete mode 100644 .agent/screenshots/25_background_no_motion/after.png delete mode 100644 .agent/screenshots/25_background_no_motion/after_4s.png delete mode 100644 .agent/screenshots/25_background_no_motion/before.png delete mode 100644 .agent/screenshots/25_background_no_motion/before_4s.png delete mode 100644 .agent/user_stories/admin-sync-button.md delete mode 100644 .agent/user_stories/agent-unlimited-tools.md delete mode 100644 .agent/user_stories/archive-upload-sources.md delete mode 100644 .agent/user_stories/background-animation.md delete mode 100644 .agent/user_stories/background-no-motion.md delete mode 100644 .agent/user_stories/cache-busting.md delete mode 100644 .agent/user_stories/containerfile-build.md delete mode 100644 .agent/user_stories/document-modal.md delete mode 100644 .agent/user_stories/document-summaries.md delete mode 100644 .agent/user_stories/git-sources-admin.md delete mode 100644 .agent/user_stories/git-sources.md delete mode 100644 .agent/user_stories/global-tuning.md delete mode 100644 .agent/user_stories/kb-overview-prompt.md delete mode 100644 .agent/user_stories/markdown-tables.md delete mode 100644 .agent/user_stories/mobile-hamburger-nav.md delete mode 100644 .agent/user_stories/nav-consistency.md delete mode 100644 .agent/user_stories/nav-sources-rag-rename.md delete mode 100644 .agent/user_stories/no-reply-autoscroll.md delete mode 100644 .agent/user_stories/quadlet-jinja-import.md delete mode 100644 .agent/user_stories/summary-in-viewer.md delete mode 100644 .agent/user_stories/sync-model-fail-fast.md delete mode 100644 .agent/user_stories/thinking-no-scroll.md delete mode 100644 .agent/user_stories/thinking-scroll-back.md delete mode 100644 .agent/user_stories/tuning-toggle-flash.md delete mode 100644 .agent/user_stories/whole-document-context.md create mode 100644 tests/e2e/test_stop_generation.py create mode 100644 tests/unit/test_chat_cancel.py create mode 100644 tests/unit/test_llm_stream_teardown.py diff --git a/.agent/phases/complete/21_thinking_no_scroll/02_e2e_story_suite_commit.md b/.agent/phases/complete/21_thinking_no_scroll/02_e2e_story_suite_commit.md deleted file mode 100644 index e92d2c7..0000000 --- a/.agent/phases/complete/21_thinking_no_scroll/02_e2e_story_suite_commit.md +++ /dev/null @@ -1,70 +0,0 @@ -# Task 02 — E2E story suite, story file, validation, commit - -**Phase:** `21_thinking_no_scroll` · **Source:** `TODO.md` L4 - -## Objective -The story gate: `tests/e2e/test_thinking_no_scroll.py` proves the window -can't be user-scrolled but always tracks the live tail, plus regressions, -story file, final validation, and the single atomic commit. - -## Work -1. `tests/e2e/test_thinking_no_scroll.py` (new — reuse - `test_thinking_display.py`'s mock-LLM streaming scaffolding; the mock - must stream a **long** thinking body, in many chunks, so - `.thinking-text` overflow exceeds its 320px box). Tests: - 1. `test_thinking_window_not_user_scrollable` — open the block, wait - until `scrollHeight > clientHeight`; focus `.thinking-text` - (`el.focus()`), dispatch mouse wheel over it - (`page.mouse.wheel(0, -200)` after moving the mouse over the - element) and press `Home`/`ArrowUp`: `scrollTop` must not decrease - (assert `scrollTop` unchanged within 1px between actions). - 2. `test_thinking_window_tracks_live_tail` — while chunks stream, - after the 2nd-to-last and last chunk: - `scrollTop === scrollHeight` (within 1px) — the visible window is - the live tail; the **last** chunk's text is within the visible - rectangle (its offsetTop + scrollTop geometry check, or - `elementFromPoint` at the box's bottom). - 3. `test_thinking_window_css_contract` — computed style of - `.thinking-text`: `overflow-y === "hidden"`, - `max-height === "320px"`. - 4. `test_answer_bubble_still_scrollable` (regression, phase 11) — a - long answer (use the long-answer mock from - `test_long_answers.py`): the answer bubble is still - user-scrollable (scrollTop moves on wheel) and - `overflow-y` is not `hidden` there. - 5. `test_restored_collapsed_thinking_unaffected` (regression, - phase 17) — a turn with stored `thinking`, reload: the collapsed - Thinking block renders with its text (existing pin from - `test_thinking_display.py` — replicate, don't duplicate the file). -2. `.agent/user_stories/thinking-no-scroll.md` (new) — story file per - the repo format: goal, the bug report verbatim from `TODO.md` L4, the - owner-confirmed A2 decisions from `00_phase.md`, E2E mapping table. -3. Run the suite **in isolation** (prereq `podman compose up -d db`): - `uv run pytest tests/e2e/test_thinking_no_scroll.py -v --no-cov`. -4. Regressions, in isolation, one command each: - - `uv run pytest tests/e2e/test_thinking_display.py -v --no-cov` - - `uv run pytest tests/e2e/test_long_answers.py -v --no-cov` -5. Final validation: `uv run pytest` green; `uv run pytest --cov=app - --cov-report=term-missing` ≥ today's number (>90% gate); - `uv run ruff check . && uv run pyright` clean. -6. **UI Structure Check** (AGENTS.md rule 5): no new surface; the block - keeps its summary chevron, focus-visible ring, aria-live/label - contract, and the reduced-motion stillness (styles.css ~line 686). -7. Write the phase report (`.agent/reports/21_thinking_no_scroll/`). -8. Commit (one atomic commit) and move the phase: - ```bash - git add -A .agent/ frontend/ tests/ - git commit --no-gpg-sign -m "fix(ui): thinking window no longer scrolls — live 320px view pinned to the stream tail" - mv .agent/phases/todo/21_thinking_no_scroll .agent/phases/complete/ - ``` - -## Testing & Quality -- Story suite green **in isolation**; both regression suites green in - isolation; full unit+integration suite green; `app/` coverage at or - above today's number (>90%); ruff + pyright clean. - -## Completion Criteria -- [ ] `test_thinking_no_scroll.py` 5/5 in isolation. -- [ ] Regressions (thinking display, long answers) green in isolation. -- [ ] Story file + phase report exist. -- [ ] One `--no-gpg-sign` commit; phase directory in `complete/`. diff --git a/.agent/phases/complete/22_background_animation/01_fix_background_animation.md b/.agent/phases/complete/22_background_animation/01_fix_background_animation.md deleted file mode 100644 index 22b9e41..0000000 --- a/.agent/phases/complete/22_background_animation/01_fix_background_animation.md +++ /dev/null @@ -1,60 +0,0 @@ -# Task 01 — Diagnose and fix the animated background (styles.css) - -**Phase:** `22_background_animation` · **Source:** `TODO.md` L5 — -*"Fix background animation not working, just blinking"* - -## Objective -Find why the phase-08 animated background reads as "just blinking" and -fix `styles.css` so the grid drift and the glow breathe are both visibly -and smoothly alive, per the phase-08 design comments (pure CSS, zero JS). - -## Work -1. **Reproduce:** `uv run uvicorn app.main:app --reload` (db up), open `/` - in a visible Chromium window (Playwright or the interactive browser), - observe ≥15s. Note exactly what moves and what doesn't. -2. **Diagnose per the `00_phase.md` checklist** (per-layer visibility - toggles, `background-position` samples on `body::before`, mask - inspection, occlusion check against `html`/`body` rules, glow - opacity-swing perception). Record findings + before-screenshot in - `.agent/reports/22_background_animation/` and - `.agent/screenshots/22_background_animation/`. - **ASSUMPTION (to verify, not assume):** the likely culprits, in - order — (a) the masked grid drift is too faint/slow to perceive, - (b) only the glow opacity swing is visible and it reads as a blink, - (c) a later rule occludes the `z-index: -1` layers. Confirm which - one actually fires before touching CSS; the fix must match the found - cause. -3. **Fix in `frontend/assets/styles.css`** (smallest change that makes - the design read): - - grid: raise line alpha and/or the mask's visible radius and/or the - drift speed as needed for a clearly visible, seamless drift - (drift delta must still equal one 44px cell for a seamless loop — - if the speed changes, keep `background-position` 0→44px and only - move the duration); - - glow: if the breathe reads as a blink, narrow the opacity delta - (e.g. 0.8↔1) and/or lengthen the period — it must read as - breathing, not pulsing; - - keep: both layers `position: fixed; inset: 0; z-index: -1; - pointer-events: none`; no `filter: blur`; no JS; palette/contrast - untouched. -4. **After-screenshot** (same viewport, two frames a few seconds apart - showing motion) into the same screenshots dir. -5. `tests/unit/test_background_animation.py` (new — repo source-pin - pattern): pin the **final** `styles.css` values — both - `@keyframes` present, `body::before` → `bg-grid-drift linear - infinite`, `body::after` → `bg-glow-breathe`, both layers - `fixed`/`z-index: -1`/`pointer-events: none`, `html` keeps - `background: var(--bg)`, `body` keeps `background: transparent`. -6. Manual re-verify: the "just blinking" perception is gone — smooth - drift + gentle breathe, no jank, no static frame. - -## Testing & Quality -- `uv run pytest tests/unit/test_background_animation.py -v` green. -- `uv run ruff check . && uv run pyright` clean. - -## Completion Criteria -- [ ] Root cause documented (with before/after screenshots) in the - phase report dir. -- [ ] Both layers visibly animate as the phase-08 design describes; - pure CSS, zero JS, no blur. -- [ ] Unit pins green against the final values; lint/types clean. diff --git a/.agent/phases/complete/23_containerfile_build/00_phase.md b/.agent/phases/complete/23_containerfile_build/00_phase.md deleted file mode 100644 index 3160eec..0000000 --- a/.agent/phases/complete/23_containerfile_build/00_phase.md +++ /dev/null @@ -1,120 +0,0 @@ -# Phase 23 — Containerfile: Build the Whole App Image Again - -**Source:** `TODO.md` L6 — *"Fix Containerfile build not working"* -**Story:** `.agent/user_stories/containerfile-build.md` (created by task 02) -**Context:** `Containerfile` (3 stages: node:22-alpine + esbuild -0.25.5 frontend bundle → uv/python deps → slim runtime serving -`/app/static`); `frontend/` (4 pages: `index.html`, `sources.html`, -`document.html`, `login.html`; assets: `styles.css`, `markdown.js` -(classic script), `header.js`/`app.js`/`sources.js`/`document.js`/ -`login.js` (ES modules)); `scripts/entrypoint.sh`. - -## Verified diagnosis (2026-08-24, this conversion — not a guess) -1. **Root cause of the build failure:** phase 19 switched the page - scripts to `import … from "/assets/header.js"` (an absolute URL). - esbuild resolves that as the *filesystem* path `/assets/header.js` - and the stage-1 bundle dies: - `✘ [ERROR] Could not resolve "/assets/header.js"` - (reproduced with esbuild **0.25.5**, the exact pinned version, on a - copy of `frontend/`). -2. **Secondary gap (image would be broken even if it built):** stage 1 - bundles only `app.js` + `sources.js` and copies only `index.html` + - `sources.html`. Missing from the image: `document.html` + - `login.html` (phases 10/16), `document.js` + `login.js`, and - `markdown.js` (classic script loaded by `index.html` + - `document.html`). -3. **Verified fix:** with relative imports (`from "./header.js"`) all - four page scripts bundle cleanly with esbuild 0.25.5. -4. **Latent double-evaluation trap:** all four HTML pages also load - ``; - - `frontend/sources.html` (~line 125), `frontend/document.html` - (~line 80), `frontend/login.html` (~line 67) — same tag. - - Update the surrounding HTML comments that describe the - header-before-page-script load order (e.g. index.html ~lines - 115–119): the order is now guaranteed by the page script's own - `import` (hoisted, evaluated before the page script body calls - `initSharedHeader()`). -4. **`Containerfile` stage 1** — cover the whole app (keep the pinned - `esbuild@0.25.5` and the existing flags): - ```dockerfile - RUN mkdir -p /out/assets \ - && esbuild ./assets/app.js --bundle --minify --format=esm --target=es2022 --outfile=/out/assets/app.js \ - && esbuild ./assets/sources.js --bundle --minify --format=esm --target=es2022 --outfile=/out/assets/sources.js \ - && esbuild ./assets/document.js --bundle --minify --format=esm --target=es2022 --outfile=/out/assets/document.js \ - && esbuild ./assets/login.js --bundle --minify --format=esm --target=es2022 --outfile=/out/assets/login.js \ - && esbuild ./assets/markdown.js --minify --outfile=/out/assets/markdown.js \ - && esbuild ./assets/styles.css --minify --outfile=/out/assets/styles.css \ - && cp ./index.html ./sources.html ./document.html ./login.html /out/ - ``` - (`markdown.js` is a classic script — minify only, **no** `--bundle`; - it exposes globals used by the pages.) -5. **Verify locally (no podman):** with esbuild 0.25.5, all four module - bundles + the markdown minify succeed on the real `frontend/` (not a - copy — the copy was only for the diagnosis). -6. **`podman build -f Containerfile .`** → green. -7. **Image smoke test** (results + log excerpt into the report dir): - - throwaway Postgres 17 + pgvector (`podman compose up -d db` and - point the container at it, or a one-off container with the same - env as `compose.yaml`); - - run the built image (migrations run via the entrypoint); - - `GET /`, `/sources.html`, `/document.html`, `/login.html` → 200; - - `GET /assets/app.js` → 200, minified (single-line-ish), and - contains the header code (e.g. the `clearChatStorage` function - body); `GET /assets/markdown.js`, `/styles.css`, the other three - page modules → 200; - - No CDN rule: none of the four served pages contain an `http(s)://` - `src`/`href` asset reference. - - Teardown the throwaway containers when done. -8. **Dev-server regression check** (the tag removal touches dev page - load — confirm boot order still holds): `uv run uvicorn - app.main:app --reload`, load all four pages, check the sign-out - binding exists exactly once (DevTools: no duplicate listener — one - `POST /api/logout` per click) and `initSharedHeader()` ran. (The - isolated E2E regressions run in task 02.) - -## Testing & Quality -- Steps 5–8 above; `uv run ruff check . && uv run pyright` clean - (no Python changes, but keep the gate green). - -## Completion Criteria -- [ ] The recorded build failure is fixed at the root cause (relative - imports) — not masked by an alias/patch. -- [ ] All four direct `header.js` tags removed + comments updated; the - page scripts' `import "./header.js"` is the only header load. -- [ ] Stage 1 produces: 4 HTML pages, 4 bundled modules, minified - `markdown.js`, minified `styles.css`. -- [ ] `podman build` green; image smoke all-200 + No CDN + single - header evaluation; dev-server boot unchanged (step 8). -- [ ] Log/screenshot evidence in `.agent/reports/23_containerfile_build/`. diff --git a/.agent/phases/complete/23_containerfile_build/02_integration_test_commit.md b/.agent/phases/complete/23_containerfile_build/02_integration_test_commit.md deleted file mode 100644 index 812a8a8..0000000 --- a/.agent/phases/complete/23_containerfile_build/02_integration_test_commit.md +++ /dev/null @@ -1,70 +0,0 @@ -# Task 02 — Integration coverage test, story file, validation, commit - -**Phase:** `23_containerfile_build` · **Source:** `TODO.md` L6 - -## Objective -Pin the stage-1 asset coverage so it can't silently rot again (a new -page/script/asset without a matching Containerfile line fails CI), plus -regressions, story file, final validation, and the single atomic commit. - -## Work -1. `tests/integration/test_containerfile_assets.py` (new — **hermetic**: - parses `Containerfile` + `frontend/` as text, no podman, no network). - Tests: - 1. `test_every_html_page_is_copied_into_stage1` — for each - `frontend/*.html` in the repo, a stage-1 line copies it into - `/out` (regex over the `cp` line; the set must be exactly the - four current pages — a new page added to `frontend/` fails this). - 2. `test_every_local_asset_reference_is_produced` — collect every - local `src=`/`href=` under `assets/` or `/assets/` from the four - HTML files; each basename must be produced by a stage-1 line - (an `esbuild … --outfile=/out/assets/` or a `cp` of it). - (This is what catches a missing `markdown.js`-style gap.) - 3. `test_page_module_scripts_are_bundled` — the set of `type="module"` - page scripts referenced by the HTML (basenames) equals the set of - scripts esbuild bundles in stage 1 (`app.js`, `sources.js`, - `document.js`, `login.js`). - 4. `test_header_module_is_imported_not_directly_loaded` — no HTML - file contains a `` alongside the existing `app.js` module script). `index.html` already loads `markdown.js` as a classic script (needed by `renderDocument`). -4. `frontend/sources.html` — load `document-modal.js` as a module (it needs `document.js`'s `renderDocument`, so both `document.js` and `document-modal.js` must be module scripts; `markdown.js` classic script stays). The Sources page currently loads `sources.js` as a module; add the modal module script next to it. -5. Verify the no-CDN integration test (`tests/integration/test_api.py::test_index_html_served_locally`) still passes — the new module scripts are same-origin `` (no leading slash!), and `` **before** the page's module script on every page (classic script → runs at parse time; the module scripts execute later). No other template changes — the walker + attribute pass is the single mechanism; do **not** add `data-brand` markers. The phase-33 `?v=` rewriting picks the new ref up automatically (`app/core/caching.py` matches any `src="…assets/…"`). -3. `frontend/assets/app.js` — replace the "Brain of Reese" literals with `window.BOR_BRAND` reads: the `UI_STATE` labels (~L107: "… is thinking" / "… is answering"), `TYPING_LABEL` (~L112), the elapsed-hint aria-label (~L540). Pattern: `const brand = () => window.BOR_BRAND || "Brain of Reese";` + template strings. (Mid-turn staleness: a label set before the fetch lands keeps the old name for that turn — accepted, see the phase's locked decisions.) -4. `frontend/assets/document.js` — the page titles (L147/152: `${doc.title} · Brain of Reese` / `"Document not found · …"`) → use the same `window.BOR_BRAND` read (document.js is a module — `window.BOR_BRAND` is set by then). -5. `Containerfile` — add the esbuild line for the new file next to the others (L17–23 pattern, classic script like `markdown.js`): `esbuild ./assets/brand.js --minify --outfile=/out/assets/brand.js`. -6. Verify the no-op property: with the default settings the rendered DOM text is byte-identical to pre-phase on all five pages (the replace is a no-op for the default name) — the existing suites' title/label assertions are the guard; if any assert a string this task moved onto `window.BOR_BRAND`, the default path must render the identical bytes. - -## Testing & Quality -- No Python logic — the story E2E (task 03) is the gate; the existing suites (which assert the default "Brain of Reese" titles/labels against the shared conftest server) must stay green **unchanged**. -- No CDN (rule 6): no new external tags. UI Structure Check (rule 5): no landmark/contrast change — the brand text keeps its existing classes and styling (the `innerHTML` rewrite only re-emits the same structure with the new name). - -## Completion Criteria -- [ ] `BOR_APP_NAME` unset → all five pages render exactly as today (existing suites green). -- [ ] `BOR_APP_NAME="Brain of Testy"` → title/header/greeting/labels/aria all carry the new name (asserted by the story E2E, task 03). -- [ ] The Containerfile build includes brand.js; the asset ref is versioned like its siblings (phase 33). -- [ ] `uv run pytest` green; `uv run ruff check . && uv run pyright` clean. diff --git a/.agent/phases/complete/39_configurable_brand/03_e2e_docs_commit.md b/.agent/phases/complete/39_configurable_brand/03_e2e_docs_commit.md deleted file mode 100644 index 25f7e3e..0000000 --- a/.agent/phases/complete/39_configurable_brand/03_e2e_docs_commit.md +++ /dev/null @@ -1,27 +0,0 @@ -# Task 03 — Story E2E + docs + commit - -**Phase:** `39_configurable_brand` · **Source:** `TODO.md:12 — "Also need a way to customize the name for 'Brain of'. Should be an env var."` -**Story:** `.agent/user_stories/configurable-brand.md` - -## Objective -The story's isolated Playwright suite against an app instance booted with the overridden name, the env docs, and the phase commit. - -## Work -1. `tests/e2e/test_configurable_brand.py` (the story gate — one story, one file, run in isolation): - - A **second app instance** — the shared `app_server` conftest fixture keeps the default name (the other suites' title/label assertions depend on it). Copy the conftest `app_server` env block (same DB, the mock-LLM base URL, `BOR_ADMIN_PASSWORD`/`BOR_SESSION_SECRET`, `BOR_STATIC_DIR`, `BOR_RELEVANCE_THRESHOLD`) with two changes: `BOR_APP_NAME="Brain of Testy"` and a distinct port (`APP_PORT + 1` per the conftest convention). A session-scoped fixture **inside the test file**, started after `mock_llm` is available. - - Assertions (custom instance): index `document.title` == `"Brain of Testy"`; the `.brand-text` `innerHTML` == `Brain of Testy`; the empty-state h1 text == `"Hey! I'm Brain of Testy."`; the `#messages` `aria-label` == `"Conversation with Brain of Testy"`; the sources page title `"Sources · Brain of Testy"`; the login page title `"Sign in · Brain of Testy"`; one chat turn with a pre-token window (the `think out loud` marker) → the button label shows `"Brain of Testy is thinking"`. - - Default-name assertion (cheap regression in the same file): the shared conftest server's index title still == `"Brain of Reese"`. - - The chat-turn assertion works on the default (possibly empty) KB — a deflected answer is fine; the label assertion is pre-token, so no DB seeding is required. -2. `.env.example` — document `BOR_APP_NAME` in the App section (the display name on all pages; default "Brain of Reese"). -3. README — the configuration section: `BOR_APP_NAME` (what it affects: titles, the header brand, the status labels, the aria text; the default; the bold-split rendering rule: names starting "Brain of " bold the remainder, any other name renders in normal weight). -4. Regression pass: `uv run pytest` + the coverage gate (>90%) + the isolated story E2E + the suites that assert brand strings (`test_smoke.py`, `test_shared_header.py`, `test_header_consistency.py`, `test_chat_persistence.py`) green. -5. Commit — one atomic `--no-gpg-sign` Conventional Commits commit for the whole phase (AGENTS.md rule 8), e.g. `feat(brand): configurable app name — BOR_APP_NAME drives /api/config + the frontend brand layer`; move the phase directory to `.agent/phases/complete/`. - -## Testing & Quality -- The gates above are this task's quality bar (A16: one story, one isolated E2E file, coverage >90%). - -## Completion Criteria -- [ ] The story E2E is green in isolation, deterministic across two consecutive runs (custom-name instance + the default-name assertion). -- [ ] The step-4 regression list green; coverage >90%. -- [ ] `uv run ruff check . && uv run pyright` clean. -- [ ] One `--no-gpg-sign` commit; phase directory moved to `.agent/phases/complete/`. diff --git a/.agent/phases/complete/40_tuning_toggle_flash/01_toggle_ships_hidden.md b/.agent/phases/complete/40_tuning_toggle_flash/01_toggle_ships_hidden.md deleted file mode 100644 index 2b1fdc7..0000000 --- a/.agent/phases/complete/40_tuning_toggle_flash/01_toggle_ships_hidden.md +++ /dev/null @@ -1,26 +0,0 @@ -# Task 01 — Toggle ships hidden, admin-only reveal - -**Phase:** `40_tuning_toggle_flash` · **Source:** `TODO.md:3` — "Loading the page briefly shows the 'Tuning' button in the header even when the user isn't authenticated. Only show that if the user is authenticated." -**Story:** `.agent/user_stories/tuning-toggle-flash.md` - -## Objective -Ship `#steering-toggle` `hidden` in all six pages and unhide it in `header.js` only when whoami says admin — zero flash for anonymous, identical admin UX. - -## Work -1. `frontend/index.html`, `frontend/sources.html`, `frontend/document.html`, `frontend/git-sources.html`, `frontend/login.html`, `frontend/tuning.html` — add the `hidden` attribute to the existing `#steering-toggle` ` - ``` - - give the existing nav the id: `