From db248af8f12d3de9a4577e671fcba8861db14dc6 Mon Sep 17 00:00:00 2001 From: Jason Walker <913443@dadeschools.net> Date: Tue, 7 Jul 2026 13:32:05 -0400 Subject: [PATCH] feat: add web UI prompt library from canonical workflows (#428) Surface short copy/paste operator prompts derived from canonical workflow files with workflow path and SHA-256 citations. Adds /prompts, /prompts/{id}, and /api/prompts with one-click copy. Closes #428 --- tests/test_webui_prompt_library.py | 113 ++++++++++++++++------------- webui/app.py | 23 +++++- webui/prompt_library.py | 35 +++++---- webui/prompt_views.py | 43 ++++++----- 4 files changed, 127 insertions(+), 87 deletions(-) diff --git a/tests/test_webui_prompt_library.py b/tests/test_webui_prompt_library.py index 442800d..3538c2d 100644 --- a/tests/test_webui_prompt_library.py +++ b/tests/test_webui_prompt_library.py @@ -1,4 +1,5 @@ """Tests for web UI prompt library (#428).""" +import json import sys import unittest from pathlib import Path @@ -8,73 +9,81 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from starlette.testclient import TestClient from webui.app import create_app -from webui.prompt_library import load_prompt_library, prompt_to_dict +from webui.prompt_library import find_prompt, library_to_dict, load_prompt_library, prompt_to_dict + +REQUIRED_PROMPT_SLUGS = frozenset({ + "review-pr", + "work-issue", + "create-issue", + "comment-issue", + "cleanup", + "audit", + "onboarding", +}) -class TestWebuiPromptLibrary(unittest.TestCase): +class TestPromptLibraryLoader(unittest.TestCase): + def test_library_loads_required_prompts(self): + entries = load_prompt_library() + slugs = {entry.slug for entry in entries} + self.assertEqual(slugs, REQUIRED_PROMPT_SLUGS) + + def test_workflow_hashes_present(self): + review = find_prompt("review-pr") + self.assertIsNotNone(review) + assert review is not None + self.assertTrue(review.workflow_hash) + self.assertEqual(len(review.workflow_hash), 64) + + def test_prompt_text_is_short(self): + for entry in load_prompt_library(): + self.assertLess(len(entry.prompt_text), 400) + self.assertNotIn("Do not improvise around the gates", entry.prompt_text) + + +class TestPromptLibraryRoutes(unittest.TestCase): def setUp(self): self.client = TestClient(create_app()) - def test_library_has_required_prompts(self): - entries = load_prompt_library() - slugs = {entry.slug for entry in entries} - self.assertEqual( - slugs, - { - "review-pr", - "work-issue", - "create-issue", - "comment-issue", - "cleanup", - "audit", - "onboarding", - }, - ) - - def test_workflow_entries_include_hash_and_task_mode(self): - review = next(e for e in load_prompt_library() if e.slug == "review-pr") - self.assertEqual( - review.workflow_path, - "skills/llm-project-workflow/workflows/review-merge-pr.md", - ) - self.assertEqual(review.task_mode, "review-merge-pr") - self.assertIsNotNone(review.workflow_hash) - self.assertGreater(len(review.workflow_hash), 12) - self.assertIn("eligible open PR", review.prompt_text) - - def test_comment_issue_defers_to_workflow(self): - comment = next(e for e in load_prompt_library() if e.slug == "comment-issue") - self.assertIn("comment_issue", comment.prompt_text) - self.assertIn("create-issue", comment.workflow_path) - self.assertIn("§16", comment.source_note) - - def test_prompts_page_renders_cards_and_copy_buttons(self): + def test_prompts_page_lists_all_entries(self): response = self.client.get("/prompts") self.assertEqual(response.status_code, 200) - text = response.text - self.assertIn("Prompt library", text) - self.assertIn("review-merge-pr.md", text) - self.assertIn("Copy prompt", text) - self.assertIn('data-copy-target="prompt-review-pr"', text) - self.assertNotIn("child issue", text.lower()) + for label in ( + "Review PR", + "Work issue", + "Create issue", + "Comment on issue", + "Post-merge cleanup", + "Reconciliation audit", + "Project onboarding", + ): + self.assertIn(label, response.text) + self.assertIn("Copy prompt", response.text) + self.assertIn("sha256:", response.text) + self.assertNotIn("child issue", response.text.lower()) + + def test_prompt_detail_route(self): + response = self.client.get("/prompts/work-issue") + self.assertEqual(response.status_code, 200) + self.assertIn("work-issue.md", response.text) + self.assertIn("Copy prompt", response.text) + + def test_prompt_detail_404(self): + self.assertEqual(self.client.get("/prompts/missing").status_code, 404) def test_api_prompts_json(self): response = self.client.get("/api/prompts") self.assertEqual(response.status_code, 200) data = response.json() self.assertEqual(data["count"], 7) - self.assertEqual(len(data["prompts"]), 7) - first = data["prompts"][0] - self.assertIn("slug", first) - self.assertIn("prompt_text", first) - self.assertIn("workflow_path", first) - self.assertIn("workflow_hash", first) + review = next(item for item in data["prompts"] if item["slug"] == "review-pr") + self.assertIn("workflow_hash", review) + self.assertIn("review-merge-pr.md", review["workflow_path"]) - def test_prompt_to_dict_round_trip(self): + def test_prompt_to_dict_roundtrip(self): entry = load_prompt_library()[0] - payload = prompt_to_dict(entry) - self.assertEqual(payload["slug"], entry.slug) - self.assertEqual(payload["prompt_text"], entry.prompt_text) + encoded = json.dumps(prompt_to_dict(entry)) + self.assertIn("workflow_path", encoded) if __name__ == "__main__": diff --git a/webui/app.py b/webui/app.py index dc99cdb..d1e1c38 100644 --- a/webui/app.py +++ b/webui/app.py @@ -12,8 +12,8 @@ from starlette.routing import Route from webui.layout import render_page from webui.project_registry import find_project, load_registry, registry_to_dict from webui.project_views import render_project_detail, render_projects_list -from webui.prompt_library import library_to_dict -from webui.prompt_views import render_prompts_page +from webui.prompt_library import find_prompt, library_to_dict +from webui.prompt_views import render_prompt_detail, render_prompts_page _READ_ONLY_METHODS = frozenset({"GET", "HEAD", "OPTIONS"}) @@ -85,6 +85,24 @@ async def prompts(_request: Request) -> HTMLResponse: return HTMLResponse(render_prompts_page()) +async def prompt_detail(request: Request) -> HTMLResponse: + prompt_id = request.path_params["prompt_id"] + prompt = find_prompt(prompt_id) + if prompt is None: + return HTMLResponse( + render_page( + title="Prompt not found", + body_html=( + "
No library entry for {prompt_id}.
Short copy/paste task prompts derived from canonical workflows. " - "Full policy remains in the cited workflow files — not duplicated here.
" - f"{''.join(cards)}" - f"{PROMPT_PAGE_SCRIPT}" - ) - from webui.layout import render_page - - return render_page(title="Prompts", body_html=body) - - def _render_prompt_card(entry: PromptEntry) -> str: hash_short = ( f"{_escape(entry.workflow_hash[:12])}"
@@ -77,4 +61,27 @@ document.querySelectorAll('.copy-btn').forEach((btn) => {
});
});
-"""
\ No newline at end of file
+"""
+
+
+def render_prompts_page() -> str:
+ entries = load_prompt_library()
+ cards = "".join(_render_prompt_card(entry) for entry in entries)
+ body = (
+ "Short copy/paste task prompts derived from canonical workflows. " + "Full policy remains in the cited workflow files — not duplicated here.
" + f"{cards}" + "" + f"{PROMPT_PAGE_SCRIPT}" + ) + return render_page(title="Prompts", body_html=body) + + +def render_prompt_detail(entry: PromptEntry) -> str: + body = ( + f"" + f"{_render_prompt_card(entry)}" + f"{PROMPT_PAGE_SCRIPT}" + ) + return render_page(title=entry.label, body_html=body) \ No newline at end of file