Skip to content

feat(web): pet corner anchor setting#2300

Open
neogenix wants to merge 5 commits into
nexu-io:mainfrom
eefynet:feat/web-pet-corner-anchor
Open

feat(web): pet corner anchor setting#2300
neogenix wants to merge 5 commits into
nexu-io:mainfrom
eefynet:feat/web-pet-corner-anchor

Conversation

@neogenix
Copy link
Copy Markdown

Why

The pet overlay anchors to the bottom-right corner of the screen by
default. In several layouts that corner happens to sit over real UI —
the "Save to disk" button on the artifact preview, the running-agent
status pill, and the design-system picker dropdown on smaller windows.
The pet eats those clicks unless the user drags it (which is awkward
once and forgotten).

Letting the user pick which corner the pet anchors to solves it at the
source. The drag-to-reposition mechanic is preserved within the chosen
corner; the corner choice persists across reloads.

What users will see

A new Position row in Pet Settings (Settings → Pet) with a four-way
segmented picker: top-left, top-right, bottom-left, bottom-right.
Default is bottom-right, matching today's behavior, so existing users
notice nothing on upgrade until they open the picker.

Picking a different corner moves the pet immediately. The speech-bubble
alignment and tail flip to the correct side of the sprite for
left-anchored corners (otherwise the bubble would hang off the wrong
side). The drag-to-reposition mechanic still works inside the new
corner.

Surface area

  • UI — new picker in Pet Settings
  • i18n keys — 5 new keys (pet.fieldCorner,
    pet.corner.top-left, .top-right, .bottom-left, .bottom-right)
    added to apps/web/src/i18n/types.ts and all 19 locale files

No CLI surface needed. PetConfig is a localStorage-only web pref —
the daemon never reads or writes it, and there's no existing
od pet … subcommand. This is consistent with the rest of PetConfig
(adopted, enabled, petId, custom.*) and with peer prefs like
theme and accentColor that also live web-only. The new corner
field follows that exact precedent. If a future PR adds an od pet …
surface (e.g. for headless adoption), corner should be added to it in
the same change.

Screenshots

Settings → Pet now shows a four-segment "Position" picker below the
existing controls. (Screen capture pending; the feature is visible only
in the Pet Settings panel — the rest of the UI is unchanged.)

Bug fix verification

This is a feature addition, not a bug fix. Verification approach:

  • Vitest spec apps/web/tests/components/pet-corner.test.tsx (7 tests):
    default value, 4 corner-style assertions (top/right vs bottom/left
    inline styles), normalizePet preserves stored corner and defaults
    to bottom-right for legacy configs without the field.
    • 6 of 7 red on origin/main; the 7th was a coincidental pass that
      is now covered with stronger assertions.
  • Playwright spec e2e/ui/pet-corner-anchor.test.ts (10 tests):
    4 quadrant positioning checks (real bounding rect against viewport
    size), 4 bubble-tail side checks (getComputedStyle(bubble, '::after')
    for left-anchored corners — the regression the 10-pass review caught),
    one live-picker-change check, one persistence-across-reload check.
    • All 10 green here. Tests 5-8 (bubble tail) red on the source state
      before commit 332a9c39 (the left-anchor CSS override).

Validation

  • pnpm guard (clean)
  • pnpm typecheck (clean)
  • pnpm --filter @open-design/web test pet-corner — 7/7 pass
  • pnpm --filter @open-design/web test PetOverlay PetSettings — adjacent suites pass with the updated fixture (corner: 'bottom-right')
  • pnpm exec playwright test -c playwright.config.ts pet-corner-anchor
    — 10/10 pass (browser-verified in Chromium, 1.28s)

Adjacent issues (out of scope)

  • PetSettings's role="radiogroup" controls (accent swatches, atlas
    rows, built-in pet grid, and the new corner picker) do not implement
    arrow-key navigation. This is a pre-existing pattern across every
    segmented picker in PetSettings, not introduced here. Worth a
    follow-up a11y sweep covering all of them at once.
  • The new drag-math branches isLeftAnchored / isTopAnchored in
    PetOverlay.tsx have Playwright coverage of the final positioning
    but no unit tests for the drag delta calculation itself. The Vitest
    suite covers the static corner style; the dynamic drag math relies
    on Playwright. Follow-up could add a direct unit test.
  • AGENTS.md's i18n section said "18 locale files" but the repo
    actually ships 19 (it.ts was added but the count and explicit
    locale list were never updated). Corrected in the docs(pet) commit
    alongside the docs/codex-pets.md Corner section.

Capability exposure note

Per AGENTS.md "Capability exposure (UI/CLI dual-track)", a user-facing
capability normally requires both a web UI surface and an od CLI
subcommand in the same PR. The corner field is a pure
localStorage-only display preference with no daemon counterpart;
adding a CLI mirror would require first introducing a daemon-side
PetConfig persistence surface, which is a much larger change and
not justified by this feature's scope. The new field follows the same
web-only precedent as every other PetConfig field. If/when a daemon
pet surface lands, corner should be wired into it at that time.

neogenix added 4 commits May 19, 2026 13:29
Add a corner picker to pet settings letting users pin the floating
overlay to any of the four viewport corners (top-left, top-right,
bottom-left, bottom-right). Default stays bottom-right.

The corner field is persisted in the PetConfig localStorage payload.
Older configs without the field are normalised to 'bottom-right' at
read time via normalizePet, so no migration script is needed.

PetOverlay now derives top/bottom/left/right inline styles from the
active corner instead of hardcoding right+bottom; the other two axes
are left undefined so existing drag behaviour and CSS rules are unaffected.

Red spec: apps/web/tests/components/pet-corner.test.tsx (7 tests)
For left-anchored corners (top-left, bottom-left) the .pet-overlay
flex container and .pet-bubble::after tail arrow were both right-aligned,
causing the bubble to float off to the right of the sprite instead of
sitting flush above it.

Fix: expose the active corner as data-corner on .pet-overlay and add
CSS overrides that switch align-items to flex-start and relocate the
bubble tail arrow to the left side of the speech bubble when the pet
is in a left-anchored corner.
- docs/codex-pets.md: add 'Corner anchor' section explaining the four-way
  picker, the web-only persistence boundary, and the inline-style + CSS
  approach used by PetOverlay + index.css for left-anchored corners.
- AGENTS.md i18n section: correct '18 locale files' to '19 locale files'
  and add 'it' to the explicit locale list (the Italian locale was added
  but the count and list were never updated).
@lefarcen lefarcen requested a review from PerishCode May 19, 2026 19:41
@lefarcen lefarcen added size/XL PR changes 700-1500 lines risk/medium Medium risk: regular code changes type/feature New feature labels May 19, 2026
Copy link
Copy Markdown
Contributor

@PerishCode PerishCode left a comment

Choose a reason for hiding this comment

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

@neogenix — thanks for the careful corner-anchor work. The PetConfig.corner type, the VALID_CORNERS validation in normalizePet, the legacy {right, bottom}{x, y} migration in loadPosition, the corner-aware drag math, and the i18n + types.ts updates across all 19 locales all look solid. The Vitest + Playwright coverage of quadrant positioning and the left-anchor bubble-tail regression is nicely scoped.

One non-blocking observation about the top-anchored corners is in the inline comment below — the rest of the change is in good shape.

🔁 Powered by Looper · runner=reviewer · agent=claude-code · An autonomous AI dev team for your GitHub repos.

Comment thread apps/web/src/index.css
Comment on lines +17186 to +17191
/* Left-anchored corners: move the tail to the left side of the bubble. */
.pet-overlay[data-corner='top-left'] .pet-bubble::after,
.pet-overlay[data-corner='bottom-left'] .pet-bubble::after {
right: auto;
left: 18px;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Top-anchored corners get a bubble in the corner, not a sprite. This new rule and the align-items: flex-start rule a few blocks up flip the horizontal alignment + tail side for data-corner='*-left', but there is no parallel vertical override for data-corner='top-*'. Because .pet-bubble is the first child of .pet-overlay in JSX (apps/web/src/components/pet/PetOverlay.tsx:470-504) and the container uses flex-direction: column, the bubble always renders above the sprite.

For bottom-anchored corners this is fine (the container's bottom edge is pinned by bottom: pos.y, so the bubble grows upward from a stationary sprite). For top-anchored corners the container's top edge is pinned by top: pos.y — so when the bubble auto-opens on mount the bubble takes the corner the user just picked and the sprite is pushed down by bubble_height + 10px gap. A bare greeting bubble adds ~80–120px; once the pet-task-list panel populates, the sprite can sit 200+px below the top corner. The pet also visibly jumps down whenever the bubble re-opens after the 4s auto-tuck (PetOverlay.tsx:179-185).

Why this matters for the stated goal. The PR is motivated by "the pet eats clicks on Save-to-disk / status pill / dropdown" — i.e. the sprite is the click target you are trying to move out of the way. A user who picks top-right to dodge a top-bar control still sees the sprite drift ~100px below the top edge whenever the bubble is open, which partially undoes the move.

Suggested fix (mirror of the left-anchor pattern, in the same file):

.pet-overlay[data-corner='top-left'],
.pet-overlay[data-corner='top-right'] {
  flex-direction: column-reverse;
}
.pet-overlay[data-corner='top-left'] .pet-bubble::after,
.pet-overlay[data-corner='top-right'] .pet-bubble::after {
  bottom: auto;
  top: -6px;
  border-bottom: 0;
  border-right: 0;
  border-top: 1px solid var(--pet-accent);
  border-left: 1px solid var(--pet-accent);
}

Worth noting that the existing Playwright quadrant positioning tests in e2e/ui/pet-corner-anchor.test.ts assert against overlay.boundingBox(), which spans both bubble and sprite — they stay green even when the visible sprite ends up well below the top corner. Adding a .pet-sprite bounding-box assertion (sprite top edge < QUAD_THRESHOLD for top corners) would lock the sprite to the chosen corner regardless of bubble state.

🔁 Powered by Looper · runner=reviewer · agent=claude-code · An autonomous AI dev team for your GitHub repos.

@lefarcen lefarcen requested a review from PerishCode May 19, 2026 20:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk/medium Medium risk: regular code changes size/XL PR changes 700-1500 lines type/feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants