diff --git a/AGENTS.md b/AGENTS.md index 89d7366..24fc502 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -195,3 +195,16 @@ bin/pyauto-pulse fetch [--instance K] # ingest + receipt only ``` Before a PR: `ruff check . && ruff format --check . && pytest -q tests && bin/pyauto-pulse check --offline`. + + +## Shared standards + +Before changing a shared interface, consult the applicable +[organism standard](https://github.com/PyAutoLabs/PyAutoBrain/blob/main/docs/standards.md) +on demand, identify affected consumers, and validate their adoption. Change +generated guidance at its canonical source and regenerate. + +For board changes, follow the applicable sizing, navigation and orchestration +standards and reuse Brain’s shared components. Keep domain data, prompt meaning +and approval boundaries with the board’s owner. + diff --git a/pulse/board.py b/pulse/board.py index cbea3a5..07c0a72 100644 --- a/pulse/board.py +++ b/pulse/board.py @@ -571,10 +571,16 @@ def render_html(views, now: str | None = None, campaign_data: dict | None = None ], ) + "
" - + campaigns.render_html(campaign_data if campaign_data is not None else campaigns.load()) + + campaigns.render_html( + campaign_data if campaign_data is not None else campaigns.load(), + work_links=[ + {"label": "PyAutoPulse", "href": REPO_URL}, + *({"label": v.instance.repo, "href": v.instance.github_url} for v in views), + ], + ) + "".join(content) + "
" - + f"\n" + + f"\n" ) diff --git a/pulse/campaigns.py b/pulse/campaigns.py index 195330a..e0606ea 100644 --- a/pulse/campaigns.py +++ b/pulse/campaigns.py @@ -143,13 +143,13 @@ def markdown(data: dict) -> str: return "\n".join(rows) -def render_html(data: dict) -> str: +def render_html(data: dict, work_links=()) -> str: def e(value): return html.escape(str(value), quote=True) def action(key, title, prompt, review=""): return ( - f'
{review}' + f'
{review}' f'
Full prompt' f'
' ) @@ -158,10 +158,18 @@ def action(key, title, prompt, review=""): review = f'Last check-in: {e(reviewed[:10] if data.get("last_checkin") else reviewed)} · review date' parts = [ marker(data), - '
' - + theme().prompt_heading("pulse"), + theme().orchestration_panel( + "pulse", + "", + "", + PROMPT, + work_links=work_links, + copy_label="Profiling Check In", + organ="pulse", + ), + '
', action("fix", "Fix Profiling Systematically", FIX_PROMPT), - action("checkin", "Profiling Check In", PROMPT, review), + review, '
', '

Active campaigns

', ] diff --git a/tests/browser_setup.cjs b/tests/browser_setup.cjs index 8d79947..4634ed6 100644 --- a/tests/browser_setup.cjs +++ b/tests/browser_setup.cjs @@ -92,14 +92,14 @@ const { execFileSync } = require("node:child_process"); configurable: true, }), ); + // The domain fix prompt keeps its own editable field and manual fallback. + await page.locator("#fix-details").evaluate((el) => (el.open = true)); + await page.locator("#fix-prompt").fill("Edited fix prompt"); await page - .locator("#checkin-details > summary") - .click() - .catch(() => {}); - await page.locator("#checkin-details").evaluate((el) => (el.open = true)); - await page.locator("#checkin-prompt").fill("Edited check-in direction"); - await page - .getByRole("button", { name: "Profiling Check In", exact: true }) + .getByRole("button", { + name: "Fix Profiling Systematically", + exact: true, + }) .click(); await page.waitForFunction(() => document @@ -107,10 +107,55 @@ const { execFileSync } = require("node:child_process"); .textContent.includes("Select and copy"), ); assert.equal( - await page.locator("#checkin-prompt").inputValue(), - "Edited check-in direction", + await page.locator("#fix-prompt").inputValue(), + "Edited fix prompt", + ); + await page.locator("#fix-details").evaluate((el) => (el.open = false)); + // The check-in prompt now lives in the shared orchestration panel: the + // user's direction is appended to the exact preview, and a rejected + // clipboard opens and selects that preview for manual copying. + const panel = page.locator("#orchestration-pulse"); + assert.equal( + await panel.locator("[data-orchestration-preview][open]").count(), + 0, + ); + await panel + .locator("[data-orchestration-direction]") + .fill("Edited check-in direction"); + await panel + .getByRole("button", { name: "Profiling Check In", exact: true }) + .click(); + await page.waitForFunction( + () => + document.querySelector("#orchestration-pulse .orchestration-status") + .textContent !== "", + ); + assert.equal( + await panel.locator("[data-orchestration-preview][open]").count(), + 1, + ); + const preview = await panel + .locator("[data-orchestration-prompt]") + .inputValue(); + const owner = await panel + .locator("[data-orchestration-prompt]") + .evaluate((el) => el.defaultValue); + assert(owner.startsWith("Use PyAutoPulse as the home"), owner.slice(0, 80)); + assert.equal( + preview, + owner + + "\n\nOptional direction (user context):\nEdited check-in direction", + ); + assert( + await page.evaluate( + () => + document.activeElement === + document.querySelector("#orchestration-pulse-prompt"), + ), ); - await page.locator("#checkin-details").evaluate((el) => (el.open = false)); + await panel + .locator("[data-orchestration-preview]") + .evaluate((el) => (el.open = false)); await page.locator("[data-open]").first().click(); assert.equal(await page.locator(".campaign-detail[open]").count(), 1); await choose(); diff --git a/tests/test_campaigns.py b/tests/test_campaigns.py index 0b02085..9809556 100644 --- a/tests/test_campaigns.py +++ b/tests/test_campaigns.py @@ -118,3 +118,33 @@ def test_each_open_task_is_inside_its_campaign_and_links_are_separate(): assert 'aria-label="Tasks for ' + html.escape(campaign["title"], quote=True) in page if campaign.get("evidence"): assert 'aria-label="Evidence for ' + html.escape(campaign["title"], quote=True) in page + + +def test_shared_panel_keeps_owner_prompt_and_all_trusted_destinations(): + import html + import re + + links = [ + {"label": "First project", "href": "https://github.com/example/first"}, + {"label": "Second project", "href": "https://github.com/example/second"}, + ] + page = campaigns.render_html(campaigns.load(), work_links=links) + preview = html.unescape( + re.search(r'data-orchestration-prompt readonly rows="8">(.*?)', page, re.S)[1] + ) + assert preview.startswith(campaigns.PROMPT + "\n\nWork on GitHub:\n") + for link in links: + assert f"- {link['label']}: {link['href']}" in preview + assert page.count("data-orchestration-panel") == 1 + assert "data-orchestration-direction" in page + assert "orchestrationSync" in board.render_html([]) + + +def test_systematic_fix_remains_separate_from_general_panel(): + import html + + page = campaigns.render_html(campaigns.load()) + assert html.escape(campaigns.FIX_PROMPT, quote=True) in page + assert 'data-field="fix-prompt"' in page + assert 'class="prompt-copy text"' in page + assert 'data-field="checkin-prompt"' not in page
CampaignStatusLinks