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
20 changes: 10 additions & 10 deletions site/prompts/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ <h1 class="misreg">Prompts and skills</h1>
<summary><h3 id="skill-thirdshift-code-review-title">thirdshift-code-review</h3></summary>
<div class="job-body">
<p class="sent-by">Used by <a href="/#unit-3">3 · Y · Review</a></p>
<details class="fold">
<details class="fold" open>
<summary><h4 id="file-thirdshift-code-review-skill-md"><code>thirdshift-code-review/SKILL.md</code></h4></summary>
<pre class="job-text"><code>---
name: thirdshift-code-review
Expand Down Expand Up @@ -553,7 +553,7 @@ <h1 class="misreg">Prompts and skills</h1>
<summary><h3 id="skill-thirdshift-codebase-design-title">thirdshift-codebase-design</h3></summary>
<div class="job-body">
<p class="sent-by">Used by the Architecture review, in an Architect run</p>
<details class="fold">
<details class="fold" open>
<summary><h4 id="file-thirdshift-codebase-design-skill-md"><code>thirdshift-codebase-design/SKILL.md</code></h4></summary>
<pre class="job-text"><code>---
name: thirdshift-codebase-design
Expand Down Expand Up @@ -775,7 +775,7 @@ <h1 class="misreg">Prompts and skills</h1>
<summary><h3 id="skill-thirdshift-implement-title">thirdshift-implement</h3></summary>
<div class="job-body">
<p class="sent-by">Used by <a href="/#unit-2">2 · M · Implement</a></p>
<details class="fold">
<details class="fold" open>
<summary><h4 id="file-thirdshift-implement-skill-md"><code>thirdshift-implement/SKILL.md</code></h4></summary>
<pre class="job-text"><code>---
name: thirdshift-implement
Expand Down Expand Up @@ -811,7 +811,7 @@ <h1 class="misreg">Prompts and skills</h1>
<summary><h3 id="skill-thirdshift-improve-codebase-architecture-title">thirdshift-improve-codebase-architecture</h3></summary>
<div class="job-body">
<p class="sent-by">Used by the Architecture review, in an Architect run</p>
<details class="fold">
<details class="fold" open>
<summary><h4 id="file-thirdshift-improve-codebase-architecture-skill-md"><code>thirdshift-improve-codebase-architecture/SKILL.md</code></h4></summary>
<pre class="job-text"><code>---
name: thirdshift-improve-codebase-architecture
Expand Down Expand Up @@ -1078,7 +1078,7 @@ <h1 class="misreg">Prompts and skills</h1>
<summary><h3 id="skill-thirdshift-pr-title">thirdshift-pr</h3></summary>
<div class="job-body">
<p class="sent-by">Used by <a href="/#unit-3">3 · Y · Review</a></p>
<details class="fold">
<details class="fold" open>
<summary><h4 id="file-thirdshift-pr-skill-md"><code>thirdshift-pr/SKILL.md</code></h4></summary>
<pre class="job-text"><code>---
name: thirdshift-pr
Expand Down Expand Up @@ -1272,7 +1272,7 @@ <h1 class="misreg">Prompts and skills</h1>
<summary><h3 id="skill-thirdshift-resolving-merge-conflicts-title">thirdshift-resolving-merge-conflicts</h3></summary>
<div class="job-body">
<p class="sent-by">Used by <a href="/#unit-4">4 · K · Finish</a>, in a Repair</p>
<details class="fold">
<details class="fold" open>
<summary><h4 id="file-thirdshift-resolving-merge-conflicts-skill-md"><code>thirdshift-resolving-merge-conflicts/SKILL.md</code></h4></summary>
<pre class="job-text"><code>---
name: thirdshift-resolving-merge-conflicts
Expand Down Expand Up @@ -1304,7 +1304,7 @@ <h1 class="misreg">Prompts and skills</h1>
<summary><h3 id="skill-thirdshift-security-audit-title">thirdshift-security-audit</h3></summary>
<div class="job-body">
<p class="sent-by">Used by the Security audit, in a Security run, or a Security review</p>
<details class="fold">
<details class="fold" open>
<summary><h4 id="file-thirdshift-security-audit-skill-md"><code>thirdshift-security-audit/SKILL.md</code></h4></summary>
<pre class="job-text"><code>---
name: thirdshift-security-audit
Expand Down Expand Up @@ -6704,7 +6704,7 @@ <h1 class="misreg">Prompts and skills</h1>
<summary><h3 id="skill-thirdshift-tdd-title">thirdshift-tdd</h3></summary>
<div class="job-body">
<p class="sent-by">Used by <a href="/#unit-2">2 · M · Implement</a></p>
<details class="fold">
<details class="fold" open>
<summary><h4 id="file-thirdshift-tdd-skill-md"><code>thirdshift-tdd/SKILL.md</code></h4></summary>
<pre class="job-text"><code>---
name: thirdshift-tdd
Expand Down Expand Up @@ -6905,7 +6905,7 @@ <h1 class="misreg">Prompts and skills</h1>
<summary><h3 id="skill-thirdshift-to-spec-title">thirdshift-to-spec</h3></summary>
<div class="job-body">
<p class="sent-by">Used by the Architecture review, in an Architect run, or Security fix publishing, in a Security run</p>
<details class="fold">
<details class="fold" open>
<summary><h4 id="file-thirdshift-to-spec-skill-md"><code>thirdshift-to-spec/SKILL.md</code></h4></summary>
<pre class="job-text"><code>---
name: thirdshift-to-spec
Expand Down Expand Up @@ -7005,7 +7005,7 @@ <h1 class="misreg">Prompts and skills</h1>
<summary><h3 id="skill-thirdshift-to-tickets-title">thirdshift-to-tickets</h3></summary>
<div class="job-body">
<p class="sent-by">Used by the Architecture review, in an Architect run, or Security fix publishing, in a Security run</p>
<details class="fold">
<details class="fold" open>
<summary><h4 id="file-thirdshift-to-tickets-skill-md"><code>thirdshift-to-tickets/SKILL.md</code></h4></summary>
<pre class="job-text"><code>---
name: thirdshift-to-tickets
Expand Down
44 changes: 39 additions & 5 deletions site/tests/prompts.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,32 @@ async function pageForTest(t, options) {
return page;
}

test("every headed section starts closed and toggles independently by its heading", async (t) => {
// A skill's own SKILL.md starts open, so it shows as soon as its skill does.
const skillMdIds = async page => new Set(await page.locator("main h4").evaluateAll(nodes =>
nodes.filter(node => /^[^/]+\/SKILL\.md$/.test(node.textContent)).map(node => node.id)));
const openIds = page => page.locator("main details[open]").evaluateAll(nodes =>
nodes.map(node => node.querySelector("summary :is(h2,h3,h4)").id));
// The folds whose opening shows anything at all.
const openSections = "main > section > .wrap > details[open]";

test("every headed section but a skill's SKILL.md starts closed, and each toggles independently by its heading", async (t) => {
const page = await pageForTest(t);
await page.goto(`${origin}/prompts/`);
const headings = page.locator("main :is(h2, h3, h4)");
assert.ok(await headings.count() > 30);
const ids = await headings.evaluateAll(nodes => nodes.map(node => node.id));
const skillMds = await skillMdIds(page);
assert.ok(skillMds.size > 5);
const isSkillMd = id => skillMds.has(id);
for (const id of ids) {
assert.equal(await page.locator(`#${id}`).evaluate(heading => {
const summary = heading.closest("summary");
return Boolean(summary && summary.parentElement.tagName === "DETAILS" && !summary.parentElement.open);
}), true, `${id} must control a closed section`);
return Boolean(summary && summary.parentElement.tagName === "DETAILS");
}), true, `${id} must control a section`);
assert.equal(await page.locator(`#${id}`).evaluate(heading => heading.closest("details").open), isSkillMd(id), `${id} starts ${isSkillMd(id) ? "open" : "closed"}`);
}
assert.equal(await page.locator("main details[open]").count(), 0);
assert.deepEqual(new Set(await openIds(page)), skillMds);
assert.equal(await page.locator(openSections).count(), 0, "nothing shows before its section opens");
for (const id of ids) {
const heading = page.locator(`#${id}`);
// Open only the containing sections, as a reader does before reaching a nested heading.
Expand All @@ -63,6 +76,8 @@ test("every headed section starts closed and toggles independently by its headin
await page.locator(`#${parent}`).click();
}
}
// Fold a SKILL.md first, so every heading is tested opening then folding.
if (isSkillMd(id)) await heading.click();
await heading.click();
assert.equal(await heading.evaluate(node => node.closest("details").open), true, id);
const content = heading.locator("xpath=ancestor::summary/parent::details/*[not(self::summary)][1]");
Expand All @@ -76,6 +91,24 @@ test("every headed section starts closed and toggles independently by its headin
assert.equal(await page.locator("#command-title").evaluate(node => node.closest("details").open), true);
});

test("opening a skill shows its SKILL.md, which still folds by its heading", async (t) => {
const page = await pageForTest(t);
await page.goto(`${origin}/prompts/`);
await page.locator("#skills-title").click();
const skillMd = page.locator("#file-thirdshift-code-review-skill-md").locator("xpath=ancestor::details[1]/pre");
assert.equal(await skillMd.isVisible(), false);
await page.locator("#skill-thirdshift-code-review-title").click();
assert.equal(await skillMd.isVisible(), true);
// The supporting files stay folded.
const others = page.locator("#skill-thirdshift-code-review h4:not(#file-thirdshift-code-review-skill-md)");
assert.ok(await others.count() > 0);
for (const heading of await others.all()) {
assert.equal(await heading.evaluate(node => node.closest("details").open), false);
}
await page.locator("#file-thirdshift-code-review-skill-md").click();
assert.equal(await skillMd.isVisible(), false);
});

test("fragment links reveal the prompt or supporting file and its containing sections", async (t) => {
const page = await pageForTest(t);
await page.goto(`${origin}/prompts/#prompt-resume`);
Expand All @@ -95,7 +128,8 @@ test("fragment links reveal the prompt or supporting file and its containing sec
test("mobile readers can toggle headings with the keyboard without JavaScript", async (t) => {
const page = await pageForTest(t, { javaScriptEnabled: false, viewport: { width: 390, height: 844 } });
await page.goto(`${origin}/prompts/`);
assert.equal(await page.locator("main details[open]").count(), 0);
assert.equal(await page.locator(openSections).count(), 0);
assert.deepEqual(new Set(await openIds(page)), await skillMdIds(page));
const prompts = page.locator("#prompts-title").locator("xpath=parent::summary");
await prompts.focus();
await page.keyboard.press("Enter");
Expand Down
21 changes: 10 additions & 11 deletions src/prompts_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,14 +517,12 @@ fn skills_section(html: &mut String) {
let mut files = Vec::new();
collect_files(skill, &mut files);
// SKILL.md first, as the agent reads it first; then the rest by path.
files.sort_by_key(|file| {
(
file.path().file_name() != Some(OsStr::new("SKILL.md")),
file.path(),
)
});
let skill_md = skill.path().join("SKILL.md");
let is_skill_md = |file: &File| file.path() == skill_md;
files.sort_by_key(|file| (!is_skill_md(file), file.path()));
for file in files {
file_block(html, file, " ");
// Opening a skill shows its SKILL.md; the supporting files stay folded.
file_block(html, file, " ", is_skill_md(file));
}
html.push_str(" </div>\n </details>\n </article>\n");
}
Expand All @@ -548,7 +546,7 @@ fn licence_section(html: &mut String) {
OsStr::new("LICENSE"),
"a file outside any skill needs a place on the page"
);
file_block(html, file, " ");
file_block(html, file, " ", false);
}
html.push_str(" </details>\n </div>\n</section>\n");
}
Expand All @@ -561,8 +559,8 @@ fn collect_files<'a>(dir: &'a Dir<'a>, files: &mut Vec<&'a File<'a>>) {
}
}

/// A file's path as a disclosure heading, then its raw text.
fn file_block(html: &mut String, file: &File, indent: &str) {
/// A file's path as a disclosure heading, then its raw text, folded unless `open`.
fn file_block(html: &mut String, file: &File, indent: &str, open: bool) {
let path = file.path().to_string_lossy();
let text = file
.contents_utf8()
Expand All @@ -574,7 +572,8 @@ fn file_block(html: &mut String, file: &File, indent: &str) {
.collect();
let _ = write!(
html,
"{indent}<details class=\"fold\">\n{indent}<summary><h4 id=\"file-{id}\"><code>{path}</code></h4></summary>\n{indent}<pre class=\"job-text\"><code>{text}</code></pre>\n{indent}</details>\n",
"{indent}<details class=\"fold\"{open}>\n{indent}<summary><h4 id=\"file-{id}\"><code>{path}</code></h4></summary>\n{indent}<pre class=\"job-text\"><code>{text}</code></pre>\n{indent}</details>\n",
open = if open { " open" } else { "" },
path = escape(&path),
text = escape(text),
);
Expand Down
Loading