diff --git a/application/single_app/config.py b/application/single_app/config.py index 7196cfe8..3ccb6ca9 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -94,7 +94,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.241.006" +VERSION = "0.241.007" SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production') diff --git a/application/single_app/templates/group_workspaces.html b/application/single_app/templates/group_workspaces.html index 38fccf61..91fd3f54 100644 --- a/application/single_app/templates/group_workspaces.html +++ b/application/single_app/templates/group_workspaces.html @@ -324,25 +324,27 @@

Group Workspace

Group Actions + {% endif %} + {% endif %} + {% if settings.enable_semantic_kernel and settings.allow_group_custom_endpoints and settings.enable_multi_model_endpoints %} + {% endif %} - {% endif %} - {% if settings.enable_semantic_kernel and settings.allow_group_custom_endpoints and settings.enable_multi_model_endpoints %} - {% endif %} +
@@ -818,7 +820,6 @@

Group Workspace

- {% endif %} {% if settings.enable_semantic_kernel and settings.allow_group_plugins %} @@ -4115,6 +4116,10 @@
// — Fetch & render group prompts — function fetchGroupPrompts() { + if (!groupPromptsTableBody || !groupPromptsPagination) { + return; + } + groupPromptsTableBody.innerHTML = ` diff --git a/docs/explanation/fixes/GROUP_PROMPTS_ROLE_UI_NULL_GUARD_FIX.md b/docs/explanation/fixes/GROUP_PROMPTS_ROLE_UI_NULL_GUARD_FIX.md new file mode 100644 index 00000000..f0a8019e --- /dev/null +++ b/docs/explanation/fixes/GROUP_PROMPTS_ROLE_UI_NULL_GUARD_FIX.md @@ -0,0 +1,46 @@ +# Group Prompts Role UI Null Guard Fix + +Fixed/Implemented in version: **0.241.003** + +## Header Information + +Issue description: +The group workspace page could throw a client-side exception while refreshing active group state: `Cannot read properties of null (reading 'style')` from `updateGroupPromptsRoleUI()`. + +Root cause analysis: +The prompt role-toggle logic assumed `create-group-prompt-section` and `group-prompts-role-warning` always existed in the DOM and wrote directly to `.style.display` without checking for missing nodes. + +Version implemented: +`config.py` was updated to `VERSION = "0.241.003"` for this fix. + +## Technical Details + +Files modified: +- `application/single_app/templates/group_workspaces.html` +- `application/single_app/config.py` +- `ui_tests/test_group_workspace_prompt_role_ui_resilience.py` + +Code changes summary: +- Switched the prompt role warning and create button containers to Bootstrap `d-none` state classes instead of inline `display: none` styles. +- Updated `updateGroupPromptsRoleUI()` to tolerate missing prompt DOM nodes and toggle visibility via `classList` instead of unsafe `.style` access. +- Added an early return in `fetchGroupPrompts()` when the prompt table container is unavailable. +- Added a UI regression test that removes the prompt role UI containers before changing the active group and asserts that no page error is raised. + +Testing approach: +- Added a Playwright UI regression test covering group changes with missing prompt role containers. +- Targeted validation should include the new UI test file plus a syntax/error pass on the updated template and config version bump. + +Impact analysis: +The group workspace now keeps loading and switching groups even when prompt role UI fragments are omitted, customized, or temporarily unavailable. + +## Validation + +Test results: +The regression test is designed to fail on the old `.style` access and pass once the null-safe toggle logic is present. + +Before/after comparison: +- Before: group changes could throw an uncaught promise error from `updateGroupPromptsRoleUI()`. +- After: group changes skip missing prompt role nodes safely and continue updating the rest of the workspace. + +User experience improvements: +Users no longer see the prompt role UI exception interrupt the group workspace load flow when those prompt elements are missing. \ No newline at end of file diff --git a/ui_tests/test_group_workspace_prompt_role_ui_resilience.py b/ui_tests/test_group_workspace_prompt_role_ui_resilience.py new file mode 100644 index 00000000..a7c0151f --- /dev/null +++ b/ui_tests/test_group_workspace_prompt_role_ui_resilience.py @@ -0,0 +1,175 @@ +# test_group_workspace_prompt_role_ui_resilience.py +""" +UI test for group workspace prompt role UI resilience. +Version: 0.241.003 +Implemented in: 0.241.003 + +This test ensures the group workspace can refresh active group context without +raising client-side errors when the prompt role warning and create button +containers are absent from the DOM. +""" + +import json +import os +from pathlib import Path + +import pytest + + +BASE_URL = os.getenv("SIMPLECHAT_UI_BASE_URL", "").rstrip("/") +STORAGE_STATE = os.getenv("SIMPLECHAT_UI_STORAGE_STATE", "") + + +def _require_ui_env(): + if not BASE_URL: + pytest.skip("Set SIMPLECHAT_UI_BASE_URL to run this UI test.") + if not STORAGE_STATE or not Path(STORAGE_STATE).exists(): + pytest.skip( + "Set SIMPLECHAT_UI_STORAGE_STATE to a valid authenticated Playwright storage state file." + ) + + +def _fulfill_json(route, payload, status=200): + route.fulfill( + status=status, + content_type="application/json", + body=json.dumps(payload), + ) + + +@pytest.mark.ui +def test_group_workspace_group_change_tolerates_missing_prompt_role_elements(playwright): + """Validate that a group change does not raise prompt role UI null errors.""" + _require_ui_env() + + browser = playwright.chromium.launch() + context = browser.new_context( + storage_state=STORAGE_STATE, + viewport={"width": 1440, "height": 900}, + ) + page = context.new_page() + + page_errors = [] + page.on("pageerror", lambda error: page_errors.append(str(error))) + + group_payloads = [ + { + "groups": [ + { + "id": "group-alpha", + "name": "Alpha Team", + "isActive": True, + "userRole": "Owner", + "status": "active", + }, + { + "id": "group-beta", + "name": "Beta Team", + "isActive": False, + "userRole": "Admin", + "status": "active", + }, + ] + }, + { + "groups": [ + { + "id": "group-alpha", + "name": "Alpha Team", + "isActive": False, + "userRole": "Owner", + "status": "active", + }, + { + "id": "group-beta", + "name": "Beta Team", + "isActive": True, + "userRole": "Admin", + "status": "active", + }, + ] + }, + ] + + def handle_groups(route): + payload = group_payloads[0] + if len(group_payloads) > 1: + payload = group_payloads.pop(0) + _fulfill_json(route, payload) + + page.route("**/api/groups?page_size=1000", handle_groups) + page.route( + "**/api/groups/setActive", + lambda route: _fulfill_json(route, {"success": True}), + ) + page.route( + "**/api/group_documents?*", + lambda route: _fulfill_json( + route, + { + "documents": [], + "page": 1, + "page_size": 10, + "total_count": 0, + }, + ), + ) + page.route( + "**/api/group_documents/tags?*", + lambda route: _fulfill_json(route, {"tags": []}), + ) + + try: + response = page.goto(f"{BASE_URL}/group_workspaces", wait_until="networkidle") + + assert response is not None, "Expected a navigation response when loading /group_workspaces." + assert response.ok, f"Expected /group_workspaces to load successfully, got HTTP {response.status}." + + page.wait_for_function( + """ + () => { + const tbody = document.querySelector('#group-documents-table tbody'); + return tbody && tbody.textContent.includes('No documents found in this group.'); + } + """ + ) + + page.evaluate( + """ + () => { + document.getElementById('create-group-prompt-section')?.remove(); + document.getElementById('group-prompts-role-warning')?.remove(); + + const select = document.getElementById('group-select'); + if (select) { + select.value = 'group-beta'; + } + + const selectedText = document.querySelector('#group-dropdown-button .selected-group-text'); + if (selectedText) { + selectedText.textContent = 'Beta Team'; + } + } + """ + ) + + page.locator("#btn-change-group").click() + page.wait_for_function( + """ + () => { + const role = document.getElementById('user-role'); + return role && role.textContent.trim() === 'Admin'; + } + """ + ) + + null_style_errors = [ + error + for error in page_errors + if "Cannot read properties of null (reading 'style')" in error + ] + + assert not null_style_errors, f"Unexpected prompt role UI errors: {null_style_errors}" + finally: + context.close() + browser.close() \ No newline at end of file