Skip to content

Commit f430c55

Browse files
vanceingallsclaude
andcommitted
feat(sdk): add find({ composition }) filter — Stage 6 WS-C completion
Closes the last headless-testable Stage 6 gap (F9 workstream C). `find({ composition: "hf-host" })` returns all scopedIds whose prefix matches the given host id — i.e. every element mounted inside that sub-composition, at any depth. Combinable with other FindQuery fields (tag, text, name, track). 3 new contract tests in session.subcomp.test.ts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2218829 commit f430c55

3 files changed

Lines changed: 46 additions & 1 deletion

File tree

packages/sdk/src/session.subcomp.test.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,49 @@ describe("override-set — scoped id keys", () => {
324324
});
325325
});
326326

327-
// ─── 5. Scoped id stability across serialize ──────────────────────────────────
327+
// ─── 5. find({ composition }) filter ─────────────────────────────────────────
328+
329+
describe("find({ composition })", () => {
330+
it("returns elements inside the named host sub-composition", async () => {
331+
const html = inlinedHtml(`
332+
<div data-hf-id="hf-root" data-hf-root>
333+
<div data-hf-id="hf-host" data-composition-file="sub.html">
334+
<p data-hf-id="hf-leaf">inside</p>
335+
</div>
336+
<p data-hf-id="hf-outer">outside</p>
337+
</div>
338+
`);
339+
const comp = await openComposition(html);
340+
const ids = comp.find({ composition: "hf-host" });
341+
expect(ids).toContain("hf-host/hf-leaf");
342+
expect(ids).not.toContain("hf-outer");
343+
expect(ids).not.toContain("hf-host"); // host itself is in parent scope
344+
});
345+
346+
it("returns empty array for unknown host id", async () => {
347+
const html = inlinedHtml(
348+
`<div data-hf-id="hf-root" data-hf-root><p data-hf-id="hf-p">x</p></div>`,
349+
);
350+
const comp = await openComposition(html);
351+
expect(comp.find({ composition: "hf-no-such" })).toEqual([]);
352+
});
353+
354+
it("can combine composition filter with other query fields", async () => {
355+
const html = inlinedHtml(`
356+
<div data-hf-id="hf-root" data-hf-root>
357+
<div data-hf-id="hf-host" data-composition-file="sub.html">
358+
<p data-hf-id="hf-a">match</p>
359+
<span data-hf-id="hf-b">no</span>
360+
</div>
361+
</div>
362+
`);
363+
const comp = await openComposition(html);
364+
const ids = comp.find({ composition: "hf-host", tag: "p" });
365+
expect(ids).toEqual(["hf-host/hf-a"]);
366+
});
367+
});
368+
369+
// ─── 6. Scoped id stability across serialize ──────────────────────────────────
328370

329371
describe("scopedId stability across serialize/re-parse", () => {
330372
it("scopedId values are identical after serialize + re-open", async () => {

packages/sdk/src/session.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ class CompositionImpl implements Composition {
186186
if (query.text && !el.text?.includes(query.text)) return false;
187187
if (query.name && el.attributes["data-name"] !== query.name) return false;
188188
if (query.track !== undefined && el.trackIndex !== query.track) return false;
189+
if (query.composition && !el.scopedId.startsWith(`${query.composition}/`)) return false;
189190
return true;
190191
})
191192
.map((el) => el.scopedId)

packages/sdk/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ export interface FindQuery {
186186
text?: string;
187187
name?: string;
188188
track?: number;
189+
/** Filter to elements inside a specific sub-composition host (by host hf-id). */
190+
composition?: string;
189191
}
190192

191193
// ─── Typed method sugar (F10) ─────────────────────────────────────────────────

0 commit comments

Comments
 (0)