Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

<!-- repos_sync:standards:begin -->
## 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.
<!-- repos_sync:standards:end -->
10 changes: 8 additions & 2 deletions pulse/board.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,10 +571,16 @@ def render_html(views, now: str | None = None, campaign_data: dict | None = None
],
)
+ "<main>"
+ 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)
+ "</main>"
+ f"<script>{JS}\n{browser_js}</script></body></html>\n"
+ f"<script>{shared.JS}\n{JS}\n{browser_js}</script></body></html>\n"
)


Expand Down
18 changes: 13 additions & 5 deletions pulse/campaigns.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'<div class="prompt-action"><button type="button" class="copy text" data-field="{key}-prompt">{e(title)}</button>{review}'
f'<div class="prompt-action"><button type="button" class="prompt-copy text" data-field="{key}-prompt">{e(title)}</button>{review}'
f'<details id="{key}-details"><summary>Full prompt</summary><label for="{key}-prompt">Edit before copying</label>'
f'<textarea id="{key}-prompt" rows="7">{e(prompt)}</textarea></details></div>'
)
Expand All @@ -158,10 +158,18 @@ def action(key, title, prompt, review=""):
review = f'<span class="review-meta" title="Ledger dates are review dates, not measurement freshness.">Last check-in: {e(reviewed[:10] if data.get("last_checkin") else reviewed)} · review date</span>'
parts = [
marker(data),
'<section class="controls" aria-label="Profiling actions">'
+ theme().prompt_heading("pulse"),
theme().orchestration_panel(
"pulse",
"",
"",
PROMPT,
work_links=work_links,
copy_label="Profiling Check In",
organ="pulse",
),
'<section class="controls" aria-label="Profiling actions">',
action("fix", "Fix Profiling Systematically", FIX_PROMPT),
action("checkin", "Profiling Check In", PROMPT, review),
review,
'<span id="copy-status" role="status" aria-live="polite"></span></section>',
'<h2 id="campaigns">Active campaigns</h2><div class="campaign-table"><table><thead><tr><th>Campaign</th><th>Status</th><th>Links</th></tr></thead><tbody>',
]
Expand Down
65 changes: 55 additions & 10 deletions tests/browser_setup.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -92,25 +92,70 @@ 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
.querySelector("#copy-status")
.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();
Expand Down
30 changes: 30 additions & 0 deletions tests/test_campaigns.py
Original file line number Diff line number Diff line change
Expand Up @@ -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">(.*?)</textarea>', 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
Loading