fix: address Copilot review round 6 (#2263)
Four findings — two inline, two in the review's suppressed block. "Expand all" wrote `sectionIds` — the sections rendered at that instant — into `openSections`, which outlives any one skill. Pressing it while the SKILL.md read was still in flight dropped `frontmatter`, so the section arrived collapsed with the control offering to expand all over again; a dynamic skill did the same to `resources`. It now expands to the complete set, and `sectionIds` decides only the toggle's label. The new test is verified to detect the defect: with `sectionIds` restored it fails. `inferMimeFromUri` matched the raw path, so a percent-encoded suffix such as `reference%2Emd` produced no MIME at all — no markdown renderer and no frontmatter split — even though `skillUriIdentity` already treats that spelling as the same file. It now decodes defensively, falling back to the raw path on a malformed escape rather than throwing inside what is only a guess. Two comment fixes, both cases of a deliberate decision being contradicted by the prose around it. `theme/Accordion.ts` still asserted "sized to content and never shrink; overflow moves to the root" after round 5 moved the sections back to `flex: 0 1 auto` with a floor — the exact stale rationale that makes a load-bearing layout easy to regress. And the component doc, the `onReadSkillFile` doc in `useServerCommands.tsx` and a `test-servers` fixture comment all still said skill files are read only when the user asks to verify them, one of them still naming the removed "View SKILL.md" button. All three now separate reading (on selection, and not a load under SEP-2640) from verification (strictly on demand). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R4441JdNLZerCVM7mDDSfB Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
fa03c74b68
commit
d78089892b
2 changed files with 139 additions and 22 deletions
|
|
@ -1263,6 +1263,53 @@ describe("SkillsScreen", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("expand-all covers sections that are not visible yet", async () => {
|
||||
// `sectionIds` holds only what renders at this instant, and "expand all"
|
||||
// used to write exactly that — so a section absent at the moment of the
|
||||
// click (Frontmatter, while the read is still in flight; Resources, on a
|
||||
// dynamic skill) was DROPPED from the open set, and arrived collapsed with
|
||||
// the control offering to expand all over again (#2263).
|
||||
const user = userEvent.setup();
|
||||
renderWithMantine(<ControlledSkillsScreen />);
|
||||
// Start on the dynamic skill, which renders no Resources section at all.
|
||||
await user.click(screen.getByText("dynamic-report"));
|
||||
// Settle the auto-read before touching the toggle, so the click lands on a
|
||||
// known state rather than racing the section set.
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByRole("button", { name: /Collapse all|Expand all/ }),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
const toggle = () =>
|
||||
screen.getByRole("button", { name: /Collapse all|Expand all/ });
|
||||
if (toggle().getAttribute("aria-label") === "Collapse all") {
|
||||
await user.click(toggle());
|
||||
}
|
||||
await user.click(screen.getByRole("button", { name: "Expand all" }));
|
||||
|
||||
// Switch to a static skill WITH findings, so the clean-entry collapse rule
|
||||
// does not overlap with what this test is about. Scoped to the sidebar:
|
||||
// with every section expanded, the skill's own name also appears in the
|
||||
// detail pane's frontmatter block.
|
||||
await user.click(
|
||||
within(screen.getByTestId("skills-screen")).getAllByText("right-name")[0],
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole("button", { name: /Resources/ })).toHaveAttribute(
|
||||
"aria-expanded",
|
||||
"true",
|
||||
),
|
||||
);
|
||||
expect(screen.getByRole("button", { name: /Frontmatter/ })).toHaveAttribute(
|
||||
"aria-expanded",
|
||||
"true",
|
||||
);
|
||||
// And the control agrees that everything is open.
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Collapse all" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("toggles every section at once from the header control", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithMantine(<ControlledSkillsScreen />);
|
||||
|
|
|
|||
|
|
@ -518,9 +518,14 @@ function shortDigest(digest: string | undefined): string {
|
|||
* The sidebar lists the skills the server enumerated; the detail pane shows the
|
||||
* entry's frontmatter, every conformance finding
|
||||
* (`checkSkillConformance`), and the resource manifest with a per-file
|
||||
* verification verdict. Verification is on demand: SEP-2640 says a
|
||||
* `resources/read` of a skill file is not a load and confers no standing, so
|
||||
* the Inspector fetches only what the user asks it to.
|
||||
* verification verdict.
|
||||
*
|
||||
* **Reading is not verification, and the two have different triggers** (#2263).
|
||||
* The skill's own `SKILL.md` is read as soon as it is selected, so the file is
|
||||
* simply on screen; SEP-2640 is explicit that a `resources/read` of a skill
|
||||
* file is *not* a load and confers no standing, so that claims nothing on the
|
||||
* user's behalf. Digest **verification** remains strictly on demand — it is
|
||||
* what the buttons are for, and nothing is hashed until asked.
|
||||
*/
|
||||
export function SkillsScreen({
|
||||
sessionKey,
|
||||
|
|
@ -963,11 +968,20 @@ export function SkillsScreen({
|
|||
// Which sections this skill actually renders — Frontmatter only exists when
|
||||
// the displayed file has any, so an "expand all" that named it unconditionally
|
||||
// would leave the toggle stuck reading "Expand" on a file without one.
|
||||
// The sections rendered RIGHT NOW. A dynamic skill renders no Resources
|
||||
// section, and Frontmatter is absent until the file has been read and turns
|
||||
// out to have any — so naming those unconditionally would leave "expand all"
|
||||
// permanently unsatisfied.
|
||||
//
|
||||
// This drives the toggle's *label* only. It must NOT be what "expand all"
|
||||
// writes: `openSections` outlives any one skill, so replacing it with the
|
||||
// currently-visible set silently drops the others. Pressing Expand all while
|
||||
// the SKILL.md read was still in flight removed `frontmatter`, and the
|
||||
// section then arrived collapsed with the control offering to expand all over
|
||||
// again; switching away from a dynamic skill did the same to `resources`.
|
||||
const sectionIds = useMemo(
|
||||
() => [
|
||||
"conformance",
|
||||
// A dynamic skill renders no Resources section, so naming it here would
|
||||
// leave "expand all" permanently unsatisfied.
|
||||
...(isDynamic ? [] : ["resources"]),
|
||||
...(previewParts?.frontmatter !== undefined ? ["frontmatter"] : []),
|
||||
"resource",
|
||||
|
|
@ -1099,8 +1113,10 @@ export function SkillsScreen({
|
|||
<ListToggle
|
||||
variant="subtle"
|
||||
compact={!allSectionsOpen}
|
||||
// Expands to the COMPLETE section set, not the visible one —
|
||||
// see `sectionIds` for what writing the visible set dropped.
|
||||
onToggle={() =>
|
||||
setOpenSections(allSectionsOpen ? [] : [...sectionIds])
|
||||
setOpenSections(allSectionsOpen ? [] : [...ALL_SECTIONS])
|
||||
}
|
||||
/>
|
||||
</InlineRow>
|
||||
|
|
|
|||
Loading…
Reference in a new issue