+ `;
+ let proxy = htmx.live.q('#parent .el.selected').q('previous .el');
+ proxy.count.should.equal(1);
+ proxy.textContent.should.equal('One');
+ });
+
+ it('chained q: next returns empty when anchor is last sibling', function() {
+ playground().innerHTML = `
+
+
One
+
Two
+
Three
+
+ `;
+ htmx.live.q('#parent .el.last').q('next .el').count.should.equal(0);
+ });
+
+ it('chained q: previous returns empty when anchor is first sibling', function() {
+ playground().innerHTML = `
+
+
One
+
Two
+
Three
+
+ `;
+ htmx.live.q('#parent .el.first').q('previous .el').count.should.equal(0);
+ });
+
+ it('chained q: next does not escape to a different parent', function() {
+ // Two groups in separate parents. next .item from #s1 searches within
+ // #s1's parent (#group1) only — cannot reach .item inside #group2.
+ playground().innerHTML = `
+
+
+
+
+
B
+
+ `;
+ let proxy = htmx.live.q('#s1').q('next .item');
+ proxy.count.should.equal(0);
+ });
+
+ it('chained q: first scopes inside each matched element', function() {
+ playground().innerHTML = `
+ ab
+ cd
+ `;
+ let proxy = htmx.live.q('section').q('first .i');
+ proxy.count.should.equal(2);
+ proxy.arr().map(e => e.textContent).should.deep.equal(['a', 'c']);
+ });
+
+ it('chained q: last scopes inside each matched element', function() {
+ playground().innerHTML = `
+ ab
+ cd
+ `;
+ let proxy = htmx.live.q('section').q('last .i');
+ proxy.count.should.equal(2);
+ proxy.arr().map(e => e.textContent).should.deep.equal(['b', 'd']);
+ });
+
+ it('chained q: first with in this also scopes inside each element', function() {
+ playground().innerHTML = `
+ ab
+ cd
+ `;
+ // explicit "in this" should give the same result as the default scoped behaviour
+ let proxy = htmx.live.q('section').q('first .i in this');
+ proxy.count.should.equal(2);
+ proxy.arr().map(e => e.textContent).should.deep.equal(['a', 'c']);
+ });
+
+ it('chained q: next across multiple rows returns each successor', function() {
+ playground().innerHTML = `
+
+
Row 1
+
Row 2
+
Row 3
+
+ `;
+ // 3 rows: row1→row2, row2→row3, row3→nothing. Deduped result = row2, row3.
+ let proxy = htmx.live.q('#rows .row').q('next .row');
+ proxy.count.should.equal(2);
+ proxy.arr().map(e => e.textContent.trim()).should.deep.equal(['Row 2', 'Row 3']);
+ });
+
it('q is available in hx-on scope and bound to element', function() {
playground().innerHTML = '
tgt
';
htmx.process(playground());
diff --git a/www/src/content/extensions/06-hx-live.md b/www/src/content/extensions/06-hx-live.md
index dcfe7a1e3..11d041615 100644
--- a/www/src/content/extensions/06-hx-live.md
+++ b/www/src/content/extensions/06-hx-live.md
@@ -322,7 +322,7 @@ q('button').click()
```js
q('first .foo') // first match in document order
-q('last .foo') // last match
+q('last .foo') // last match in document order
q('next .foo') // first match after this element
q('previous .foo') // closest match before this element
q('closest .foo') // nearest ancestor matching .foo
@@ -332,14 +332,29 @@ q('.foo in this') // restrict to the current element
`next`, `previous`, and `closest` resolve against `this`. They require an expression scope with a current element. For an ancestor lookup that works anywhere, use [`closest(selector)`](#closestselector).
-**Chaining**
+**Chaining**
-`.q(...)` on a proxy re-runs the grammar with each element as the anchor:
+`.q(...)` on a proxy re-runs the grammar with each element as the positional anchor.
+
+`first` and `last` scope inside each matched element:
+
+```js
+q('section').q('first .item') // first .item inside each section
+q('section').q('last .item') // last .item inside each section
+```
+
+`next` and `previous` search within the matched element's parent, so they find siblings and their descendants but cannot reach elements under a different parent:
+
+```js
+q('.row').q('next .row') // each row's next sibling row
+q('#parent .el.selected').q('next .el') // next .el after the selected one
+q('#parent .el.selected').q('previous .el') // previous .el before the selected one
+```
+
+`closest` finds the nearest ancestor of each matched element:
```js
q('.error').q('closest .field') // surrounding .field of each .error
-q('section').q('first .item') // first .item per section
-q('.row').q('next .row') // each row's successor
```
For plain descendant queries, CSS is shorter: `q('.card .title')` and `q('.card').q('.title')` are equivalent. Use chaining when you need a directional per matched element.