feat(members): open the last member on arrival, mirror it in the URL - #8546
feat(members): open the last member on arrival, mirror it in the URL#8546CrysisDeu wants to merge 1 commit into
Conversation
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
UX Review (Fable 5) — ✅ PASSUX-level review of UX-Verdict: PASS Auto-open, last-member restore, and URL mirroring remove the empty-column dead end, and the gone-member fallback says who opened instead of swapping silently. Suggestions
[UX-REVIEWED] ae4804d |
Design Review (Fable 5) — ✅ PASSDesign-level review of Design-Verdict: PASS URL as the single selection source with one activate path, name-not-slug identity, and an announced gone-member fallback is the right, proportionate shape. [DESIGN-REVIEWED] ae4804d |
First Principles Review (Fable 5) — 🟡 CONCERNSPremise-level review of All verification is done. I've read the contract, intent, patch, the full page source, and counted consumers of every new surface. Composing the review now. First-Principles-Verdict: CONCERNS The quoted ask is met by items 1–2 alone; the URL layer is unrequested capability that carries the gone-member notices, 26 catalog lines, and the history-state protocol. What this change shipsIntent: land on the conversation you last had open on the Members page instead of an empty column — an ADDITION (
Watch
Subtractions
[FIRST-PRINCIPLES-REVIEWED] ae4804d |
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsI've analyzed the single candidate and traced the URL-sync effect, the Candidate 1 (auto-open drains unread badge): The drain effect at lines 383–385 fires because No new groundable defect emerged from falsification: the effect is a stable fixed point (a valid No findings. [OPUS-REVIEWED] ae4804d Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
cdffffd to
4777a72
Compare
4777a72 to
e891871
Compare
|
UX Review disposition — fixed in
|
|
UX Review disposition — fixed in
|
|
UX Review disposition — accepted-and-deferred → #8570
|
|
UX Review disposition — accepted-and-deferred → #8570
|
e891871 to
0d139ea
Compare
0d139ea to
d11cc70
Compare
d11cc70 to
ab6615e
Compare
|
UX Review disposition — fixed in
|
|
UX Review disposition — fixed in
|
|
UX Review disposition — fixed in
|
ab6615e to
f44861a
Compare
bolichen97
left a comment
There was a problem hiding this comment.
Direction is right and the implementation is solid: no history loop (all fallback URL writes use replace), exact-name lookup so a stale link can never open the wrong member's thread, both gone-member notices in all 13 catalogs, and CI is green on this head. Two behaviours I want changed before approving:
- Every member click pushes a history entry. Ten clicks in the roster means ten Back presses to leave
/members. The Sessions sidebar does not behave this way when switching conversations. Pleasereplacewhen switching between members and onlypushwhen entering a thread from the roster (or from a deep link), so Back leaves the page after one press. - A dead link overwrites the remembered member.
activate()writesLAST_MEMBER_KEYon every open including the fallback, so following one stale?member=ghostlink permanently replaces the user's remembered member with the first row. Skip the memory write on the fallback path; the test'a URL naming a member wins...'should be adjusted accordingly.
Low, non-blocking: the back/forward claim rests on a useNavigate spy (navigateSpy(-1)), not a driven history test -- one MemoryRouter test walking two members and pressing Back would pin the behaviour above. Will re-approve as soon as those two land.
f44861a to
83915af
Compare
The Crew Members page landed on an empty column every visit and forgot
which member was open when the user navigated away and back. Now:
- A visit that names no member opens the remembered one, else the first
row in display order — never the empty column.
- The last member opened is remembered per browser (mc-members-last-member,
via safeStorage), so leaving for Sessions/Settings and returning, or a
reload, lands on the same conversation. A remembered member that was
deleted or renamed falls back to the first row without an error.
- The open member rides the URL (?member=<name>): back/forward walks the
conversations and a link lands on one. The value is the exact crew name,
not the slug — the slug is lossy and would misroute Oncall to oncall.
A link naming a member that is gone takes the same fallback and says so
in a warn-toned status above the thread, leading with the swap
("Showing X — 'Y' is no longer on the roster."; 13 catalogs + en-XA):
the user asked for someone specific, and a silently mounted other thread
is the misroute this page exists to prevent.
- Below md the page stays a two-level list->detail navigation: no
?member= IS the roster, so no auto-open there (same rule as
SidePanelLayout's remembered tab). A stale ?member= returns to the
roster with the same notice above the list. The header back pops the
entry the roster pushed (no duplicate roster entry in history) and
drops the param in place for a deep link, which has no roster behind it.
Tests cover default, restore, stale fallback (memory: silent; URL: with
notice on both sides of md, retired on the next open), URL-over-memory,
click writes URL+memory, and the below-md back semantics.
83915af to
ae4804d
Compare
Summary
The Crew Members page landed on an empty middle column on every visit and forgot which member was open when you navigated away and back. Zezhen's ask: 「crew mode 页面点开要打开一个 crew 的 chat,如果来回 navigate 要回到上一个打开的」.
mc-members-last-member, viasafeStorage), so leaving for Sessions / Settings and coming back, or a reload, lands on the same conversation. A remembered member that was deleted or renamed falls back to the first row without an error./members?member=<name>): back/forward walks the conversations, and a link lands on one. The value is the exact crew name, not the slug — the slug is lossy (Oncall/oncallshare one), and a slug link would misroute to the other member's thread, the exact failure this page's header comment says it exists to prevent. The URL is the single source of which member is open; clicks write the URL and one sync effect opens the thread, so click / back-forward / link / restore share one path. A link naming a member that is gone (deleted/renamed) falls back to the first row and says so in a warn-toned status above the thread, leading with the swap — "Showing alpha — 'ghost' is no longer on the roster." (pages.membersPage.member_gone; 13 catalogs + regenerateden-XA) — the user asked for someone specific, so the swap is neither silent nor muted. The remembered-member fallback stays silent: there the user named nobody.?member=IS the roster, so no auto-open there (the same ruleSidePanelLayoutapplies to its remembered tab); a stale?member=returns to the roster with the same notice above the list (member_gone_roster), and the header back button pops the entry the roster pushed (so the browser's own Back does not land on a duplicate roster entry) while a deep link — which has no roster entry behind it — drops the param in place.Storage scope: one key per origin. The roster is the gateway's global crew list and localStorage is already per-gateway, so there is no frontend workspace dimension to key on.
Two new strings,
pages.membersPage.member_goneandmember_gone_roster, added to all 13 catalogs withen-XAregenerated; the existingpick_a_memberempty state is still what the mobile roster and an empty crew list show.Recording
recorded from cdffffd (the PR head ae4804d (rebased onto main c791f0f) differs from it only by the stale-
?member=handling — the below-md return-to-roster branch and the desktop gone-member notice — which the recording's flow does not exercise) · isolated pod (kirocrew pod up) built from this branch · mode: default · real server + real roster (the pod's own crews), no fixtures · the only injected element is the bottom caption bar, which printslocation.pathname + location.searchso the URL is visible (the address bar is outside the viewport capture). GIF pinned to a since-dropped evidence commitd599c35b2on this branch.Tests
website/src/pages/members/MembersPage.test.tsxresolveDefaultMember(pure): default → first row; restore → remembered; stale → first row; empty roster →undefined.?member=beats memory;?member=naming a gone member → first row + URL rewritten +member-gone-notice(role=status, warn tone, leads with the swap) that retires when another member opens; click writes URL + memory andaria-current.memberThreadnot called, URL untouched); stale?member=→ URL cleared, roster shown withmember-gone-roster-notice, no POST, notice retires on tap; tap → thread + URL; header back → pops the roster-pushed entry (navigate(-1)), memory kept; from a deep link → param dropped in place, roster shown.Local:
tsc -bandeslinton both files clean. Vitest left to CI per the work item's rule.Not changed
No refactor of the page beyond splitting the sort into unfiltered/filtered (so "first member" ignores the search box) and moving the thread-open body into
activateso the URL effect and the same-member re-click share it.