Skip to content

fix(staged): hide the Rebase button while a rebase is queued or running - #938

Merged
matt2e merged 3 commits into
mainfrom
hide-rebase-button-when-rebasing
Sep 3, 2026
Merged

fix(staged): hide the Rebase button while a rebase is queued or running#938
matt2e merged 3 commits into
mainfrom
hide-rebase-button-when-rebasing

Conversation

@matt2e

@matt2e matt2e commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

The header Rebase button stayed visible after a rebase was requested — the parent is still ahead until the pipeline lands, so it kept inviting repeat clicks. The backend only dedupes the queued case (queue_commit_pipeline_locked matches against find_queued_pipeline, which reads queued sessions only), so a second click against a running rebase inserts a fresh queued rebase that runs once the first lands.

Changes

  • Pending timeline rows carry their pipeline kind. CommitTimelineItem gains pipeline_kind, set only on pending rows from the session's pipeline. The frontend needs a structured field here because the row subject is a display label that an agent-pushed ACP title can replace mid-pipeline (e.g. when a conflicted rebase hands off to an agent).
  • New rebaseInFlight helper reads that field to detect a queued or running rebase on the branch, with unit tests.
  • BranchCard withholds the header Rebase button while a rebase is in flight; commandPipelinePending already covers the window between the click and the timeline reload that surfaces the row.
  • The menu's Rebase item now disables on rebaseAlreadyInFlight as well, closing the second path to a duplicate rebase. It's a separate prop from rebaseSquashDisabled, so Squash — which queues behind the rebase harmlessly — is unaffected.
  • BranchCardHeaderInfo swaps the previously unreachable rebaseDisabled boolean for a rebaseDisabledReason string (matching TimelineRow's convention), so a wrong branch or a command in progress surfaces as a disabled button with a tooltip instead of one that silently vanishes.

Testing

  • New Rust test asserting a queued rebase's pending row carries PipelineKind::Rebase while a plain commit session's does not.
  • New rebaseInFlight.test.ts covering queued, running, completed, non-rebase pipelines, and a missing timeline.

matt2e and others added 2 commits September 2, 2026 13:55
The header Rebase button stayed visible after a rebase was requested —
the parent is still ahead until the pipeline lands, so it kept inviting
repeat clicks the backend can only dedupe.

Pending timeline rows now carry their session's pipeline kind, since the
row subject is a display label an agent-pushed ACP title can replace
mid-pipeline. BranchCard withholds the header Rebase button while an
active (queued or running) rebase row is present;
commandPipelinePending already covers the window between the click and
the timeline reload that surfaces the row.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
…utton's disabled state

Review of the previous commit found two loose ends and one wrong
rationale.

The `…` menu's Rebase item was still gated only on
branchCommandDisabledReason, so it stayed clickable while a rebase ran
and could enqueue a duplicate. It now takes rebaseAlreadyInFlight as a
separate prop from rebaseSquashDisabled, so Squash — which queues behind
the rebase harmlessly — is unaffected.

BranchCardHeaderInfo's rebaseDisabled prop was unreachable: the button
only renders under `onRebase`, which the caller withheld for the same
conditions the prop was meant to disable. The header now hides the button
only for an in-flight rebase and passes the reason string through
rebaseDisabledReason (matching TimelineRow's convention), so a wrong
branch or a command in progress surfaces as a disabled button with a
tooltip instead of one that silently vanishes.

The rebaseInFlight comment claimed the backend dedupes a repeat click
anyway. That holds only for the queued case: queue_commit_pipeline_locked
matches against find_queued_pipeline, which reads queued sessions only,
so a second request against a *running* rebase inserts a fresh queued one
that runs after the first lands. Corrected, so nobody reads the guard as
cosmetic and drops it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>

@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

Here are some automated review suggestions for this pull request.

Reviewed commit: 4638df6f8c

ℹ️ 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".

* plain commit session, since the subject is a display label an agent-pushed
* ACP title can replace mid-pipeline.
*/
pipelineKind?: PipelineKind | null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Invalidate timelines cached before pipelineKind existed

In the web client, timelines survive deployments in both IndexedDB under the unchanged CACHE_SCHEMA_VERSION = 1 and the unversioned staged:boot:timelines localStorage snapshot. After upgrading while a rebase is active, its cached pending row therefore has pipelineKind === undefined, so rebaseInFlight returns false; a fresh cache can short-circuit revalidation for 10–30 seconds, leaving both Rebase entry points available and allowing a second request to queue behind a running rebase. Bump/version the response cache and invalidate or version the boot snapshot when introducing this field.

Useful? React with 👍 / 👎.

…elineKind field

`pipelineKind` was added to `CommitTimelineItem` earlier on this branch to
drive the `rebaseInFlight` guard, but neither web-mode layer that persists
timeline responses across a deploy was versioned for it. After an upgrade
that lands mid-rebase, cached pending rows read `pipelineKind ===
undefined`, the guard reads false, and both Rebase entry points come back
while a rebase is queued or running — the duplicate-enqueue hole the guard
exists to close, since the backend only dedupes *queued* rebases.

CACHE_SCHEMA_VERSION goes to 2, so IndexedDB entries written by the
previous build fail the version check and read as misses instead of
short-circuiting the fetch inside their 30s TTL.

The `staged:boot:timelines` localStorage snapshot was unversioned, and it
is the worse of the two: it seeds the in-memory cache at module init, and
an entry under TIMELINE_FRESH_MS makes getBranchTimelineWithRevalidation
return `fresh: null` — no fetch at all to correct the shape. Its payload
is now stamped with the same constant and discarded (and cleared) on
mismatch; a pre-versioning snapshot is a bare record with no
`schemaVersion`, so it fails the same check. Sharing one constant across
both layers means the next cached-shape change needs exactly one bump.

Tests cover both layers: a previous-version IDB entry is a miss in
`cachedInvoke` and `cachedCommand`, an unversioned or mismatched snapshot
is discarded and its key freed, and persist-then-seed round-trips under
the current version. The snapshot tests mock the constant to an arbitrary
value so they track the shared export rather than its current number.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
@matt2e
matt2e merged commit f913f82 into main Sep 3, 2026
4 checks passed
@matt2e
matt2e deleted the hide-rebase-button-when-rebasing branch September 3, 2026 05:41
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.

1 participant