Skip to content

[Refactor] Merge pin button and status slot into a single left-side indicator #150

@Astro-Han

Description

@Astro-Han

What task are you trying to do?

Unify the session row left-side region so a single reserved 24px slot displays either the running / permission / error / unseen indicator or the pin icon, instead of two separate reserved slots.

Context

PR #149 (closing #143) stabilized the status slot width and redrew Pin / Filter icons, but kept the two-slot architecture: the pin button is rendered via SessionItem leadingSlot (before SessionRow) and the status indicator lives inside SessionRow. This creates a visible gap between the pin position and the running spinner position, which does not match the original intent ("pin button position displays as running when running").

Deferred from #143 scope during review because the architectural change is medium-risk and deserves its own ticket.

What would a good result look like?

  • Top-level session rows have a single reserved size-6 slot with priority: running > permission > error > unseen > pin (if pinned or hover) > empty.
  • Child sessions keep the existing in-row status slot (no pin button at nested levels).
  • Pin button a11y contract preserved: tabIndex={isPinned() ? 0 : -1}, aria-hidden hover-to-discover, stopPropagation() on click.
  • When pinned + running, the spinner shows visually but the pin button must stay keyboard-reachable so the user can still unpin.
  • Top-level title baseline shifts left by ~28px (24px slot + gap-1) after the merge, consistent across all top-level rows. Expected, not a regression.

Which audience does this matter to most?

Both

Extra context

Implementation sketch

  • packages/app/src/pages/layout/sidebar-items.tsx:
    • Add leadingSlot?: JSX.Element prop on SessionRow.
    • Append a final <Match when={props.leadingSlot}>{props.leadingSlot}</Match> to the status <Switch> so the pin button renders only when no status is active.
    • Remove the outer leadingSlot wrapper at sidebar-items.tsx:229-231.
    • In SessionItem, pass leadingSlot={!props.level && props.leadingSlot ? props.leadingSlot(props.session) : undefined} to SessionRow.
  • packages/app/src/pages/layout/pawwork-sidebar.tsx:
    • Pin button implementation mostly unchanged (size-6 wrapper, click handler, tint).
    • May need a11y tweak so the pin button remains reachable when pinned + running (candidate: always render the button, overlay status visually via CSS rather than Switch-hide).

Risk notes

  1. A11y edge case (highest risk): when pinned + running, naive Switch priority hides the pin button from the tree. Need an approach that keeps the button tab-reachable while showing the status indicator.
  2. Event bubbling: pin click inside <A> must stopPropagation() (current code already does).
  3. Combinatorial testing: 4 status states × pinned/unpinned × hover/no-hover = 16 top-level combinations, plus child-level variants.

Out of scope

  • Filter / sort button (unchanged).
  • Status priority order (unchanged: permission > running > error > unseen per sidebar-items.tsx:173).
  • Child session layout (unchanged).

Manual verification checklist (run bun dev:desktop)

  1. Idle unpinned row → slot empty, title at unified baseline.
  2. Hover unpinned row → outline pin in text-text-weak.
  3. Pinned row → filled pin in text-accent-brand.
  4. Running session (pinned) → spinner visible; pin still focusable via Tab; Enter unpins.
  5. Running session (unpinned) → spinner visible; hovering does not reveal pin.
  6. Permission / error / unseen → yellow / red / blue dot respectively.
  7. Child sessions → no pin at nested levels; status indicator works normally.
  8. Keyboard-only: Tab order through sidebar remains stable across state transitions.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium priorityenhancementNew feature or requestuiDesign system and user interface

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions