Skip to content

fix: allow addSibling to target the root message#2432

Open
RudrenduPaul wants to merge 3 commits into
huggingface:mainfrom
RudrenduPaul:fix/addsibling-root-message
Open

fix: allow addSibling to target the root message#2432
RudrenduPaul wants to merge 3 commits into
huggingface:mainfrom
RudrenduPaul:fix/addsibling-root-message

Conversation

@RudrenduPaul

Copy link
Copy Markdown

Closes #2386, Closes #2387

What this PR does

addSibling in src/lib/utils/tree/addSibling.ts threw an error whenever
the sibling target was the conversation's root message:

Error: The sibling message is the root message, therefore we can't add a sibling

This restriction was unnecessary. A sibling of the root message is just
another top-level message with an empty ancestors array, and the rest of
the function already handles that case correctly.

Root cause

The guard clause explicitly threw when sibling.ancestors was empty or
undefined:

if (!sibling.ancestors || sibling.ancestors?.length === 0) {
  throw new Error("The sibling message is the root message, therefore we can't add a sibling");
}

Change

Removed the guard clause. With it gone, the existing logic already handles
the root case correctly:

  • sibling.ancestors is [], so the new message is pushed with
    ancestors: [] (same as the root).
  • nearestAncestorId resolves to sibling.ancestors[sibling.ancestors.length - 1],
    i.e. [][-1], which is undefined.
  • conv.messages.find(m => m.id === nearestAncestorId) returns undefined
    since no message has id === undefined.
  • The if (nearestAncestor) block is therefore skipped, which is exactly
    right: there is no parent node whose children array needs updating.

The non-root path is completely unaffected — the removed guard only ever
fired when ancestors was empty/falsy, so every other call path behaves
identically to before.

Also updated addSibling.spec.ts: the existing test asserted the old
throwing behavior (with a // TODO: This behaviour should be fixed comment
already acknowledging it needed to change). Replaced it with a test that
confirms addSibling now succeeds when targeting the root message and
returns a new message with the same (empty) ancestors as the root.

Verification

  • Traced the full function logic by hand for the root-message case to
    confirm no other code path re-throws or crashes once the guard is gone.
  • npx prettier --check and npx eslint both pass clean on the changed files.
  • Ran the full addSibling.spec.ts suite: 8/8 tests pass, including the
    updated root-message test and the pre-existing "should add a sibling to
    a message" test (confirms non-root behavior is unchanged).

Note: Claude Code was used to assist in drafting this fix. All changes were reviewed by the submitter.

addSibling threw when the sibling target was the root message, even
though the existing logic already handles that case correctly: the
new message is pushed with the same (empty) ancestors array, and the
nearestAncestor lookup resolves to undefined so the children-update
step is safely skipped. Removed the unnecessary guard clause.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

const nearestAncestorId = sibling.ancestors[sibling.ancestors.length - 1];
const nearestAncestor = conv.messages.find((m) => m.id === nearestAncestorId);

P2 Badge Record root-level siblings in the branch index

When the target is the root, sibling.ancestors is empty, so nearestAncestorId becomes undefined and no children array is updated. The branch picker is built exclusively from message.children (createMessagesAlternatives in src/routes/conversation/[id]/+page.svelte lines 116-123, then matched in ChatWindow.svelte line 620), so retrying or editing the first user message creates a second top-level message that is not present in any alternatives list; after this succeeds, users cannot navigate between the original root branch and the retried root branch. Please also represent root siblings in the branch data, or teach the UI to include messages with empty ancestors.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Removing the root-message guard also removed a TypeScript narrowing
check on sibling.ancestors, causing 2 svelte-check errors -- fixed
with an explicit ?? [] fallback.

Also addresses the Codex review finding on this PR: root-level
siblings had no parent to record them in a children array, so they
were created but never appeared in any alternatives group, making
them unreachable from the branch switcher UI. createMessagesAlternatives
now also emits a root-level alternatives group when more than one
top-level message exists.
@RudrenduPaul

Copy link
Copy Markdown
Author

Thanks @chatgpt-codex-connector for catching this — confirmed and fixed in the latest commit.

Root-level siblings had no parent to record them in a children array, so they were created but never appeared in any messagesAlternatives group, making them unreachable from the branch switcher UI. createMessagesAlternatives in +page.svelte now also emits a root-level alternatives group when more than one top-level message exists, so ChatMessage's existing alternatives={messagesAlternatives.find((a) => a.includes(message.id))} lookup picks it up with no other UI changes needed.

While verifying this, svelte-check also caught that removing the original guard clause in addSibling.ts had silently dropped a TypeScript narrowing check on sibling.ancestors (2 new type errors) — fixed with an explicit ?? [] fallback. All 8 existing tests still pass.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

addSibling throws unnecessarily when target is the root message fix: addSibling incorrectly rejects root message as sibling target

1 participant