Skip to content

fix(teleport): abort import batches before partial load/unlock commits

MacroscopeApp / Macroscope - UI Consistency failed Aug 27, 2026 in 4m 37s

UI Consistency: 1 new finding

  • apps/web/src/components/chat/ChatComposer.tsx: the new teleport draft guards (removeComposerImageFromDraft, removeComposerTerminalContextFromDraft, removeComposerElementContextFromDraft, removeComposerPreviewAnnotationFromDraft, removeComposerReviewCommentFromDraft) turn visible remove controls into silent no-ops while the composer is teleport-locked. The attachment thumbnail X, the preview-annotation / review-comment / element-context chip removes, and the terminal-context chip remove still render enabled with pointer cursor and no disabled state or feedback, unlike the other teleport-locked paths in the same file (addComposerImages, stash / stash-restore) which surface a reason. Suggested fix: thread disabled={isTeleportComposerLocked} into those remove controls (or suppress the chip rows while locked).

Still outstanding from an earlier run (not re-commented): insertComposerTextAtEnd gates on isSendDisabled, which also covers non-teleport reasons ("Image still uploading", "Retry or remove the failed image", "Sending feedback", "Messages loading"), so file-tree "Add to chat" mentions and mention drops are rejected in states where drafting is still allowed.

Details

Note

Your check run agent prompt is: .macroscope/check-run-agents/ui-consistency.md
More information about how Check Run Agents work can be found in our Help Center.

Files reviewed in scope: commandPaletteBus.ts, components/ChatView.tsx + ChatView.logic.ts, components/CommandPalette.tsx + CommandPalette.logic.ts, components/chat/ChatComposer.tsx, components/chat/ChatHeader.tsx, components/chat/DraftHeroHeadline.tsx, components/teleport/*, lib/teleport*.ts, state/teleport.ts.

Checked and found acceptable:

  • TeleportOutButton uses the shared Button (variant="outline", size="icon-xs") with the existing data-toolbar-control themed-toolbar hook and comment already used by ProjectScriptsControl; aria-disabled plus opacity-64/cursor-not-allowed keeps the tooltip explaining why the action is unavailable, matching the existing GitActionsControl precedent, and mirrors the primitive's disabled:opacity-64 rather than overriding it.
  • teleportNativeConflictBannerItem follows the existing ComposerBannerStack action convention (Button size="xs", real disabled, "Verb..." busy label) and hides the fork action when the status cannot be forked.
  • Environment routing: TeleportOutButton resolves capability, RPC target, and copy from its explicit environmentId prop (threaded from ChatHeader); useTeleportNativeConflict takes the route-scoped environmentId; CommandPalette import filtering resolves capability per candidate project's own environment rather than the active one.
  • DraftHeroHeadline's new import affordance is a plain in-hero text action matching the file's existing dotted-underline raw-button treatment; no core Button variant covers muted text with color-change (no underline) hover, so it was not flagged.
  • No global CSS, theme variant, mask, or scroll-fade ownership changes in this PR.

🔍 Agent activity — 22 tool calls

💭 I'm scanning through the list of changed files under apps/web/src, noting the command palette, chat view, and chat component files that fall in scope. Prior runs flagged issues around ChatComposer's send-disabled condition near line 2803—I want to verify whether that got resolved between the earlier commits and the current head. I'm also eyeing DraftHeroHeadline's raw button element as a possible shared-primitive concern, and comparing TeleportOutButton's styling against other header buttons for consistency before pulling up the actual files.

1. git_tools — isSendDisabled

2. browse_code — apps/web/src/components/teleport/TeleportOutButton.tsx

💭 I want to verify whether the isSendDisabled condition was properly incorporated into the collapsed composer's primary action logic, so I'm going to look at the current file around lines 2600-2860 to check the actual state of that code.

3. browse_code — apps/web/src/components/chat/ChatComposer.tsx

💭 The isSendDisabled issue at line 2803 was already flagged in a previous run and remains unaddressed, so per the no-duplicate-comments rule I shouldn't repost it. The addComposerImages guard issue from an earlier run, though, does appear to have been fixed. Now I'm looking for genuinely new findings. The TeleportOutButton uses aria-disabled with custom opacity/cursor classes rather than the native disabled state, likely to keep tooltips functional on disabled buttons — a reasonable pattern, though I want to check whether other header buttons handle this the same way and whether Tailwind v4's arbitrary opacity values like opacity-64 are actually valid. I also want to verify the new data-toolbar-control attribute actually has a CSS hook somewhere, since the comment suggests it replaces data-slot=button styling — if there's no matching selector, that's a real dead-hook issue. Similarly, I should check whether DraftHeroHeadline's raw button element duplicates an existing Button variant, and whether TeleportOutButton's placement and props are consistent with sibling buttons in ChatHeader.

4. git_tools — data-toolbar-control

5. git_tools — opacity-64

💭 I notice a discrepancy: the diff shows the TeleportOutButton className including "cursor-not-allowed", but the current grep at head doesn't have it. I'm trying to reconcile whether the diff view I saw earlier is stale compared to the actual head state.

6. git_tools — cursor-not-allowed

7. browse_code — apps/web/src/index.css

💭 The line numbers don't match because grep used the default commit rather than the specific revision, so I should pin the rev to 55e775d for consistent grep results going forward. Now I want to check the [data-toolbar-control] CSS at head.

8. git_tools — data-toolbar-control

💭 The data-toolbar-control hook checks out as an existing documented pattern. I'm now considering using aria-disabled with cursor-not-allowed opacity-64 instead of the native disabled attribute at the call site, since this mirrors Button's disabled styling while keeping tooltips functional—checking if there's repo precedent for this in GitActionsControl.tsx. Since aria-disabled leaves the button keyboard-focusable, I want to confirm the onClick handler still guards against triggering while busy, which seems fine. There are a few more real findings worth chasing down: whether DraftHeroHeadline's raw button element could use an existing Button variant instead, whether TeleportOutButton's size matches other icon buttons in the ChatHeader, and whether the CommandPalette's loading spinner should use a shared Spinner component rather than a raw LoaderIcon with animate-spin. I'll go check these.

9. browse_code — apps/web/src/components/chat/DraftHeroHeadline.tsx

10. browse_code — apps/web/src/components/ui/button.tsx

💭 I'm weighing whether the new headline button should use the existing Button component with a link variant versus the raw styled button pattern already used nearby, since the link variant's built-in padding and height might not match the intended lightweight inline text-action styling. Actually, the more pressing concern is that this raw button lacks the coarse-pointer minimum hit-target sizing (pointer-coarse:after:min-h-11) that Button provides by default, which matters since this control is touch-reachable on the mobile draft hero. I'm leaning toward flagging that this should use Button variant="link" (or similar) to inherit the focus ring and touch target while keeping its contextual color styling, though I want to double-check this isn't just an aesthetic nitpick given the sibling raw button already exists nearby. Given the sibling raw button in the same file also lacks the coarse-pointer hit target, flagging this one would be inconsistent with the file's existing pattern, so I'll hold off. I want to shift focus to more substantive concerns per the rules, starting with environment routing in shared renderers—checking that ChatHeader correctly passes activeThreadEnvironmentId and activeProjectCwd to TeleportOutButton, which looks fine, and now examining how useTeleportNativeConflict is used in ChatView. Now I'm tracing DraftHeroHeadline's environment resolution logic: it falls back from activeProjectRef's environmentId to primaryEnvironmentId for capability checks, and I'm questioning whether activeProjectTitle can be non-null while activeProjectRef is null, since that mismatch could cause capability checks to use the wrong environment. I'm confirming that CommandPalette handles this correctly by filtering import capability per-project environment using the environments lookup, which is the right pattern versus the fallback approach in DraftHeroHeadline. The teleport conflict action and composer gating both check the appropriate environment IDs correctly, and now I'm looking at how TeleportNativeConflictBanner's button sizing compares to other banner actions.

11. git_tools — actions:

💭 I want to check how other conflict banner items in ChatView, like the branch banner or resume compaction, style their action buttons, so I can confirm the right size and variant conventions for this new button. Let me grep ChatView for existing banner action buttons to compare.

12. git_tools — actions: (

💭 Checking the button variant conventions across banners — the new one uses a default primary variant with size="xs", consistent with the existing environment-unavailable banner. I'm also noting it passes both aria-label and aria-busy attributes for accessibility during the forking state. Now I want to shift focus to the biggest remaining risk area: ChatComposer, where the teleport lock disables the editor via a combined disabled condition. I should check what that disabled prop actually feeds into and whether it might break scroll or paste behavior, so I'm going to look at the composer editor region around lines 3480-3560.

13. browse_code — apps/web/src/components/chat/ChatComposer.tsx

💭 I'm checking whether image removal is also gated while teleport-locked, and it looks like the removal callback got wrapped with the same guard so users can't remove images during that state either. But that raises a UI consistency issue: the X buttons on attachment chips still render visually enabled and clickable, so clicking them silently does nothing instead of showing a disabled state — that's a real interaction-semantics gap worth flagging. I want to verify this precisely by checking the exact lines where removal callbacks are gated versus where the buttons themselves are rendered, to confirm the buttons aren't also disabled.

14. browse_code — apps/web/src/components/chat/ChatComposer.tsx

💭 I'm noticing that all these removal controls—image X buttons, chips for terminal context, preview annotations, review comments, element context—silently become no-ops while teleport-locked, yet they still render as enabled with pointer cursors and aria-labels claiming they're removable. That's a real interaction-semantics gap this PR introduces, since the visible controls give no disabled state or feedback. I'm checking whether this falls under the UI Consistency check's remit, since the rules call for preserving accessibility and interaction semantics during migrations. That confirms this is a valid finding—a control appearing enabled while doing nothing silently breaks disabled-state semantics, and the fix is straightforward: pass a disabled flag tied to the teleport-lock state into these remove buttons or their parent chip components. Now I'm trying to verify which of these chip components actually accept a disabled prop, since the inline image remove button in ChatComposer is easy to patch but isn't itself a changed line in the diff, so I need to trace which onRemove props were actually touched by this diff to know where I can anchor the suggestion. Since the prior comment on isSendDisabled is still unaddressed, I shouldn't re-flag it per the no-duplicate rule. That leaves three candidate new findings: the silent no-op remove affordances during teleport-lock, the raw button in DraftHeroHeadline, and TeleportOutButton's use of aria-disabled plus call-site classes overriding the primitive's disabled styling — which fits the pattern of flagging large class strings that override a primitive's core styling. Looking closer, the aria-disabled approach actually preserves the tooltip explaining why the action is disabled, which is arguably better UX, and there's precedent for this pattern in GitActionsControl, so I don't think this is a real finding. The raw button in the hero text is also borderline but matches the existing sibling's dotted-underline style with no primitive variant that fits better, and its focus-visible styling is consistent with the sibling too, so that's not a finding either. That leaves the silent no-op affordance removal during teleport-lock as the one worth digging into further. I think this is a legitimate but modest finding. Now I want to check something more substantive around environment routing — specifically whether useTeleportNativeConflict's environmentId reflects the thread's actual environment rather than whatever's globally active, so I'm going to look at how ChatView derives that value.

15. git_tools — useTeleportNativeConflict({|const environmentId =|activeThreadEnvironmentId|activeProjectCwd

💭 I'm checking whether the environmentId passed into useTeleportNativeConflict from ChatView is the same as activeThread.environmentId, since that would confirm they're equivalent for a thread route.

16. git_tools — environmentId = |environmentId,$

💭 That's not it either. Let me search for the environmentId declaration in ChatViewContent instead.

17. git_tools — const { environmentId|const environmentId

18. git_tools — environmentId

💭 Environment scoping in ChatView looks fine since it's passed explicitly. I'm circling back to the silent no-op affordances issue—specifically that while teleport-locked, image thumbnails still show remove/retry buttons that don't actually do anything, and terminal context chips in the editor have similar dead controls. I need to check whether ComposerPromptEditor properly disables the terminal context chips when locked, and also look at whether preview annotations, review comments, and element context chips—rendered separately above the editor—also have remove buttons that silently no-op. I'll check ComposerReviewCommentChips to see if it accepts a disabled prop.

19. git_tools — Composer(PreviewAnnotation|ReviewComment|ElementContext)Chips

20. browse_code — apps/web/src/components/chat/ChatComposer.tsx

💭 I'm confirming that chip lists and the inline image remove button now silently no-op while teleport-locked, with no disabled state or feedback shown. This looks like a legitimate finding — these remove affordances stay visually enabled but became inert due to the PR's behavior change, which should instead disable them or provide clear feedback. The smallest fix would be reusing the existing toast pattern from addComposerImages, or plumbing a disabled prop through the chip components, though that's a moderate change since it touches three components. I'm weighing whether this finding is significant enough to actually fail the check. The silent no-op is a real broken affordance — a user clicking remove on an attachment gets no feedback at all — and while two prior runs flagged ChatComposer gating, this is a distinct issue about missing affordance feedback rather than over-broad gating, so it's not a duplicate. I want to verify whether the composer applies any visual locked treatment elsewhere, so I'm searching for other usages of isTeleportComposerLocked.

21. git_tools — isTeleportComposerLocked|teleportLockReason

💭 I've confirmed that the lock flag only disables the editor itself, leaving attachment removal, stash, mic, and model picker controls active—some fail silently while others toast errors, which is inconsistent even within the same PR since the stash paths already toast errors. I'll leave an inline comment flagging this inconsistency around the removal callback code. eason for other locked interactions elsewhere, so I'll suggest gating these remove controls with the same locked flag so their disabled state and keyboard behavior stay consistent. I'll skip providing a diff since this spans multiple files and hunks. As for severity, this is a real interaction-semantics gap rather than an aesthetic nitpick, so I'm leaning toward marking the check as a failure rather than neutral, consistent with how similar findings have been treated before. I'm also weighing whether the still-unresolved prior finding about insertComposerTextAtEnd's isSendDisabled should factor into the conclusion — it does remain relevant since it's unaddressed and reflects a genuine regression, so I'll reference it in the check summary without reposting the full comment, which reinforces the failure verdict. I'll post the review with one inline comment and note the outstanding prior finding in the body.

22. modify_pr — ## UI Consistency One new finding on the teleport composer lock. The previously reported insertComposerTextAtEnd gate (apps/web/src/components/chat/ChatComposer.tsx L2803 — isSendDisabled also covers "Image still uploading" / "Sending feedback" / "Messages loading", so file-tree "Add to chat" and mention drops are rejected outside the teleport lock) is still present; not re-commented here.


Agent Credits: 623 credits