mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-20 09:09:35 +02:00
fix(tabs): children spawned from one parent keep creation order (#528 merge-time fix)
With the follow setting off (the default), pinCreatedChild marked each new child manual where normalization put it: right after the parent's FOLLOWING block, which no longer holds the siblings pinned before it. A fan-out therefore showed parent, w3, w2, w1 in the layout and the global sessionOrder. The pinned child now steps past the siblings (and their descendants) already after the parent. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a38eab49cd
commit
5ac27f3eec
2 files changed
+44
-1
No files matched your search
@@ -131,7 +131,31 @@ function pinCreatedChild(layout: TabLayout, sessionId: string, metadata: readonl
|
|||||||
const parentStored = refs.some((candidate) => candidate.kind === 'session' && candidate.id === parentId);
|
const parentStored = refs.some((candidate) => candidate.kind === 'session' && candidate.id === parentId);
|
||||||
const parentValid = metadata.some((fact) => fact.kind === 'session' && fact.id === parentId && fact.ownerValid);
|
const parentValid = metadata.some((fact) => fact.kind === 'session' && fact.id === parentId && fact.ownerValid);
|
||||||
if (!ref || ref.placement === 'manual' || !parentStored || !parentValid) return layout;
|
if (!ref || ref.placement === 'manual' || !parentStored || !parentValid) return layout;
|
||||||
return setManualPlacement(layout, ref, true);
|
const pinned = setManualPlacement(layout, ref, true);
|
||||||
|
// Normalization put the child right after its parent's FOLLOWING block, i.e.
|
||||||
|
// ahead of the siblings pinned before it. Step past those (and their
|
||||||
|
// descendants) so a fan-out keeps creation order.
|
||||||
|
const parentOf = new Map<string, string>();
|
||||||
|
for (const fact of metadata) {
|
||||||
|
if (fact.kind === 'session' && fact.ownerValid && fact.parentSessionId) parentOf.set(fact.id, fact.parentSessionId);
|
||||||
|
}
|
||||||
|
const descendsFromParent = (id: string): boolean => {
|
||||||
|
let current = parentOf.get(id);
|
||||||
|
for (let hops = 0; current !== undefined && hops <= parentOf.size; hops++) {
|
||||||
|
if (current === parentId) return true;
|
||||||
|
current = parentOf.get(current);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
const isChild = (candidate: TabRef): boolean => candidate.kind === 'session' && candidate.id === sessionId;
|
||||||
|
const group = pinned.groups.find((candidate) => candidate.refs.some(isChild));
|
||||||
|
const container = group ? group.refs : pinned.ungrouped;
|
||||||
|
const from = container.findIndex(isChild);
|
||||||
|
let to = from;
|
||||||
|
while (to + 1 < container.length && container[to + 1].kind === 'session' && descendsFromParent(container[to + 1].id))
|
||||||
|
to++;
|
||||||
|
if (to === from) return pinned;
|
||||||
|
return moveRef(pinned, { kind: 'session', id: sessionId }, { groupId: group?.id ?? null, index: to }, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Move a just-created session to the end of `groupId`, or leave the layout alone. */
|
/** Move a just-created session to the end of `groupId`, or leave the layout alone. */
|
||||||
|
|||||||
@@ -1438,6 +1438,25 @@ describe('TabLayoutService grouped session creation', () => {
|
|||||||
expect(moved.layout.ungrouped.map((ref) => ref.id)).toEqual(['b', 'a']);
|
expect(moved.layout.ungrouped.map((ref) => ref.id)).toEqual(['b', 'a']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('with the follow setting off, children spawned one after another keep creation order', async () => {
|
||||||
|
const h = createHarness({ layouts: { alice: grouped() }, live: facts() });
|
||||||
|
let layout = grouped();
|
||||||
|
for (const [index, id] of ['c1', 'c2', 'c3'].entries()) {
|
||||||
|
layout = await h.service.sessionCreated(
|
||||||
|
'alice',
|
||||||
|
id,
|
||||||
|
{},
|
||||||
|
register(h, { id, owner: 'alice', createdAt: 3 + index, parentSessionId: 'a' })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
expect(layout.groups[0].refs).toEqual([
|
||||||
|
{ kind: 'session', id: 'a' },
|
||||||
|
{ kind: 'session', id: 'c1', placement: 'manual' },
|
||||||
|
{ kind: 'session', id: 'c2', placement: 'manual' },
|
||||||
|
{ kind: 'session', id: 'c3', placement: 'manual' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('reads the follow setting fresh at every creation', async () => {
|
it('reads the follow setting fresh at every creation', async () => {
|
||||||
const h = createHarness({ layouts: { alice: grouped() }, live: facts() });
|
const h = createHarness({ layouts: { alice: grouped() }, live: facts() });
|
||||||
const setting = vi.fn<() => boolean>().mockReturnValueOnce(true).mockReturnValueOnce(false);
|
const setting = vi.fn<() => boolean>().mockReturnValueOnce(true).mockReturnValueOnce(false);
|
||||||
|
|||||||
Reference in new issue
Block a user