5536 feature view accept pending invitations - #122
Draft
a-effort wants to merge 13 commits into
Draft
Conversation
The teams table and the members dialog each hardcoded bg-yellow-500. The invitations dialog needs the same square, so give it a token named for the domain rather than shipping a third copy. Value is #ffd200 from the design, not yellow-500's #eab308, so the existing squares change shade slightly. No .dark override, for the reason the other icon-bg tokens already state. Signed-off-by: Anna Effort <anna.effort@ibm.com>
Transport for the invitee's own invitations, for #5536. Two of the three routes do not exist yet and are the subject of #6010, which also leaves the inbox path undecided. Both unknowns are confined to this module, so settling them is a one-line change here. TeamInvitation re-exports the generated TeamInvitationResponse rather than restating it, so regenerating the client turns any drift between what this assumed and what the backend ships into a compile error. Signed-off-by: Anna Effort <anna.effort@ibm.com>
Its own bundle rather than keys in teams.json, since the components consuming it sit outside components/teams and a later surface elsewhere in the app should not reach into the teams bundle. The invitation sentence is one message with named placeholders so translators control word order, and the count uses ICU plural. Signed-off-by: Anna Effort <anna.effort@ibm.com>
One invitation as a sentence plus either its two actions or the outcome that replaced them. Accepted and declined are deliberately asymmetric, per node 5966:29865: accepted gets a check and full contrast, declined is muted with no icon. Roles map to display names with a fallback to the raw string, so an unrecognised role stays readable. Renders a div rather than an li, so a surface showing one invitation inline is not forced into list markup. Signed-off-by: Anna Effort <anna.effort@ibm.com>
Presentational: it takes the list and callbacks and owns no fetching. Once nothing is left to act on it holds the confirmation for 4000ms and closes itself, reusing sonner's default duration so the app has one dwell rather than two. Pointer entry cancels the timer outright rather than pausing it, since a timer that resumes on pointer exit closes at an unpredictable moment. Radix's usual pause-while-focused signal is unusable because the dialog traps focus. Scroll is scoped to the list rather than the dialog. DialogContent's own overflow puts the close button inside the scroll container, so past roughly seven invitations the X scrolls out of reach. Resolving a row unmounts the focused button, so focus moves to the next actionable row or to the close button, which is what the new data-slot on the primitive is for. Signed-off-by: Anna Effort <anna.effort@ibm.com>
Resolutions are committed after the server confirms, not optimistically: both actions are irreversible and the dialog stays open, so a rollback would visibly flip a row back. Named Data to keep it distinct from the context consumer of the same idea. An accidental second instance holds its own resolutions, which is the divergent count the provider exists to prevent, so the two should not be one import typo apart. Signed-off-by: Anna Effort <anna.effort@ibm.com>
One provider, one dialog, N triggers. Two mounted copies of a self-contained component would hold separate resolutions, so resolving in one would leave the other's count stale with nothing logged. Mounted in AppShell, which an app-wide fetch would not justify on its own. The fetch is gated on a mounted consumer instead, so a page with no trigger issues no request. That gating is also what lets this land ahead of #6010 with no feature flag: nothing calls the missing routes until a trigger is rendered. Focus return is handled here rather than by a DialogTrigger, which would force every trigger into one dialog's subtree. The element that opened the dialog is commonly gone by close, since resolving the last invitation unmounts every trigger. Signed-off-by: Anna Effort <anna.effort@ibm.com>
The Settings > Teams trigger, hidden at zero pending. Not yet rendered anywhere. Mounting it registers the consumer that starts the fetch, so the Teams page wiring waits on #6010. Takes the height and padding of the Create team button it will sit beside rather than the design's standalone 36px chip. Signed-off-by: Anna Effort <anna.effort@ibm.com>
Measured against the Figma frame: 8px padding on all four sides with a content-driven height, 4px corners, and an explicit foreground on the label, which was inheriting its colour. The icon square is 17.5px rather than the drawn 20px. At 20px the chip stands 38px tall, and the Settings tab row reserves 32px for its toolbar contents, so the drawn size pushes the whole row taller. The three outline buttons opt out of the variant's shadow-xs, which reads as an unwanted shadow in light mode. Removing it at the variant belongs to #35, so these join the dead overrides that PR already sweeps up. Signed-off-by: Anna Effort <anna.effort@ibm.com>
The step this feature could not land earlier. Mounting the chip is what registers a consumer, which is what enables the fetch, so this is the only change that calls the routes IBM/mcp-context-forge#6010 has yet to add. Everything before it merges inert. Teams reacts to an accepted invitation through a counter in context rather than a callback prop, so a later surface elsewhere in the app can react independently of whichever one rendered the trigger. e2e/invitations.spec.ts stubs the three routes through the api-mock fixture, one test per visual state. Under E2E_REAL_API=true it becomes the integration test, once e2e/seed/seed.ts can seed a pending invitation for the test user. Signed-off-by: Anna Effort <anna.effort@ibm.com>
Cut the build-time narrative: which routes did not exist yet, which alternatives were weighed, why a structure was chosen over another. That belongs in the PR and the issues, and goes stale in the code. What is left says what a reader needs to work with it: mount one hook instance, the row is not an <li>, the dwell never resumes, focus return is manual because the opener is usually gone. Signed-off-by: Anna Effort <anna.effort@ibm.com>
Corners at 12px. The sentence and the action row lose their 16px inset, so both sit flush with the header against the dialog's own padding. Roles render lowercase, matching teams.json, with the English article inside the value: "an owner", "a member". pt-BR and es-ES carry no article, since "como proprietário" is right and "como um proprietário" is not. The resolved row keeps the height of the buttons it replaces, so accepting or declining no longer shifts the invitation below it, and the accepted check uses the success token. The list no longer scrolls. scrollbar-gutter-stable goes on DialogContent, which is a scroll container through the primitive; the property does not inherit, so it needs its own. Signed-off-by: Anna Effort <anna.effort@ibm.com>
The chip renders when invitations are pending and not otherwise, and the teams list refetches when acceptedCount increments. The refetch is what puts a newly joined team in the table, and it had no coverage at all. Signed-off-by: Anna Effort <anna.effort@ibm.com>
This was referenced Sep 11, 2026
[API]: Invitee inbox endpoint (GET /users/me/invitations) + decline route
IBM/mcp-context-forge#6010
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the invitee-facing UI for IBM/mcp-context-forge#5536. A count chip in the Settings > Teams toolbar opens a dialog listing every pending invitation, each with Decline and Join team. Resolving one confirms in place and decrements the count behind the dialog. Once nothing is left to act on, the dialog holds the confirmation for 4 seconds and closes itself, cancelled by pointer entry.
teams.invite.mp4
Structure is one provider, one dialog, N triggers. PendingInvitationsProvider mounts in AppShell and owns the data; triggers are thin consumers of usePendingInvitations(). The fetch is gated on a mounted consumer, so pages with no trigger issue no request. MVP ships one trigger.
Blocked on IBM/mcp-context-forge#6010 and not mergeable until it lands. Two of the three routes do not exist: nothing lists the invitations addressed to the caller, and there is no decline route. Without the list there is no token, so even the working accept route is unreachable.
Resolutions are committed after the server confirms rather than optimistically, since both actions are irreversible and the dialog stays open. The inviter renders as an email address, because TeamInvitationResponse carries no name field. Roles render lowercase to match the rest of the app.
e2e/invitations.spec.ts has one test per visual state, stubbed through the api-mock fixture, and becomes the integration test under E2E_REAL_API=true once e2e/seed/seed.ts can seed an invitation. It has not been run yet, so that needs doing before this leaves draft. Nothing in CI covers appearance either, so it is worth a pass through both themes.
Two follow-ups, neither in scope. IBM/mcp-context-forge#6796 covers the untranslated role picker. And the Settings toolbar only renders once a user has a team, so someone invited to their first team never sees the chip; the last test in the spec documents it and nothing tracks it yet.